Compare commits

..

12 Commits

Author SHA1 Message Date
Min Idzelis
053dd490b4 fix test 2025-06-15 03:40:31 +00:00
Min Idzelis
f4f81341da tests 2025-06-15 03:22:32 +00:00
Min Idzelis
3e66913cf8 chore: lint 2025-06-15 03:16:39 +00:00
Min Idzelis
504309eff5 chore: make sql 2025-06-15 03:04:23 +00:00
Min Idzelis
b44abf5b4b Merge remote-tracking branch 'origin/main' into timeline_events 2025-06-15 02:58:04 +00:00
Min Idzelis
c76e8da173 chore: cleanup 2025-06-15 02:47:18 +00:00
Min Idzelis
9cc2189ef7 chore: remove unused code and fix test expectations
- Remove unused activityManager import from asset viewer components
- Remove unused function stub in activity manager
- Fix album service test expectations for emit parameters
- Clean up formatting in person repository mock
- Update trash service tests for emit event changes
2025-06-15 02:25:42 +00:00
Min Idzelis
6b87efe7a3 feat(web): improve websocket filtering and add restored assets support
- Refactor websocket support to use modular filter functions
- Add support for on_asset_restore events
- Improve handling of asset updates with proper filtering for visibility, favorites, trash, tags, albums, and persons
- Add null checks in timeline manager for empty arrays
2025-06-15 02:25:18 +00:00
Min Idzelis
7b75da1f10 refactor(server): change asset update events to send IDs instead of full assets
- Change on_asset_update event to send asset IDs array instead of full AssetResponseDto
- Add asset.update event emission in asset service for update operations
- Update notification handlers to work with asset IDs
- Improve update logic to avoid duplicate events when metadata is updated
- Update frontend websocket types to match new event format
2025-06-15 02:24:06 +00:00
Min Idzelis
a7559f0691 feat(server): add websocket events for activity changes
- Add 'activity.change' event to event repository
- Emit event when new activity (reaction/comment) is created
- Add notification handler to broadcast activity changes to relevant users
- Update frontend websocket types to include on_activity_change event
- Update tests to mock album repository calls
2025-06-15 02:23:09 +00:00
Min Idzelis
6f2f295cf3 refactor(server): clean up asset repository and add getTrashedIds method
- Remove redundant return statement in asset repository update method
- Add getTrashedIds method to trash repository for retrieving trashed asset IDs by user
2025-06-15 02:22:37 +00:00
Min Idzelis
b3d080f6e8 feat(server,web): add websocket events for album updates
and person face changes
2025-06-11 10:52:43 +00:00
1260 changed files with 20620 additions and 57926 deletions

View File

@@ -55,7 +55,7 @@
"userEnvProbe": "loginInteractiveShell", "userEnvProbe": "loginInteractiveShell",
"remoteEnv": { "remoteEnv": {
// The location where your uploaded files are stored // The location where your uploaded files are stored
"UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./library}", "UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./Library}",
// Connection secret for postgres. You should change it to a random password // 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 // Please use only the characters `A-Za-z0-9`, without special characters or spaces
"DB_PASSWORD": "${localEnv:DB_PASSWORD:postgres}", "DB_PASSWORD": "${localEnv:DB_PASSWORD:postgres}",

View File

@@ -7,34 +7,12 @@ export DEV_PORT="${DEV_PORT:-3000}"
# Devcontainer: Clone [repository|pull request] in container volumne # Devcontainer: Clone [repository|pull request] in container volumne
WORKSPACES_DIR="/workspaces" WORKSPACES_DIR="/workspaces"
IMMICH_DIR="$WORKSPACES_DIR/immich" IMMICH_DIR="$WORKSPACES_DIR/immich"
IMMICH_DEVCONTAINER_LOG="$HOME/immich-devcontainer.log"
log() {
# Display command on console, log with timestamp to file
echo "$*"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >>"$IMMICH_DEVCONTAINER_LOG"
}
run_cmd() {
# Ensure log directory exists
mkdir -p "$(dirname "$IMMICH_DEVCONTAINER_LOG")"
log "$@"
# Execute command: display normally on console, log with timestamps to file
"$@" 2>&1 | tee >(while IFS= read -r line; do
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $line" >>"$IMMICH_DEVCONTAINER_LOG"
done)
# Preserve exit status
return "${PIPESTATUS[0]}"
}
# Find directories excluding /workspaces/immich # Find directories excluding /workspaces/immich
mapfile -t other_dirs < <(find "$WORKSPACES_DIR" -mindepth 1 -maxdepth 1 -type d ! -path "$IMMICH_DIR" ! -name ".*") mapfile -t other_dirs < <(find "$WORKSPACES_DIR" -mindepth 1 -maxdepth 1 -type d ! -path "$IMMICH_DIR" ! -name ".*")
if [ ${#other_dirs[@]} -gt 1 ]; then if [ ${#other_dirs[@]} -gt 1 ]; then
log "Error: More than one directory found in $WORKSPACES_DIR other than $IMMICH_DIR." echo "Error: More than one directory found in $WORKSPACES_DIR other than $IMMICH_DIR."
exit 1 exit 1
elif [ ${#other_dirs[@]} -eq 1 ]; then elif [ ${#other_dirs[@]} -eq 1 ]; then
export IMMICH_WORKSPACE="${other_dirs[0]}" export IMMICH_WORKSPACE="${other_dirs[0]}"
@@ -42,39 +20,38 @@ else
export IMMICH_WORKSPACE="$IMMICH_DIR" export IMMICH_WORKSPACE="$IMMICH_DIR"
fi fi
log "Found immich workspace in $IMMICH_WORKSPACE" echo "Found immich workspace in $IMMICH_WORKSPACE"
log ""
run_cmd() {
echo "$@"
"$@"
}
fix_permissions() { fix_permissions() {
log "Fixing permissions for ${IMMICH_WORKSPACE}" echo "Fixing permissions for ${IMMICH_WORKSPACE}"
run_cmd sudo find "${IMMICH_WORKSPACE}/server/upload" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres/*" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres" -exec chown node {} + run_cmd sudo find "${IMMICH_WORKSPACE}/server/upload" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres/*" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres" -exec chown node {} +
# Change ownership for directories that exist run_cmd sudo chown node -R "${IMMICH_WORKSPACE}/.vscode" \
for dir in "${IMMICH_WORKSPACE}/.vscode" \
"${IMMICH_WORKSPACE}/cli/node_modules" \ "${IMMICH_WORKSPACE}/cli/node_modules" \
"${IMMICH_WORKSPACE}/e2e/node_modules" \ "${IMMICH_WORKSPACE}/e2e/node_modules" \
"${IMMICH_WORKSPACE}/open-api/typescript-sdk/node_modules" \ "${IMMICH_WORKSPACE}/open-api/typescript-sdk/node_modules" \
"${IMMICH_WORKSPACE}/server/node_modules" \ "${IMMICH_WORKSPACE}/server/node_modules" \
"${IMMICH_WORKSPACE}/server/dist" \ "${IMMICH_WORKSPACE}/server/dist" \
"${IMMICH_WORKSPACE}/web/node_modules" \ "${IMMICH_WORKSPACE}/web/node_modules" \
"${IMMICH_WORKSPACE}/web/dist"; do "${IMMICH_WORKSPACE}/web/dist"
if [ -d "$dir" ]; then
run_cmd sudo chown node -R "$dir"
fi
done
log ""
} }
install_dependencies() { install_dependencies() {
log "Installing dependencies" echo "Installing dependencies"
( (
cd "${IMMICH_WORKSPACE}" || exit 1 cd "${IMMICH_WORKSPACE}" || exit 1
export CI=1 FROZEN=1 OFFLINE=1 run_cmd make install-server
run_cmd make setup-web-dev setup-server-dev run_cmd make install-open-api
run_cmd make build-open-api
run_cmd make install-web
) )
log ""
} }

View File

@@ -3,7 +3,6 @@ services:
build: build:
target: dev-container-server target: dev-container-server
env_file: !reset [] env_file: !reset []
hostname: immich-dev
environment: environment:
- IMMICH_SERVER_URL=http://127.0.0.1:2283/ - IMMICH_SERVER_URL=http://127.0.0.1:2283/
volumes: !override volumes: !override
@@ -13,8 +12,8 @@ services:
- open_api_node_modules:/workspaces/immich/open-api/typescript-sdk/node_modules - open_api_node_modules:/workspaces/immich/open-api/typescript-sdk/node_modules
- server_node_modules:/workspaces/immich/server/node_modules - server_node_modules:/workspaces/immich/server/node_modules
- web_node_modules:/workspaces/immich/web/node_modules - web_node_modules:/workspaces/immich/web/node_modules
- ${UPLOAD_LOCATION:-upload1-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/workspaces/immich/server/upload - ${UPLOAD_LOCATION-./Library}/photos:/workspaces/immich/server/upload
- ${UPLOAD_LOCATION:-upload2-devcontainer-volume}${UPLOAD_LOCATION:+/photos/upload}:/workspaces/immich/server/upload/upload - ${UPLOAD_LOCATION-./Library}/photos/upload:/workspaces/immich/server/upload/upload
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
immich-web: immich-web:
@@ -30,9 +29,8 @@ services:
POSTGRES_USER: ${DB_USERNAME-postgres} POSTGRES_USER: ${DB_USERNAME-postgres}
POSTGRES_DB: ${DB_DATABASE_NAME-immich} POSTGRES_DB: ${DB_DATABASE_NAME-immich}
POSTGRES_INITDB_ARGS: '--data-checksums' POSTGRES_INITDB_ARGS: '--data-checksums'
POSTGRES_HOST_AUTH_METHOD: md5
volumes: volumes:
- ${UPLOAD_LOCATION:-postgres-devcontainer-volume}${UPLOAD_LOCATION:+/postgres}:/var/lib/postgresql/data - ${UPLOAD_LOCATION-./Library}/postgres:/var/lib/postgresql/data
redis: redis:
env_file: !reset [] env_file: !reset []
@@ -44,6 +42,3 @@ volumes:
open_api_node_modules: open_api_node_modules:
server_node_modules: server_node_modules:
web_node_modules: web_node_modules:
upload1-devcontainer-volume:
upload2-devcontainer-volume:
postgres-devcontainer-volume:

View File

@@ -3,15 +3,15 @@
# shellcheck disable=SC1091 # shellcheck disable=SC1091
source /immich-devcontainer/container-common.sh source /immich-devcontainer/container-common.sh
log "Starting Nest API Server" echo "Starting Nest API Server"
log ""
cd "${IMMICH_WORKSPACE}/server" || ( cd "${IMMICH_WORKSPACE}/server" || (
log "Immich workspace not found" echo workspace not found
exit 1 exit 1
) )
while true; do while true; do
run_cmd node ./node_modules/.bin/nest start --debug "0.0.0.0:9230" --watch node ./node_modules/.bin/nest start --debug "0.0.0.0:9230" --watch
log "Nest API Server crashed with exit code $?. Respawning in 3s ..." echo " Nest API Server crashed with exit code $?. Respawning in 3s ..."
sleep 3 sleep 3
done done

View File

@@ -3,20 +3,20 @@
# shellcheck disable=SC1091 # shellcheck disable=SC1091
source /immich-devcontainer/container-common.sh source /immich-devcontainer/container-common.sh
log "Starting Immich Web Frontend" echo "Starting Immich Web Frontend"
log ""
cd "${IMMICH_WORKSPACE}/web" || ( cd "${IMMICH_WORKSPACE}/web" || (
log "Immich Workspace not found" echo Workspace not found
exit 1 exit 1
) )
until curl --output /dev/null --silent --head --fail "http://127.0.0.1:${IMMICH_PORT}/api/server/config"; do until curl --output /dev/null --silent --head --fail "http://127.0.0.1:${IMMICH_PORT}/api/server/config"; do
log "Waiting for api server..." echo 'waiting for api server...'
sleep 1 sleep 1
done done
while true; do while true; do
run_cmd node ./node_modules/.bin/vite dev --host 0.0.0.0 --port "${DEV_PORT}" node ./node_modules/.bin/vite dev --host 0.0.0.0 --port "${DEV_PORT}"
log "Web crashed with exit code $?. Respawning in 3s ..." echo "Web crashed with exit code $?. Respawning in 3s ..."
sleep 3 sleep 3
done done

View File

@@ -3,18 +3,5 @@
# shellcheck disable=SC1091 # shellcheck disable=SC1091
source /immich-devcontainer/container-common.sh source /immich-devcontainer/container-common.sh
log "Setting up Immich dev container..."
fix_permissions fix_permissions
log "Installing npm dependencies (node_modules)..."
install_dependencies install_dependencies
log "Setup complete, please wait while backend and frontend services automatically start"
log
log "If necessary, the services may be manually started using"
log
log "$ /immich-devcontainer/container-start-backend.sh"
log "$ /immich-devcontainer/container-start-frontend.sh"
log
log "From different terminal windows, as these scripts automatically restart the server"
log "on error, and will continuously run in a loop"

View File

@@ -4,14 +4,9 @@
design/ design/
docker/ docker/
Dockerfile
!docker/scripts !docker/scripts
docs/ docs/
!docs/package.json
!docs/package-lock.json
e2e/ e2e/
!e2e/package.json
!e2e/package-lock.json
fastlane/ fastlane/
machine-learning/ machine-learning/
misc/ misc/
@@ -20,7 +15,6 @@ mobile/
cli/coverage/ cli/coverage/
cli/dist/ cli/dist/
cli/node_modules/ cli/node_modules/
cli/Dockerfile
open-api/typescript-sdk/build/ open-api/typescript-sdk/build/
open-api/typescript-sdk/node_modules/ open-api/typescript-sdk/node_modules/
@@ -31,11 +25,9 @@ server/upload/
server/src/queries server/src/queries
server/dist/ server/dist/
server/www/ server/www/
server/Dockerfile
web/node_modules/ web/node_modules/
web/coverage/ web/coverage/
web/.svelte-kit web/.svelte-kit
web/build/ web/build/
web/.env web/.env
web/Dockerfile

2
.github/.nvmrc vendored
View File

@@ -1 +1 @@
22.17.0 22.16.0

View File

@@ -1,4 +0,0 @@
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

6
.github/package-lock.json generated vendored
View File

@@ -9,9 +9,9 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "3.6.2", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {

View File

@@ -58,7 +58,7 @@ jobs:
contents: read contents: read
# Skip when PR from a fork # Skip when PR from a fork
if: ${{ !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' && needs.pre-job.outputs.should_run == 'true' }} if: ${{ !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' && needs.pre-job.outputs.should_run == 'true' }}
runs-on: mich runs-on: macos-14
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@@ -66,40 +66,24 @@ jobs:
ref: ${{ inputs.ref || github.sha }} ref: ${{ inputs.ref || github.sha }}
persist-credentials: false persist-credentials: false
- name: Create the Keystore
env:
KEY_JKS: ${{ secrets.KEY_JKS }}
working-directory: ./mobile
run: printf "%s" $KEY_JKS | base64 -d > android/key.jks
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with: with:
distribution: 'zulu' distribution: 'zulu'
java-version: '17' java-version: '17'
cache: 'gradle'
- name: Restore Gradle Cache
id: cache-gradle-restore
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
~/.android/sdk
mobile/android/.gradle
mobile/.dart_tool
key: build-mobile-gradle-${{ runner.os }}-main
- name: Setup Flutter SDK - name: Setup Flutter SDK
uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2.21.0 uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046 # v2.19.0
with: with:
channel: 'stable' channel: 'stable'
flutter-version-file: ./mobile/pubspec.yaml flutter-version-file: ./mobile/pubspec.yaml
cache: true cache: true
- name: Setup Android SDK - name: Create the Keystore
uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3.2.2 env:
with: KEY_JKS: ${{ secrets.KEY_JKS }}
packages: '' working-directory: ./mobile
run: echo $KEY_JKS | base64 -d > android/key.jks
- name: Get Packages - name: Get Packages
working-directory: ./mobile working-directory: ./mobile
@@ -119,30 +103,12 @@ jobs:
ALIAS: ${{ secrets.ALIAS }} ALIAS: ${{ secrets.ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }} ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
IS_MAIN: ${{ github.ref == 'refs/heads/main' }}
run: | run: |
if [[ $IS_MAIN == 'true' ]]; then flutter build apk --release
flutter build apk --release --flavor production flutter build apk --release --split-per-abi --target-platform android-arm,android-arm64,android-x64
flutter build apk --release --flavor production --split-per-abi --target-platform android-arm,android-arm64,android-x64
else
flutter build apk --debug --flavor production --split-per-abi --target-platform android-arm64
fi
- name: Publish Android Artifact - name: Publish Android Artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: release-apk-signed name: release-apk-signed
path: mobile/build/app/outputs/flutter-apk/**/*.apk path: mobile/build/app/outputs/flutter-apk/*.apk
- name: Save Gradle Cache
id: cache-gradle-save
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4
if: github.ref == 'refs/heads/main'
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
~/.android/sdk
mobile/android/.gradle
mobile/.dart_tool
key: ${{ steps.cache-gradle-restore.outputs.cache-primary-key }}

View File

@@ -38,9 +38,6 @@ jobs:
with: with:
node-version-file: './cli/.nvmrc' node-version-file: './cli/.nvmrc'
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Prepare SDK - name: Prepare SDK
run: npm ci --prefix ../open-api/typescript-sdk/ run: npm ci --prefix ../open-api/typescript-sdk/
- name: Build SDK - name: Build SDK
@@ -70,7 +67,7 @@ jobs:
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
@@ -99,7 +96,7 @@ jobs:
type=raw,value=latest,enable=${{ github.event_name == 'release' }} type=raw,value=latest,enable=${{ github.event_name == 'release' }}
- name: Build and push image - name: Build and push image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
with: with:
file: cli/Dockerfile file: cli/Dockerfile
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64

View File

@@ -50,7 +50,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -63,7 +63,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -76,6 +76,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh # ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
with: with:
category: '/language:${{matrix.language}}' category: '/language:${{matrix.language}}'

View File

@@ -131,7 +131,7 @@ jobs:
tag-suffix: '-rocm' tag-suffix: '-rocm'
platforms: linux/amd64 platforms: linux/amd64
runner-mapping: '{"linux/amd64": "mich"}' runner-mapping: '{"linux/amd64": "mich"}'
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@129aeda75a450666ce96e8bc8126652e717917a7 # multi-runner-build-workflow-0.1.1 uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@094bfb927b8cd75b343abaac27b3241be0fccfe9 # multi-runner-build-workflow-0.1.0
permissions: permissions:
contents: read contents: read
actions: read actions: read
@@ -154,7 +154,7 @@ jobs:
name: Build and Push Server name: Build and Push Server
needs: pre-job needs: pre-job
if: ${{ needs.pre-job.outputs.should_run_server == 'true' }} if: ${{ needs.pre-job.outputs.should_run_server == 'true' }}
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@129aeda75a450666ce96e8bc8126652e717917a7 # multi-runner-build-workflow-0.1.1 uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@094bfb927b8cd75b343abaac27b3241be0fccfe9 # multi-runner-build-workflow-0.1.0
permissions: permissions:
contents: read contents: read
actions: read actions: read
@@ -177,7 +177,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: always() if: always()
steps: steps:
- uses: immich-app/devtools/actions/success-check@68f10eb389bb02a3cf9d1156111964c549eb421b # 0.0.4 - uses: immich-app/devtools/actions/success-check@6b81b1572e466f7f48ba3c823159ce3f4a4d66a6 # success-check-action-0.0.3
with: with:
needs: ${{ toJSON(needs) }} needs: ${{ toJSON(needs) }}
@@ -188,6 +188,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: always() if: always()
steps: steps:
- uses: immich-app/devtools/actions/success-check@68f10eb389bb02a3cf9d1156111964c549eb421b # 0.0.4 - uses: immich-app/devtools/actions/success-check@6b81b1572e466f7f48ba3c823159ce3f4a4d66a6 # success-check-action-0.0.3
with: with:
needs: ${{ toJSON(needs) }} needs: ${{ toJSON(needs) }}

View File

@@ -57,8 +57,6 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version-file: './docs/.nvmrc' node-version-file: './docs/.nvmrc'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Run npm install - name: Run npm install
run: npm ci run: npm ci

View File

@@ -32,8 +32,6 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Fix formatting - name: Fix formatting
run: make install-all && make format-all run: make install-all && make format-all

View File

@@ -1,13 +0,0 @@
name: Org Checks
on:
pull_request_review:
pull_request:
jobs:
check-approvals:
name: Check for Team/Admin Review
uses: immich-app/devtools/.github/workflows/required-approval.yml@main
permissions:
pull-requests: read
contents: read

View File

@@ -14,7 +14,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Require PR to have a changelog label - name: Require PR to have a changelog label
uses: mheap/github-action-required-labels@8afbe8ae6ab7647d0c9f0cfa7c2f939650d22509 # v5.5.1 uses: mheap/github-action-required-labels@fb29a14a076b0f74099f6198f77750e8fc236016 # v5.5.0
with: with:
mode: exactly mode: exactly
count: 1 count: 1

View File

@@ -100,7 +100,7 @@ jobs:
name: release-apk-signed name: release-apk-signed
- name: Create draft release - name: Create draft release
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2 uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
with: with:
draft: true draft: true
tag_name: ${{ env.IMMICH_VERSION }} tag_name: ${{ env.IMMICH_VERSION }}

View File

@@ -25,8 +25,6 @@ jobs:
with: with:
node-version-file: './open-api/typescript-sdk/.nvmrc' node-version-file: './open-api/typescript-sdk/.nvmrc'
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Install deps - name: Install deps
run: npm ci run: npm ci
- name: Build - name: Build

View File

@@ -42,9 +42,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
defaults:
run:
working-directory: ./mobile
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@@ -52,29 +49,34 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Setup Flutter SDK - name: Setup Flutter SDK
uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2.21.0 uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046 # v2.19.0
with: with:
channel: 'stable' channel: 'stable'
flutter-version-file: ./mobile/pubspec.yaml flutter-version-file: ./mobile/pubspec.yaml
- name: Install dependencies - name: Install dependencies
run: dart pub get run: dart pub get
working-directory: ./mobile
- name: Install DCM - name: Install DCM
uses: CQLabs/setup-dcm@8697ae0790c0852e964a6ef1d768d62a6675481a # v2.0.1 run: |
with: sudo apt-get update
github-token: ${{ secrets.GITHUB_TOKEN }} wget -qO- https://dcm.dev/pgp-key.public | sudo gpg --dearmor -o /usr/share/keyrings/dcm.gpg
version: auto echo 'deb [signed-by=/usr/share/keyrings/dcm.gpg arch=amd64] https://dcm.dev/debian stable main' | sudo tee /etc/apt/sources.list.d/dart_stable.list
working-directory: ./mobile sudo apt-get update
sudo apt-get install dcm
- name: Generate translation file - name: Generate translation file
run: make translation run: make translation
working-directory: ./mobile
- name: Run Build Runner - name: Run Build Runner
run: make build run: make build
working-directory: ./mobile
- name: Generate platform API - name: Generate platform API
run: make pigeon run: make pigeon
working-directory: ./mobile
- name: Find file changes - name: Find file changes
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4 uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
@@ -96,16 +98,19 @@ jobs:
- name: Run dart analyze - name: Run dart analyze
run: dart analyze --fatal-infos run: dart analyze --fatal-infos
working-directory: ./mobile
- name: Run dart format - name: Run dart format
run: dart format lib/ --set-exit-if-changed run: dart format lib/ --set-exit-if-changed
working-directory: ./mobile
- name: Run dart custom_lint - name: Run dart custom_lint
run: dart run custom_lint run: dart run custom_lint
working-directory: ./mobile
# TODO: Use https://github.com/CQLabs/dcm-action
- name: Run DCM - name: Run DCM
run: dcm analyze lib --fatal-style --fatal-warnings run: dcm analyze lib
working-directory: ./mobile
zizmor: zizmor:
name: zizmor name: zizmor
@@ -129,7 +134,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload SARIF file - name: Upload SARIF file
uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
with: with:
sarif_file: results.sarif sarif_file: results.sarif
category: zizmor category: zizmor

View File

@@ -84,8 +84,6 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Run npm install - name: Run npm install
run: npm ci run: npm ci
@@ -103,7 +101,7 @@ jobs:
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Run small tests & coverage - name: Run small tests & coverage
run: npm test run: npm run test:cov
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
cli-unit-tests: cli-unit-tests:
@@ -127,8 +125,6 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version-file: './cli/.nvmrc' node-version-file: './cli/.nvmrc'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Setup typescript-sdk - name: Setup typescript-sdk
run: npm ci && npm run build run: npm ci && npm run build
@@ -150,7 +146,7 @@ jobs:
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Run unit tests & coverage - name: Run unit tests & coverage
run: npm run test run: npm run test:cov
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
cli-unit-tests-win: cli-unit-tests-win:
@@ -174,8 +170,6 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version-file: './cli/.nvmrc' node-version-file: './cli/.nvmrc'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Setup typescript-sdk - name: Setup typescript-sdk
run: npm ci && npm run build run: npm ci && npm run build
@@ -190,7 +184,7 @@ jobs:
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Run unit tests & coverage - name: Run unit tests & coverage
run: npm run test run: npm run test:cov
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
web-lint: web-lint:
@@ -214,8 +208,6 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version-file: './web/.nvmrc' node-version-file: './web/.nvmrc'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Run setup typescript-sdk - name: Run setup typescript-sdk
run: npm ci && npm run build run: npm ci && npm run build
@@ -257,8 +249,6 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version-file: './web/.nvmrc' node-version-file: './web/.nvmrc'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Run setup typescript-sdk - name: Run setup typescript-sdk
run: npm ci && npm run build run: npm ci && npm run build
@@ -272,7 +262,7 @@ jobs:
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Run unit tests & coverage - name: Run unit tests & coverage
run: npm run test run: npm run test:cov
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
i18n-tests: i18n-tests:
@@ -292,8 +282,6 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version-file: './web/.nvmrc' node-version-file: './web/.nvmrc'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Install dependencies - name: Install dependencies
run: npm --prefix=web ci run: npm --prefix=web ci
@@ -338,8 +326,6 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version-file: './e2e/.nvmrc' node-version-file: './e2e/.nvmrc'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Run setup typescript-sdk - name: Run setup typescript-sdk
run: npm ci && npm run build run: npm ci && npm run build
@@ -383,8 +369,6 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Run npm install - name: Run npm install
run: npm ci run: npm ci
@@ -418,8 +402,6 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version-file: './e2e/.nvmrc' node-version-file: './e2e/.nvmrc'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Run setup typescript-sdk - name: Run setup typescript-sdk
run: npm ci && npm run build run: npm ci && npm run build
@@ -468,8 +450,6 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version-file: './e2e/.nvmrc' node-version-file: './e2e/.nvmrc'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Run setup typescript-sdk - name: Run setup typescript-sdk
run: npm ci && npm run build run: npm ci && npm run build
@@ -481,7 +461,7 @@ jobs:
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Install Playwright Browsers - name: Install Playwright Browsers
run: npx playwright install chromium --only-shell run: npx playwright install --with-deps chromium
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Docker build - name: Docker build
@@ -499,7 +479,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: always() if: always()
steps: steps:
- uses: immich-app/devtools/actions/success-check@68f10eb389bb02a3cf9d1156111964c549eb421b # 0.0.4 - uses: immich-app/devtools/actions/success-check@6b81b1572e466f7f48ba3c823159ce3f4a4d66a6 # success-check-action-0.0.3
with: with:
needs: ${{ toJSON(needs) }} needs: ${{ toJSON(needs) }}
@@ -516,7 +496,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Setup Flutter SDK - name: Setup Flutter SDK
uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2.21.0 uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046 # v2.19.0
with: with:
channel: 'stable' channel: 'stable'
flutter-version-file: ./mobile/pubspec.yaml flutter-version-file: ./mobile/pubspec.yaml
@@ -588,8 +568,6 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version-file: './.github/.nvmrc' node-version-file: './.github/.nvmrc'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Run npm install - name: Run npm install
run: npm ci run: npm ci
@@ -609,7 +587,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Run ShellCheck - name: Run ShellCheck
uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0 uses: ludeeus/action-shellcheck@master
with: with:
ignore_paths: >- ignore_paths: >-
**/open-api/** **/open-api/**
@@ -631,8 +609,6 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Install server dependencies - name: Install server dependencies
run: npm --prefix=server ci run: npm --prefix=server ci
@@ -668,7 +644,7 @@ jobs:
contents: read contents: read
services: services:
postgres: postgres:
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3@sha256:1f5583fe3397210a0fbc7f11b0cec18bacc4a99e3e8ea0548e9bd6bcf26ec37a image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1
env: env:
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres POSTGRES_USER: postgres
@@ -694,8 +670,6 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Install server dependencies - name: Install server dependencies
run: npm ci run: npm ci
@@ -748,7 +722,6 @@ jobs:
run: | run: |
echo "ERROR: Generated SQL files not up to date!" echo "ERROR: Generated SQL files not up to date!"
echo "Changed files: ${CHANGED_FILES}" echo "Changed files: ${CHANGED_FILES}"
git diff
exit 1 exit 1
# mobile-integration-tests: # mobile-integration-tests:

View File

@@ -52,6 +52,6 @@ jobs:
permissions: {} permissions: {}
if: always() if: always()
steps: steps:
- uses: immich-app/devtools/actions/success-check@68f10eb389bb02a3cf9d1156111964c549eb421b # 0.0.4 - uses: immich-app/devtools/actions/success-check@6b81b1572e466f7f48ba3c823159ce3f4a4d66a6 # success-check-action-0.0.3
with: with:
needs: ${{ toJSON(needs) }} needs: ${{ toJSON(needs) }}

19
.vscode/launch.json vendored
View File

@@ -18,25 +18,6 @@
"name": "Immich Workers", "name": "Immich Workers",
"remoteRoot": "/usr/src/app", "remoteRoot": "/usr/src/app",
"localRoot": "${workspaceFolder}/server" "localRoot": "${workspaceFolder}/server"
},
{
"name": "Flavor - Production",
"request": "launch",
"type": "dart",
"codeLens": {
"for": [
"run-test",
"run-test-file",
"run-file",
"debug-test",
"debug-test-file",
"debug-file",
],
"title": "${debugType}",
},
"args": [
"--flavor", "production"
],
} }
] ]
} }

View File

@@ -1,33 +1,27 @@
dev: dev:
@trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans || make dev-down
dev-down: dev-down:
docker compose -f ./docker/docker-compose.dev.yml down --remove-orphans docker compose -f ./docker/docker-compose.dev.yml down --remove-orphans
dev-update: dev-update:
@trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans docker compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
dev-scale: 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 docker compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans
.PHONY: e2e .PHONY: e2e
e2e: e2e:
@trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.yml up --build -V --remove-orphans docker compose -f ./e2e/docker-compose.yml up --build -V --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: prod:
@trap 'make prod-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans docker compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans
prod-down: prod-down:
docker compose -f ./docker/docker-compose.prod.yml down --remove-orphans docker compose -f ./docker/docker-compose.prod.yml down --remove-orphans
prod-scale: 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 docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
.PHONY: open-api .PHONY: open-api
open-api: open-api:
@@ -54,8 +48,6 @@ audit-%:
npm --prefix $(subst sdk,open-api/typescript-sdk,$*) audit fix npm --prefix $(subst sdk,open-api/typescript-sdk,$*) audit fix
install-%: install-%:
npm --prefix $(subst sdk,open-api/typescript-sdk,$*) i npm --prefix $(subst sdk,open-api/typescript-sdk,$*) i
ci-%:
npm --prefix $(subst sdk,open-api/typescript-sdk,$*) ci
build-cli: build-sdk build-cli: build-sdk
build-web: build-sdk build-web: build-sdk
build-%: install-% build-%: install-%
@@ -90,7 +82,6 @@ test-medium-dev:
build-all: $(foreach M,$(filter-out e2e .github,$(MODULES)),build-$M) ; build-all: $(foreach M,$(filter-out e2e .github,$(MODULES)),build-$M) ;
install-all: $(foreach M,$(MODULES),install-$M) ; install-all: $(foreach M,$(MODULES),install-$M) ;
ci-all: $(foreach M,$(filter-out .github,$(MODULES)),ci-$M) ;
check-all: $(foreach M,$(filter-out sdk cli docs .github,$(MODULES)),check-$M) ; check-all: $(foreach M,$(filter-out sdk cli docs .github,$(MODULES)),check-$M) ;
lint-all: $(foreach M,$(filter-out sdk docs .github,$(MODULES)),lint-$M) ; lint-all: $(foreach M,$(filter-out sdk docs .github,$(MODULES)),lint-$M) ;
format-all: $(foreach M,$(filter-out sdk,$(MODULES)),format-$M) ; format-all: $(foreach M,$(filter-out sdk,$(MODULES)),format-$M) ;
@@ -99,12 +90,9 @@ hygiene-all: lint-all format-all check-all sql audit-all;
test-all: $(foreach M,$(filter-out sdk docs .github,$(MODULES)),test-$M) ; test-all: $(foreach M,$(filter-out sdk docs .github,$(MODULES)),test-$M) ;
clean: clean:
find . -name "node_modules" -type d -prune -exec rm -rf {} + find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
find . -name "dist" -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 "build" -type d -prune -exec rm -rf '{}' +
find . -name "svelte-kit" -type d -prune -exec rm -rf '{}' + find . -name "svelte-kit" -type d -prune -exec rm -rf '{}' +
command -v docker >/dev/null 2>&1 && docker compose -f ./docker/docker-compose.dev.yml rm -v -f || true docker compose -f ./docker/docker-compose.dev.yml rm -v -f || true
command -v docker >/dev/null 2>&1 && docker compose -f ./e2e/docker-compose.yml rm -v -f || true docker compose -f ./e2e/docker-compose.yml rm -v -f || true
setup-server-dev: install-server
setup-web-dev: install-sdk build-sdk install-web

View File

@@ -1 +1 @@
22.17.0 22.16.0

View File

@@ -1,2 +0,0 @@
#!/usr/bin/env node
import '../dist/index.js';

530
cli/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.72", "version": "2.2.68",
"description": "Command Line Interface (CLI) for Immich", "description": "Command Line Interface (CLI) for Immich",
"type": "module", "type": "module",
"exports": "./dist/index.js", "exports": "./dist/index.js",
"bin": { "bin": {
"immich": "./bin/immich" "immich": "dist/index.js"
}, },
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"keywords": [ "keywords": [
@@ -21,7 +21,7 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/micromatch": "^4.0.9", "@types/micromatch": "^4.0.9",
"@types/mock-fs": "^4.13.1", "@types/mock-fs": "^4.13.1",
"@types/node": "^22.15.33", "@types/node": "^22.15.29",
"@vitest/coverage-v8": "^3.0.0", "@vitest/coverage-v8": "^3.0.0",
"byte-size": "^9.0.0", "byte-size": "^9.0.0",
"cli-progress": "^3.12.0", "cli-progress": "^3.12.0",
@@ -36,7 +36,7 @@
"prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-organize-imports": "^4.0.0",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"typescript-eslint": "^8.28.0", "typescript-eslint": "^8.28.0",
"vite": "^7.0.0", "vite": "^6.0.0",
"vite-tsconfig-paths": "^5.0.0", "vite-tsconfig-paths": "^5.0.0",
"vitest": "^3.0.0", "vitest": "^3.0.0",
"vitest-fetch-mock": "^0.4.0", "vitest-fetch-mock": "^0.4.0",
@@ -69,6 +69,6 @@
"micromatch": "^4.0.8" "micromatch": "^4.0.8"
}, },
"volta": { "volta": {
"node": "22.17.0" "node": "22.16.0"
} }
} }

View File

@@ -16,7 +16,7 @@ name: immich-dev
services: services:
immich-server: immich-server:
container_name: immich_server container_name: immich_server
command: ['/usr/src/app/bin/immich-dev'] command: [ '/usr/src/app/bin/immich-dev' ]
image: immich-server-dev:latest image: immich-server-dev:latest
# extends: # extends:
# file: hwaccel.transcoding.yml # file: hwaccel.transcoding.yml
@@ -70,7 +70,7 @@ services:
# user: 0:0 # user: 0:0
build: build:
context: ../web context: ../web
command: ['/usr/src/app/bin/immich-web'] command: [ '/usr/src/app/bin/immich-web' ]
env_file: env_file:
- .env - .env
ports: ports:
@@ -116,13 +116,13 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:8-bookworm@sha256:facc1d2c3462975c34e10fccb167bfa92b0e0dbd992fc282c29a61c3243afb11 image: docker.io/valkey/valkey:8-bookworm@sha256:a19bebed6a91bd5e6e2106fef015f9602a3392deeb7c9ed47548378dcee3dfc2
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
database: database:
container_name: immich_postgres container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:5f6a838e4e44c8e0e019d0ebfe3ee8952b69afc2809b2c25f7b0119641978e91 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-pgvectors0.2.0
env_file: env_file:
- .env - .env
environment: environment:
@@ -134,7 +134,6 @@ services:
- ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data - ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data
ports: ports:
- 5432:5432 - 5432:5432
shm_size: 128mb
# set IMMICH_TELEMETRY_INCLUDE=all in .env to enable metrics # set IMMICH_TELEMETRY_INCLUDE=all in .env to enable metrics
# immich-prometheus: # immich-prometheus:
# container_name: immich_prometheus # container_name: immich_prometheus

View File

@@ -56,14 +56,14 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:8-bookworm@sha256:facc1d2c3462975c34e10fccb167bfa92b0e0dbd992fc282c29a61c3243afb11 image: docker.io/valkey/valkey:8-bookworm@sha256:a19bebed6a91bd5e6e2106fef015f9602a3392deeb7c9ed47548378dcee3dfc2
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
restart: always restart: always
database: database:
container_name: immich_postgres container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:5f6a838e4e44c8e0e019d0ebfe3ee8952b69afc2809b2c25f7b0119641978e91 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-pgvectors0.2.0
env_file: env_file:
- .env - .env
environment: environment:
@@ -75,7 +75,6 @@ services:
- ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data - ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data
ports: ports:
- 5432:5432 - 5432:5432
shm_size: 128mb
restart: always restart: always
# set IMMICH_TELEMETRY_INCLUDE=all in .env to enable metrics # set IMMICH_TELEMETRY_INCLUDE=all in .env to enable metrics
@@ -83,7 +82,7 @@ services:
container_name: immich_prometheus container_name: immich_prometheus
ports: ports:
- 9090:9090 - 9090:9090
image: prom/prometheus@sha256:63805ebb8d2b3920190daf1cb14a60871b16fd38bed42b857a3182bc621f4996 image: prom/prometheus@sha256:9abc6cf6aea7710d163dbb28d8eeb7dc5baef01e38fa4cd146a406dd9f07f70d
volumes: volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml - ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus - prometheus-data:/prometheus
@@ -92,10 +91,10 @@ services:
# add data source for http://immich-prometheus:9090 to get started # add data source for http://immich-prometheus:9090 to get started
immich-grafana: immich-grafana:
container_name: immich_grafana container_name: immich_grafana
command: ['./run.sh', '-disable-reporting'] command: [ './run.sh', '-disable-reporting' ]
ports: ports:
- 3000:3000 - 3000:3000
image: grafana/grafana:12.0.2-ubuntu@sha256:0512d81cdeaaff0e370a9aa66027b465d1f1f04379c3a9c801a905fabbdbc7a5 image: grafana/grafana:12.0.1-ubuntu@sha256:65575bb9c761335e2ff30e364f21d38632e3b2e75f5f81d83cc92f44b9bbc055
volumes: volumes:
- grafana-data:/var/lib/grafana - grafana-data:/var/lib/grafana

View File

@@ -49,14 +49,14 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:8-bookworm@sha256:facc1d2c3462975c34e10fccb167bfa92b0e0dbd992fc282c29a61c3243afb11 image: docker.io/valkey/valkey:8-bookworm@sha256:a19bebed6a91bd5e6e2106fef015f9602a3392deeb7c9ed47548378dcee3dfc2
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
restart: always restart: always
database: database:
container_name: immich_postgres container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:5f6a838e4e44c8e0e019d0ebfe3ee8952b69afc2809b2c25f7b0119641978e91 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-pgvectors0.2.0
environment: environment:
POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME} POSTGRES_USER: ${DB_USERNAME}
@@ -67,7 +67,6 @@ services:
volumes: 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 # 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 - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
shm_size: 128mb
restart: always restart: always
volumes: volumes:

View File

@@ -1 +1 @@
22.17.0 22.16.0

View File

@@ -490,7 +490,7 @@ You can also scan the Postgres database file structure for errors:
<details> <details>
<summary>Scan for file structure errors</summary> <summary>Scan for file structure errors</summary>
```bash ```bash
docker exec -it immich_postgres pg_amcheck --username=<DB_USERNAME> --heapallindexed --parent-check --rootdescend --progress --all --install-missing docker exec -it immich_postgres pg_amcheck --username=postgres --heapallindexed --parent-check --rootdescend --progress --all --install-missing
``` ```
A normal result will end something like this and return with an exit code of `0`: A normal result will end something like this and return with an exit code of `0`:

View File

@@ -57,7 +57,7 @@ Then please follow the steps in the following section for restoring the database
<TabItem value="Linux system" label="Linux system" default> <TabItem value="Linux system" label="Linux system" default>
```bash title='Backup' ```bash title='Backup'
docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=<DB_USERNAME> | gzip > "/path/to/backup/dump.sql.gz" docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=postgres | gzip > "/path/to/backup/dump.sql.gz"
``` ```
```bash title='Restore' ```bash title='Restore'
@@ -79,7 +79,7 @@ docker compose up -d # Start remainder of Immich apps
<TabItem value="Windows system (PowerShell)" label="Windows system (PowerShell)"> <TabItem value="Windows system (PowerShell)" label="Windows system (PowerShell)">
```powershell title='Backup' ```powershell title='Backup'
[System.IO.File]::WriteAllLines("C:\absolute\path\to\backup\dump.sql", (docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=<DB_USERNAME>)) [System.IO.File]::WriteAllLines("C:\absolute\path\to\backup\dump.sql", (docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=postgres))
``` ```
```powershell title='Restore' ```powershell title='Restore'
@@ -150,10 +150,12 @@ for more info read the [release notes](https://github.com/immich-app/immich/rele
- Preview images (small thumbnails and large previews) for each asset and thumbnails for recognized faces. - Preview images (small thumbnails and large previews) for each asset and thumbnails for recognized faces.
- Stored in `UPLOAD_LOCATION/thumbs/<userID>`. - Stored in `UPLOAD_LOCATION/thumbs/<userID>`.
- **Encoded Assets:** - **Encoded Assets:**
- Videos that have been re-encoded from the original for wider compatibility. The original is not removed. - Videos that have been re-encoded from the original for wider compatibility. The original is not removed.
- Stored in `UPLOAD_LOCATION/encoded-video/<userID>`. - Stored in `UPLOAD_LOCATION/encoded-video/<userID>`.
- **Postgres** - **Postgres**
- The Immich database containing all the information to allow the system to function properly. - 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. **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`. - Stored in `DB_DATA_LOCATION`.
@@ -199,6 +201,7 @@ When you turn off the storage template engine, it will leave the assets in `UPLO
- Temporarily located in `UPLOAD_LOCATION/upload/<userID>`. - Temporarily located in `UPLOAD_LOCATION/upload/<userID>`.
- Transferred to `UPLOAD_LOCATION/library/<userID>` upon successful upload. - Transferred to `UPLOAD_LOCATION/library/<userID>` upon successful upload.
- **Postgres** - **Postgres**
- The Immich database containing all the information to allow the system to function properly. - 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. **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`. - Stored in `DB_DATA_LOCATION`.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -46,12 +46,6 @@ services:
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. 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 run on a schedule, which is every night at midnight. This schedule, with the exception of [External Libraries](/docs/features/libraries) scanning, cannot be changed.
<img src={require('./img/admin-jobs.webp').default} width="60%" title="Admin jobs" /> <img src={require('./img/admin-jobs.webp').default} width="60%" title="Admin jobs" />
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).
<img src={require('./img/admin-nightly-tasks.webp').default} width="60%" title="Admin nightly tasks" />
:::note
Some jobs ([External Libraries](/docs/features/libraries) scanning, Database Dump) are configured in their own sections in System Settings.
:::

View File

@@ -20,6 +20,7 @@ Immich supports 3rd party authentication via [OpenID Connect][oidc] (OIDC), an i
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. 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. Create a new (Client) Application
1. The **Provider** type should be `OpenID Connect` or `OAuth2` 1. The **Provider** type should be `OpenID Connect` or `OAuth2`
2. The **Client type** should be `Confidential` 2. The **Client type** should be `Confidential`
3. The **Application** type should be `Web` 3. The **Application** type should be `Web`
@@ -28,6 +29,7 @@ Before enabling OAuth in Immich, a new client application needs to be configured
2. Configure Redirect URIs/Origins 2. Configure Redirect URIs/Origins
The **Sign-in redirect URIs** should include: The **Sign-in redirect URIs** should include:
- `app.immich:///oauth-callback` - for logging in with OAuth from the [Mobile App](/docs/features/mobile-app.mdx) - `app.immich:///oauth-callback` - for logging in with OAuth from the [Mobile App](/docs/features/mobile-app.mdx)
- `http://DOMAIN:PORT/auth/login` - for logging in with OAuth from the Web Client - `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 - `http://DOMAIN:PORT/user-settings` - for manually linking OAuth in the Web Client
@@ -35,17 +37,21 @@ Before enabling OAuth in Immich, a new client application needs to be configured
Redirect URIs should contain all the domains you will be using to access Immich. Some examples include: Redirect URIs should contain all the domains you will be using to access Immich. Some examples include:
Mobile Mobile
- `app.immich:///oauth-callback` (You **MUST** include this for iOS and Android mobile apps to work properly) - `app.immich:///oauth-callback` (You **MUST** include this for iOS and Android mobile apps to work properly)
Localhost Localhost
- `http://localhost:2283/auth/login` - `http://localhost:2283/auth/login`
- `http://localhost:2283/user-settings` - `http://localhost:2283/user-settings`
Local IP Local IP
- `http://192.168.0.200:2283/auth/login` - `http://192.168.0.200:2283/auth/login`
- `http://192.168.0.200:2283/user-settings` - `http://192.168.0.200:2283/user-settings`
Hostname Hostname
- `https://immich.example.com/auth/login` - `https://immich.example.com/auth/login`
- `https://immich.example.com/user-settings` - `https://immich.example.com/user-settings`
@@ -62,7 +68,6 @@ Once you have a new OAuth client application configured, Immich can be configure
| Scope | string | openid email profile | Full list of scopes to send with the request (space delimited) | | Scope | string | openid email profile | Full list of scopes to send with the request (space delimited) |
| Signing Algorithm | string | RS256 | The algorithm used to sign the id token (examples: RS256, HS256) | | Signing Algorithm | string | RS256 | The algorithm used to sign the id token (examples: RS256, HS256) |
| Storage Label Claim | string | preferred_username | Claim mapping for the user's storage label**¹** | | 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**¹** | | 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 (Enter 0 for unlimited quota) | | Default Storage Quota (GiB) | number | 0 | Default quota for user without storage quota claim (Enter 0 for unlimited quota) |
| Button Text | string | Login with OAuth | Text for the OAuth button on the web | | Button Text | string | Login with OAuth | Text for the OAuth button on the web |

View File

@@ -64,13 +64,7 @@ COMMIT;
### Updating VectorChord ### 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: When installing a new version of VectorChord, you will need to manually update the extension by connecting to the Immich database and running `ALTER EXTENSION vchord UPDATE;`.
```
ALTER EXTENSION vchord UPDATE;
REINDEX INDEX face_index;
REINDEX INDEX clip_index;
```
## Migrating to VectorChord ## Migrating to VectorChord
@@ -82,8 +76,6 @@ Support for pgvecto.rs will be dropped in a later release, hence we recommend al
The easiest option is to have both extensions installed during the migration: The easiest option is to have both extensions installed during the migration:
<details>
<summary>Migration steps (automatic)</summary>
1. Ensure you still have pgvecto.rs installed 1. Ensure you still have pgvecto.rs installed
2. Install `pgvector` (`>= 0.7.0, < 1.0.0`). 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`) 2. Install `pgvector` (`>= 0.7.0, < 1.0.0`). 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] 3. [Install VectorChord][vchord-install]
@@ -97,12 +89,8 @@ The easiest option is to have both extensions installed during the migration:
11. Restart the Postgres database 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` 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`
</details>
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: 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:
<details>
<summary>Migration steps (manual)</summary>
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 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 ```sql
@@ -135,20 +123,14 @@ ALTER TABLE face_search ALTER COLUMN embedding SET DATA TYPE vector(512);
5. Start Immich and let it create new indices using VectorChord 5. Start Immich and let it create new indices using VectorChord
</details>
### Migrating from pgvector ### Migrating from pgvector
<details>
<summary>Migration steps</summary>
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 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 2. Follow the Prerequisites to install VectorChord
3. If Immich does not have superuser permissions, run the SQL command `CREATE EXTENSION vchord CASCADE;` 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 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 5. Start Immich and let it create new indices using VectorChord
</details>
Note that VectorChord itself uses pgvector types, so you should not uninstall pgvector after following these steps. 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 [vchord-install]: https://docs.vectorchord.ai/vectorchord/getting-started/installation.html

View File

@@ -199,11 +199,13 @@ To use your SSH key for commit signing, see the [GitHub guide on SSH commit sign
When the Dev Container starts, it automatically: When the Dev Container starts, it automatically:
1. **Runs post-create script** (`container-server-post-create.sh`): 1. **Runs post-create script** (`container-server-post-create.sh`):
- Adjusts file permissions for the `node` user - Adjusts file permissions for the `node` user
- Installs dependencies: `npm install` in all packages - Installs dependencies: `npm install` in all packages
- Builds TypeScript SDK: `npm run build` in `open-api/typescript-sdk` - Builds TypeScript SDK: `npm run build` in `open-api/typescript-sdk`
2. **Starts development servers** via VS Code tasks: 2. **Starts development servers** via VS Code tasks:
- `Immich API Server (Nest)` - API server with hot-reloading on port 2283 - `Immich API Server (Nest)` - API server with hot-reloading on port 2283
- `Immich Web Server (Vite)` - Web frontend with hot-reloading on port 3000 - `Immich Web Server (Vite)` - Web frontend with hot-reloading on port 3000
- Both servers watch for file changes and recompile automatically - Both servers watch for file changes and recompile automatically
@@ -333,12 +335,14 @@ make install-all # Install all dependencies
The Dev Container is pre-configured for debugging: The Dev Container is pre-configured for debugging:
1. **API Server Debugging**: 1. **API Server Debugging**:
- Set breakpoints in VS Code - Set breakpoints in VS Code
- Press `F5` or use "Run and Debug" panel - Press `F5` or use "Run and Debug" panel
- Select "Attach to Server" configuration - Select "Attach to Server" configuration
- Debug port: 9231 - Debug port: 9231
2. **Worker Debugging**: 2. **Worker Debugging**:
- Use "Attach to Workers" configuration - Use "Attach to Workers" configuration
- Debug port: 9230 - Debug port: 9230
@@ -424,6 +428,7 @@ While the Dev Container focuses on server and web development, you can connect m
``` ```
2. **Configure mobile app**: 2. **Configure mobile app**:
- Server URL: `http://YOUR_IP:2283/api` - Server URL: `http://YOUR_IP:2283/api`
- Ensure firewall allows port 2283 - Ensure firewall allows port 2283

View File

@@ -2,7 +2,7 @@
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. 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) You can enable this feature under [`Account Settings > Features > Folder View`](https://my.immich.app/user-settings?isOpen=feature+folders)
## Enable folder view ## Enable folder view

View File

@@ -56,7 +56,7 @@ Internally, Immich uses the [glob](https://www.npmjs.com/package/glob) package t
### Automatic watching (EXPERIMENTAL) ### 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. This feature - currently hidden in the config file - 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 to pull in your changes. 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 to pull in your changes.
@@ -112,7 +112,7 @@ _Remember to run `docker compose up -d` to register the changes. Make sure you c
These actions must be performed by the Immich administrator. These actions must be performed by the Immich administrator.
- Click on your avatar in the upper right corner - Click on your avatar on the upper right corner
- Click on Administration -> External Libraries - Click on Administration -> External Libraries
- Click on Create an external library… - Click on Create an external library…
- Select which user owns the library, this can not be changed later - Select which user owns the library, this can not be changed later
@@ -159,7 +159,9 @@ Within seconds, the assets from the old-pics and videos folders should show up i
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. 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) You can enable this feature under [`Account Settings > Features > Folder View`](https://my.immich.app/user-settings?isOpen=feature+folders)
The UI is currently only available for the web; mobile will come in a subsequent release.
<img src={require('./img/folder-view-1.webp').default} width="100%" title='Folder-view' /> <img src={require('./img/folder-view-1.webp').default} width="100%" title='Folder-view' />
@@ -169,7 +171,7 @@ You can enable this feature under [`Account Settings > Features > Folders`](http
Only an admin can do this. 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 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 you can refer to [Crontab Guru](https://crontab.guru/). You can set the scanning interval using the preset or cron format. For more information you can refer to [Crontab Guru](https://crontab.guru/).
<img src={require('./img/library-custom-scan-interval.webp').default} width="75%" title='Set custom scan interval for external library' /> <img src={require('./img/library-custom-scan-interval.webp').default} width="75%" title='Set custom scan interval for external library' />

View File

@@ -16,7 +16,7 @@ For the full list, refer to the [Immich source code](https://github.com/immich-a
| `HEIC` | `.heic` | :white_check_mark: | | | `HEIC` | `.heic` | :white_check_mark: | |
| `HEIF` | `.heif` | :white_check_mark: | | | `HEIF` | `.heif` | :white_check_mark: | |
| `JPEG 2000` | `.jp2` | :white_check_mark: | | | `JPEG 2000` | `.jp2` | :white_check_mark: | |
| `JPEG` | `.jpeg` `.jpg` `.jpe` `.insp` | :white_check_mark: | | | `JPEG` | `.webp` `.jpg` `.jpe` `.insp` | :white_check_mark: | |
| `JPEG XL` | `.jxl` | :white_check_mark: | | | `JPEG XL` | `.jxl` | :white_check_mark: | |
| `PNG` | `.png` | :white_check_mark: | | | `PNG` | `.png` | :white_check_mark: | |
| `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop | | `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop |

View File

@@ -41,7 +41,7 @@ In the Immich web UI:
- Click Add path - Click Add path
<img src={require('./img/add-path-button.webp').default} width="50%" title="Add Path button" /> <img src={require('./img/add-path-button.webp').default} width="50%" title="Add Path button" />
- Enter **/home/user/photos1** as the path and click Add - Enter **/usr/src/app/external** as the path and click Add
<img src={require('./img/add-path-field.webp').default} width="50%" title="Add Path field" /> <img src={require('./img/add-path-field.webp').default} width="50%" title="Add Path field" />
- Save the new path - Save the new path

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -52,9 +52,9 @@ REMOTE_BACKUP_PATH="/path/to/remote/backup/directory"
### Local ### Local
# Backup Immich database # Backup Immich database
docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=<DB_USERNAME> > "$UPLOAD_LOCATION"/database-backup/immich-database.sql docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=postgres > "$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: # 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=<DB_USERNAME> | /usr/bin/gzip --rsyncable > "$UPLOAD_LOCATION"/database-backup/immich-database.sql.gz # docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=postgres | /usr/bin/gzip --rsyncable > "$UPLOAD_LOCATION"/database-backup/immich-database.sql.gz
### Append to local Borg repository ### Append to local Borg repository
borg create "$BACKUP_PATH/immich-borg::{now}" "$UPLOAD_LOCATION" --exclude "$UPLOAD_LOCATION"/thumbs/ --exclude "$UPLOAD_LOCATION"/encoded-video/ borg create "$BACKUP_PATH/immich-borg::{now}" "$UPLOAD_LOCATION" --exclude "$UPLOAD_LOCATION"/thumbs/ --exclude "$UPLOAD_LOCATION"/encoded-video/

View File

@@ -72,25 +72,22 @@ Information on the current workers can be found [here](/docs/administration/jobs
## Database ## Database
| Variable | Description | Default | Containers | | Variable | Description | Default | Containers |
| :---------------------------------- | :------------------------------------------------------------------------------------- | :--------: | :----------------------------- | | :---------------------------------- | :--------------------------------------------------------------------------- | :--------: | :----------------------------- |
| `DB_URL` | Database URL | | server | | `DB_URL` | Database URL | | server |
| `DB_HOSTNAME` | Database host | `database` | server | | `DB_HOSTNAME` | Database host | `database` | server |
| `DB_PORT` | Database port | `5432` | server | | `DB_PORT` | Database port | `5432` | server |
| `DB_USERNAME` | Database user | `postgres` | server, database<sup>\*1</sup> | | `DB_USERNAME` | Database user | `postgres` | server, database<sup>\*1</sup> |
| `DB_PASSWORD` | Database password | `postgres` | server, database<sup>\*1</sup> | | `DB_PASSWORD` | Database password | `postgres` | server, database<sup>\*1</sup> |
| `DB_DATABASE_NAME` | Database name | `immich` | server, database<sup>\*1</sup> | | `DB_DATABASE_NAME` | Database name | `immich` | server, database<sup>\*1</sup> |
| `DB_SSL_MODE` | Database SSL mode | | server | | `DB_SSL_MODE` | Database SSL mode | | server |
| `DB_VECTOR_EXTENSION`<sup>\*2</sup> | Database vector extension (one of [`vectorchord`, `pgvector`, `pgvecto.rs`]) | | server | | `DB_VECTOR_EXTENSION`<sup>\*2</sup> | 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_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`])<sup>\*3</sup> | `SSD` | server |
\*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`. \*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. \*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 :::info
All `DB_` variables must be provided to all Immich workers, including `api` and `microservices`. All `DB_` variables must be provided to all Immich workers, including `api` and `microservices`.

View File

@@ -39,8 +39,8 @@ alt="Dot Env Example"
/> />
- Change the default `DB_PASSWORD`, and add custom database connection information if necessary. - 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 `DB_DATA_LOCATION` to a folder where the database will be saved to disk.
- Change `UPLOAD_LOCATION` to a folder (absolute path) where media (uploaded and generated) will be stored. - Change `UPLOAD_LOCATION` to a folder where media (uploaded and generated) will be stored.
11. Click on "**Deploy the stack**". 11. Click on "**Deploy the stack**".

View File

@@ -9,7 +9,7 @@ This is a community contribution and not officially supported by the Immich team
Community support can be found in the dedicated channel on the [Discord Server](https://discord.immich.app/). 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).** **Please report app issues to the corresponding [Github Repository](https://github.com/truenas/charts/tree/master/community/immich).**
::: :::
Immich can easily be installed on TrueNAS Community Edition via the **Community** train application. Immich can easily be installed on TrueNAS Community Edition via the **Community** train application.

View File

@@ -75,6 +75,7 @@ alt="Select Plugins > Compose.Manager > Add New Stack > Label it Immich"
5. Click "**Save Changes**", you will be prompted to edit stack UI labels, just leave this blank and click "**Ok**" 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**" 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: 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` - `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. - `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.

1531
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,9 +16,8 @@
"write-heading-ids": "docusaurus write-heading-ids" "write-heading-ids": "docusaurus write-heading-ids"
}, },
"dependencies": { "dependencies": {
"@docusaurus/core": "~3.8.0", "@docusaurus/core": "~3.7.0",
"@docusaurus/preset-classic": "~3.8.0", "@docusaurus/preset-classic": "~3.7.0",
"@docusaurus/theme-common": "~3.8.0",
"@mdi/js": "^7.3.67", "@mdi/js": "^7.3.67",
"@mdi/react": "^1.6.1", "@mdi/react": "^1.6.1",
"@mdx-js/react": "^3.0.0", "@mdx-js/react": "^3.0.0",
@@ -27,7 +26,6 @@
"clsx": "^2.0.0", "clsx": "^2.0.0",
"docusaurus-lunr-search": "^3.3.2", "docusaurus-lunr-search": "^3.3.2",
"docusaurus-preset-openapi": "^0.7.5", "docusaurus-preset-openapi": "^0.7.5",
"lunr": "^2.3.9",
"postcss": "^8.4.25", "postcss": "^8.4.25",
"prism-react-renderer": "^2.3.1", "prism-react-renderer": "^2.3.1",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",
@@ -37,7 +35,7 @@
"url": "^0.11.0" "url": "^0.11.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "~3.8.0", "@docusaurus/module-type-aliases": "~3.7.0",
"@docusaurus/tsconfig": "^3.7.0", "@docusaurus/tsconfig": "^3.7.0",
"@docusaurus/types": "^3.7.0", "@docusaurus/types": "^3.7.0",
"prettier": "^3.2.4", "prettier": "^3.2.4",
@@ -59,6 +57,6 @@
"node": ">=20" "node": ">=20"
}, },
"volta": { "volta": {
"node": "22.17.0" "node": "22.16.0"
} }
} }

View File

@@ -58,12 +58,6 @@ const guides: CommunityGuidesProps[] = [
description: 'Access Immich with an end-to-end encrypted connection.', description: 'Access Immich with an end-to-end encrypted connection.',
url: 'https://meshnet.nordvpn.com/how-to/remote-files-media-access/immich-remote-access', url: 'https://meshnet.nordvpn.com/how-to/remote-files-media-access/immich-remote-access',
}, },
{
title: 'Trust Self Signed Certificates with Immich - OAuth Setup',
description:
'Set up Certificate Authority trust with Immich, and your private OAuth2/OpenID service, while using a private CA for HTTPS commication.',
url: 'https://github.com/immich-app/immich/discussions/18614',
},
]; ];
function CommunityGuide({ title, description, url }: CommunityGuidesProps): JSX.Element { function CommunityGuide({ title, description, url }: CommunityGuidesProps): JSX.Element {

View File

@@ -85,7 +85,6 @@ import React from 'react';
import { Item, Timeline } from '../components/timeline'; import { Item, Timeline } from '../components/timeline';
const releases = { const releases = {
'v1.135.0': new Date(2025, 5, 18),
'v1.133.0': new Date(2025, 4, 21), 'v1.133.0': new Date(2025, 4, 21),
'v1.130.0': new Date(2025, 2, 25), 'v1.130.0': new Date(2025, 2, 25),
'v1.127.0': new Date(2025, 1, 26), 'v1.127.0': new Date(2025, 1, 26),
@@ -197,6 +196,14 @@ const roadmap: Item[] = [
description: 'Automate tasks with workflows', description: 'Automate tasks with workflows',
getDateLabel: () => 'Planned for 2025', getDateLabel: () => 'Planned for 2025',
}, },
{
done: false,
icon: mdiTableKey,
iconColor: 'gray',
title: 'Fine grained access controls',
description: 'Granular access controls for users and api keys',
getDateLabel: () => 'Planned for 2025',
},
{ {
done: false, done: false,
icon: mdiImageEdit, icon: mdiImageEdit,
@@ -232,26 +239,12 @@ const roadmap: Item[] = [
]; ];
const milestones: Item[] = [ const milestones: Item[] = [
{
icon: mdiStar,
iconColor: 'gold',
title: '70,000 Stars',
description: 'Reached 70K Stars on GitHub!',
getDateLabel: withLanguage(new Date(2025, 6, 9)),
},
withRelease({
icon: mdiTableKey,
iconColor: 'gray',
title: 'Fine grained access controls',
description: 'Granular access controls for api keys',
release: 'v1.135.0',
}),
withRelease({ withRelease({
icon: mdiCast, icon: mdiCast,
iconColor: 'aqua', iconColor: 'aqua',
title: 'Google Cast (web and mobile)', title: 'Google Cast (web)',
description: 'Cast assets to Google Cast/Chromecast compatible devices', description: 'Cast assets to Google Cast/Chromecast compatible devices',
release: 'v1.135.0', release: 'v1.133.0',
}), }),
withRelease({ withRelease({
icon: mdiLockOutline, icon: mdiLockOutline,

View File

@@ -1,5 +1,4 @@
/docs /docs/overview/welcome 307 /docs /docs/overview/introduction 307
/docs/ /docs/overview/welcome 307
/docs/mobile-app-beta-program /docs/features/mobile-app 307 /docs/mobile-app-beta-program /docs/features/mobile-app 307
/docs/contribution-guidelines /docs/overview/support-the-project#contributing 307 /docs/contribution-guidelines /docs/overview/support-the-project#contributing 307
/docs/install /docs/install/docker-compose 307 /docs/install /docs/install/docker-compose 307

View File

@@ -1,20 +1,4 @@
[ [
{
"label": "v1.135.3",
"url": "https://v1.135.3.archive.immich.app"
},
{
"label": "v1.135.2",
"url": "https://v1.135.2.archive.immich.app"
},
{
"label": "v1.135.1",
"url": "https://v1.135.1.archive.immich.app"
},
{
"label": "v1.135.0",
"url": "https://v1.135.0.archive.immich.app"
},
{ {
"label": "v1.134.0", "label": "v1.134.0",
"url": "https://v1.134.0.archive.immich.app" "url": "https://v1.134.0.archive.immich.app"

View File

@@ -1 +1 @@
22.17.0 22.16.0

View File

@@ -36,10 +36,10 @@ services:
- 2285:2285 - 2285:2285
redis: redis:
image: redis:6.2-alpine@sha256:03fd052257735b41cd19f3d8ae9782926bf9b704fb6a9dc5e29f9ccfbe8827f0 image: redis:6.2-alpine@sha256:3211c33a618c457e5d241922c975dbc4f446d0bdb2dc75694f5573ef8e2d01fa
database: database:
image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:3aef84a0a4fabbda17ef115c3019ba0c914ec73e9f6e59203674322d858b8eea image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:9c704fb49ce27549df00f1b096cc93f8b0c959ef087507704d74954808f78a82
command: -c fsync=off -c shared_preload_libraries=vchord.so -c config_file=/var/lib/postgresql/data/postgresql.conf command: -c fsync=off -c shared_preload_libraries=vchord.so -c config_file=/var/lib/postgresql/data/postgresql.conf
environment: environment:
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres

1248
e2e/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.135.3", "version": "1.134.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
@@ -24,9 +24,8 @@
"@immich/cli": "file:../cli", "@immich/cli": "file:../cli",
"@immich/sdk": "file:../open-api/typescript-sdk", "@immich/sdk": "file:../open-api/typescript-sdk",
"@playwright/test": "^1.44.1", "@playwright/test": "^1.44.1",
"@socket.io/component-emitter": "^3.1.2",
"@types/luxon": "^3.4.2", "@types/luxon": "^3.4.2",
"@types/node": "^22.15.33", "@types/node": "^22.15.29",
"@types/oidc-provider": "^9.0.0", "@types/oidc-provider": "^9.0.0",
"@types/pg": "^8.15.1", "@types/pg": "^8.15.1",
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
@@ -45,7 +44,6 @@
"pngjs": "^7.0.0", "pngjs": "^7.0.0",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-organize-imports": "^4.0.0",
"sharp": "^0.34.0",
"socket.io-client": "^4.7.4", "socket.io-client": "^4.7.4",
"supertest": "^7.0.0", "supertest": "^7.0.0",
"typescript": "^5.3.3", "typescript": "^5.3.3",
@@ -54,6 +52,6 @@
"vitest": "^3.0.0" "vitest": "^3.0.0"
}, },
"volta": { "volta": {
"node": "22.17.0" "node": "22.16.0"
} }
} }

View File

@@ -7,7 +7,6 @@ import {
ReactionType, ReactionType,
createActivity as create, createActivity as create,
createAlbum, createAlbum,
removeAssetFromAlbum,
} from '@immich/sdk'; } from '@immich/sdk';
import { createUserDto, uuidDto } from 'src/fixtures'; import { createUserDto, uuidDto } from 'src/fixtures';
import { errorDto } from 'src/responses'; import { errorDto } from 'src/responses';
@@ -343,36 +342,5 @@ describe('/activities', () => {
expect(status).toBe(204); expect(status).toBe(204);
}); });
it('should return empty list when asset is removed', async () => {
const album3 = await createAlbum(
{
createAlbumDto: {
albumName: 'Album 3',
assetIds: [asset.id],
},
},
{ headers: asBearerAuth(admin.accessToken) },
);
await createActivity({ albumId: album3.id, assetId: asset.id, type: ReactionType.Like });
await removeAssetFromAlbum(
{
id: album3.id,
bulkIdsDto: {
ids: [asset.id],
},
},
{ headers: asBearerAuth(admin.accessToken) },
);
const { status, body } = await request(app)
.get('/activities')
.query({ albumId: album.id })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toEqual(200);
expect(body).toEqual([]);
});
}); });
}); });

View File

@@ -20,7 +20,7 @@ describe('/api-keys', () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await utils.resetDatabase(['api_key']); await utils.resetDatabase(['api_keys']);
}); });
describe('POST /api-keys', () => { describe('POST /api-keys', () => {

View File

@@ -15,7 +15,6 @@ import { DateTime } from 'luxon';
import { randomBytes } from 'node:crypto'; import { randomBytes } from 'node:crypto';
import { readFile, writeFile } from 'node:fs/promises'; import { readFile, writeFile } from 'node:fs/promises';
import { basename, join } from 'node:path'; import { basename, join } from 'node:path';
import sharp from 'sharp';
import { Socket } from 'socket.io-client'; import { Socket } from 'socket.io-client';
import { createUserDto, uuidDto } from 'src/fixtures'; import { createUserDto, uuidDto } from 'src/fixtures';
import { makeRandomImage } from 'src/generators'; import { makeRandomImage } from 'src/generators';
@@ -41,40 +40,6 @@ const today = DateTime.fromObject({
}) as DateTime<true>; }) as DateTime<true>;
const yesterday = today.minus({ days: 1 }); const yesterday = today.minus({ days: 1 });
const createTestImageWithExif = async (filename: string, exifData: Record<string, any>) => {
// Generate unique color to ensure different checksums for each image
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
// Create a 100x100 solid color JPEG using Sharp
const imageBytes = await sharp({
create: {
width: 100,
height: 100,
channels: 3,
background: { r, g, b },
},
})
.jpeg({ quality: 90 })
.toBuffer();
// Add random suffix to filename to avoid collisions
const uniqueFilename = filename.replace('.jpg', `-${randomBytes(4).toString('hex')}.jpg`);
const filepath = join(tempDir, uniqueFilename);
await writeFile(filepath, imageBytes);
// Filter out undefined values before writing EXIF
const cleanExifData = Object.fromEntries(Object.entries(exifData).filter(([, value]) => value !== undefined));
await exiftool.write(filepath, cleanExifData);
// Re-read the image bytes after EXIF has been written
const finalImageBytes = await readFile(filepath);
return { filepath, imageBytes: finalImageBytes, filename: uniqueFilename };
};
describe('/asset', () => { describe('/asset', () => {
let admin: LoginResponseDto; let admin: LoginResponseDto;
let websocket: Socket; let websocket: Socket;
@@ -1225,411 +1190,6 @@ describe('/asset', () => {
}); });
}); });
describe('EXIF metadata extraction', () => {
describe('Additional date tag extraction', () => {
describe('Date-time vs time-only tag handling', () => {
it('should fall back to file timestamps when only time-only tags are available', async () => {
const { imageBytes, filename } = await createTestImageWithExif('time-only-fallback.jpg', {
TimeCreated: '2023:11:15 14:30:00', // Time-only tag, should not be used for dateTimeOriginal
// Exclude all date-time tags to force fallback to file timestamps
SubSecDateTimeOriginal: undefined,
DateTimeOriginal: undefined,
SubSecCreateDate: undefined,
SubSecMediaCreateDate: undefined,
CreateDate: undefined,
MediaCreateDate: undefined,
CreationDate: undefined,
DateTimeCreated: undefined,
GPSDateTime: undefined,
DateTimeUTC: undefined,
SonyDateTime2: undefined,
GPSDateStamp: undefined,
});
const oldDate = new Date('2020-01-01T00:00:00.000Z');
const asset = await utils.createAsset(admin.accessToken, {
assetData: {
filename,
bytes: imageBytes,
},
fileCreatedAt: oldDate.toISOString(),
fileModifiedAt: oldDate.toISOString(),
});
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
// Should fall back to file timestamps, which we set to 2020-01-01
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
new Date('2020-01-01T00:00:00.000Z').getTime(),
);
});
it('should prefer DateTimeOriginal over time-only tags', async () => {
const { imageBytes, filename } = await createTestImageWithExif('datetime-over-time.jpg', {
DateTimeOriginal: '2023:10:10 10:00:00', // Should be preferred
TimeCreated: '2023:11:15 14:30:00', // Should be ignored (time-only)
});
const asset = await utils.createAsset(admin.accessToken, {
assetData: {
filename,
bytes: imageBytes,
},
});
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
// Should use DateTimeOriginal, not TimeCreated
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
new Date('2023-10-10T10:00:00.000Z').getTime(),
);
});
});
describe('GPSDateTime tag extraction', () => {
it('should extract GPSDateTime with GPS coordinates', async () => {
const { imageBytes, filename } = await createTestImageWithExif('gps-datetime.jpg', {
GPSDateTime: '2023:11:15 12:30:00Z',
GPSLatitude: 37.7749,
GPSLongitude: -122.4194,
// Exclude other date tags
SubSecDateTimeOriginal: undefined,
DateTimeOriginal: undefined,
SubSecCreateDate: undefined,
SubSecMediaCreateDate: undefined,
CreateDate: undefined,
MediaCreateDate: undefined,
CreationDate: undefined,
DateTimeCreated: undefined,
TimeCreated: undefined,
});
const asset = await utils.createAsset(admin.accessToken, {
assetData: {
filename,
bytes: imageBytes,
},
});
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
expect(assetInfo.exifInfo?.latitude).toBeCloseTo(37.7749, 4);
expect(assetInfo.exifInfo?.longitude).toBeCloseTo(-122.4194, 4);
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
new Date('2023-11-15T12:30:00.000Z').getTime(),
);
});
});
describe('CreateDate tag extraction', () => {
it('should extract CreateDate when available', async () => {
const { imageBytes, filename } = await createTestImageWithExif('create-date.jpg', {
CreateDate: '2023:11:15 10:30:00',
// Exclude other higher priority date tags
SubSecDateTimeOriginal: undefined,
DateTimeOriginal: undefined,
SubSecCreateDate: undefined,
SubSecMediaCreateDate: undefined,
MediaCreateDate: undefined,
CreationDate: undefined,
DateTimeCreated: undefined,
TimeCreated: undefined,
GPSDateTime: undefined,
});
const asset = await utils.createAsset(admin.accessToken, {
assetData: {
filename,
bytes: imageBytes,
},
});
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
new Date('2023-11-15T10:30:00.000Z').getTime(),
);
});
});
describe('GPSDateStamp tag extraction', () => {
it('should fall back to file timestamps when only date-only tags are available', async () => {
const { imageBytes, filename } = await createTestImageWithExif('gps-datestamp.jpg', {
GPSDateStamp: '2023:11:15', // Date-only tag, should not be used for dateTimeOriginal
// Note: NOT including GPSTimeStamp to avoid automatic GPSDateTime creation
GPSLatitude: 51.5074,
GPSLongitude: -0.1278,
// Explicitly exclude all testable date-time tags to force fallback to file timestamps
DateTimeOriginal: undefined,
CreateDate: undefined,
CreationDate: undefined,
GPSDateTime: undefined,
});
const oldDate = new Date('2020-01-01T00:00:00.000Z');
const asset = await utils.createAsset(admin.accessToken, {
assetData: {
filename,
bytes: imageBytes,
},
fileCreatedAt: oldDate.toISOString(),
fileModifiedAt: oldDate.toISOString(),
});
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
expect(assetInfo.exifInfo?.latitude).toBeCloseTo(51.5074, 4);
expect(assetInfo.exifInfo?.longitude).toBeCloseTo(-0.1278, 4);
// Should fall back to file timestamps, which we set to 2020-01-01
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
new Date('2020-01-01T00:00:00.000Z').getTime(),
);
});
});
/*
* NOTE: The following EXIF date tags are NOT effectively usable with JPEG test files:
*
* NOT WRITABLE to JPEG:
* - MediaCreateDate: Can be read from video files but not written to JPEG
* - DateTimeCreated: Read-only tag in JPEG format
* - DateTimeUTC: Cannot be written to JPEG files
* - SonyDateTime2: Proprietary Sony tag, not writable to JPEG
* - SubSecMediaCreateDate: Tag not defined for JPEG format
* - SourceImageCreateTime: Non-standard insta360 tag, not writable to JPEG
*
* WRITABLE but NOT READABLE from JPEG:
* - SubSecDateTimeOriginal: Can be written but not read back from JPEG
* - SubSecCreateDate: Can be written but not read back from JPEG
*
* EFFECTIVELY TESTABLE TAGS (writable and readable):
* - DateTimeOriginal ✓
* - CreateDate ✓
* - CreationDate ✓
* - GPSDateTime ✓
*
* The metadata service correctly handles non-readable tags and will fall back to
* file timestamps when only non-readable tags are present.
*/
describe('Date tag priority order', () => {
it('should respect the complete date tag priority order', async () => {
// Test cases using only EFFECTIVELY TESTABLE tags (writable AND readable from JPEG)
const testCases = [
{
name: 'DateTimeOriginal has highest priority among testable tags',
exifData: {
DateTimeOriginal: '2023:04:04 04:00:00', // TESTABLE - highest priority among readable tags
CreateDate: '2023:05:05 05:00:00', // TESTABLE
CreationDate: '2023:07:07 07:00:00', // TESTABLE
GPSDateTime: '2023:10:10 10:00:00', // TESTABLE
},
expectedDate: '2023-04-04T04:00:00.000Z',
},
{
name: 'CreateDate when DateTimeOriginal missing',
exifData: {
CreateDate: '2023:05:05 05:00:00', // TESTABLE
CreationDate: '2023:07:07 07:00:00', // TESTABLE
GPSDateTime: '2023:10:10 10:00:00', // TESTABLE
},
expectedDate: '2023-05-05T05:00:00.000Z',
},
{
name: 'CreationDate when standard EXIF tags missing',
exifData: {
CreationDate: '2023:07:07 07:00:00', // TESTABLE
GPSDateTime: '2023:10:10 10:00:00', // TESTABLE
},
expectedDate: '2023-07-07T07:00:00.000Z',
},
{
name: 'GPSDateTime when no other testable date tags present',
exifData: {
GPSDateTime: '2023:10:10 10:00:00', // TESTABLE
Make: 'SONY',
},
expectedDate: '2023-10-10T10:00:00.000Z',
},
];
for (const testCase of testCases) {
const { imageBytes, filename } = await createTestImageWithExif(
`${testCase.name.replaceAll(/\s+/g, '-').toLowerCase()}.jpg`,
testCase.exifData,
);
const asset = await utils.createAsset(admin.accessToken, {
assetData: {
filename,
bytes: imageBytes,
},
});
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
expect(assetInfo.exifInfo?.dateTimeOriginal, `Failed for: ${testCase.name}`).toBeDefined();
expect(
new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime(),
`Date mismatch for: ${testCase.name}`,
).toBe(new Date(testCase.expectedDate).getTime());
}
});
});
describe('Edge cases for date tag handling', () => {
it('should fall back to file timestamps with GPSDateStamp alone', async () => {
const { imageBytes, filename } = await createTestImageWithExif('gps-datestamp-only.jpg', {
GPSDateStamp: '2023:08:08', // Date-only tag, should not be used for dateTimeOriginal
// Intentionally no GPSTimeStamp
// Exclude all other date tags
SubSecDateTimeOriginal: undefined,
DateTimeOriginal: undefined,
SubSecCreateDate: undefined,
SubSecMediaCreateDate: undefined,
CreateDate: undefined,
MediaCreateDate: undefined,
CreationDate: undefined,
DateTimeCreated: undefined,
TimeCreated: undefined,
GPSDateTime: undefined,
DateTimeUTC: undefined,
});
const oldDate = new Date('2020-01-01T00:00:00.000Z');
const asset = await utils.createAsset(admin.accessToken, {
assetData: {
filename,
bytes: imageBytes,
},
fileCreatedAt: oldDate.toISOString(),
fileModifiedAt: oldDate.toISOString(),
});
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
// Should fall back to file timestamps, which we set to 2020-01-01
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
new Date('2020-01-01T00:00:00.000Z').getTime(),
);
});
it('should handle all testable date tags present to verify complete priority order', async () => {
const { imageBytes, filename } = await createTestImageWithExif('all-testable-date-tags.jpg', {
// All TESTABLE date tags to JPEG format (writable AND readable)
DateTimeOriginal: '2023:04:04 04:00:00', // TESTABLE - highest priority among readable tags
CreateDate: '2023:05:05 05:00:00', // TESTABLE
CreationDate: '2023:07:07 07:00:00', // TESTABLE
GPSDateTime: '2023:10:10 10:00:00', // TESTABLE
// Note: Excluded non-testable tags:
// SubSec tags: writable but not readable from JPEG
// Non-writable tags: MediaCreateDate, DateTimeCreated, DateTimeUTC, SonyDateTime2, etc.
// Time-only/date-only tags: already excluded from EXIF_DATE_TAGS
});
const asset = await utils.createAsset(admin.accessToken, {
assetData: {
filename,
bytes: imageBytes,
},
});
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
// Should use DateTimeOriginal as it has the highest priority among testable tags
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
new Date('2023-04-04T04:00:00.000Z').getTime(),
);
});
it('should use CreationDate when SubSec tags are missing', async () => {
const { imageBytes, filename } = await createTestImageWithExif('creation-date-priority.jpg', {
CreationDate: '2023:07:07 07:00:00', // WRITABLE
GPSDateTime: '2023:10:10 10:00:00', // WRITABLE
// Note: DateTimeCreated, DateTimeUTC, SonyDateTime2 are NOT writable to JPEG
// Note: TimeCreated and GPSDateStamp are excluded from EXIF_DATE_TAGS (time-only/date-only)
// Exclude SubSec and standard EXIF tags
SubSecDateTimeOriginal: undefined,
DateTimeOriginal: undefined,
SubSecCreateDate: undefined,
CreateDate: undefined,
});
const asset = await utils.createAsset(admin.accessToken, {
assetData: {
filename,
bytes: imageBytes,
},
});
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
// Should use CreationDate when available
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
new Date('2023-07-07T07:00:00.000Z').getTime(),
);
});
it('should skip invalid date formats and use next valid tag', async () => {
const { imageBytes, filename } = await createTestImageWithExif('invalid-date-handling.jpg', {
// Note: Testing invalid date handling with only WRITABLE tags
GPSDateTime: '2023:10:10 10:00:00', // WRITABLE - Valid date
CreationDate: '2023:13:13 13:00:00', // WRITABLE - Valid date
// Note: TimeCreated excluded (time-only), DateTimeCreated not writable to JPEG
// Exclude other date tags
SubSecDateTimeOriginal: undefined,
DateTimeOriginal: undefined,
SubSecCreateDate: undefined,
CreateDate: undefined,
});
const asset = await utils.createAsset(admin.accessToken, {
assetData: {
filename,
bytes: imageBytes,
},
});
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
// Should skip invalid dates and use the first valid one (GPSDateTime)
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
new Date('2023-10-10T10:00:00.000Z').getTime(),
);
});
});
});
});
describe('POST /assets/exist', () => { describe('POST /assets/exist', () => {
it('ignores invalid deviceAssetIds', async () => { it('ignores invalid deviceAssetIds', async () => {
const response = await utils.checkExistingAssets(user1.accessToken, { const response = await utils.checkExistingAssets(user1.accessToken, {

View File

@@ -0,0 +1,146 @@
import { LoginResponseDto, login, signUpAdmin } from '@immich/sdk';
import { loginDto, signupDto } from 'src/fixtures';
import { errorDto, loginResponseDto, signupResponseDto } from 'src/responses';
import { app, utils } from 'src/utils';
import request from 'supertest';
import { beforeEach, describe, expect, it } from 'vitest';
const { email, password } = signupDto.admin;
describe(`/auth/admin-sign-up`, () => {
beforeEach(async () => {
await utils.resetDatabase();
});
describe('POST /auth/admin-sign-up', () => {
it(`should sign up the admin`, async () => {
const { status, body } = await request(app).post('/auth/admin-sign-up').send(signupDto.admin);
expect(status).toBe(201);
expect(body).toEqual(signupResponseDto.admin);
});
it('should not allow a second admin to sign up', async () => {
await signUpAdmin({ signUpDto: signupDto.admin });
const { status, body } = await request(app).post('/auth/admin-sign-up').send(signupDto.admin);
expect(status).toBe(400);
expect(body).toEqual(errorDto.alreadyHasAdmin);
});
});
});
describe('/auth/*', () => {
let admin: LoginResponseDto;
beforeEach(async () => {
await utils.resetDatabase();
await signUpAdmin({ signUpDto: signupDto.admin });
admin = await login({ loginCredentialDto: loginDto.admin });
});
describe(`POST /auth/login`, () => {
it('should reject an incorrect password', async () => {
const { status, body } = await request(app).post('/auth/login').send({ email, password: 'incorrect' });
expect(status).toBe(401);
expect(body).toEqual(errorDto.incorrectLogin);
});
it('should accept a correct password', async () => {
const { status, body, headers } = await request(app).post('/auth/login').send({ email, password });
expect(status).toBe(201);
expect(body).toEqual(loginResponseDto.admin);
const token = body.accessToken;
expect(token).toBeDefined();
const cookies = headers['set-cookie'];
expect(cookies).toHaveLength(3);
expect(cookies[0].split(';').map((item) => item.trim())).toEqual([
`immich_access_token=${token}`,
'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/validateToken', () => {
it('should reject an invalid token', async () => {
const { status, body } = await request(app).post(`/auth/validateToken`).set('Authorization', 'Bearer 123');
expect(status).toBe(401);
expect(body).toEqual(errorDto.invalidToken);
});
it('should accept a valid token', async () => {
const { status, body } = await request(app)
.post(`/auth/validateToken`)
.send({})
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({ authStatus: true });
});
});
describe('POST /auth/change-password', () => {
it('should require the current password', async () => {
const { status, body } = await request(app)
.post(`/auth/change-password`)
.send({ password: 'wrong-password', newPassword: 'Password1234' })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.wrongPassword);
});
it('should change the password', async () => {
const { status } = await request(app)
.post(`/auth/change-password`)
.send({ password, newPassword: 'Password1234' })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
await login({
loginCredentialDto: {
email: 'admin@immich.cloud',
password: 'Password1234',
},
});
});
});
describe('POST /auth/logout', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post(`/auth/logout`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should logout the user', async () => {
const { status, body } = await request(app)
.post(`/auth/logout`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({
successful: true,
redirectUri: '/auth/login?autoLaunch=0',
});
});
});
});

View File

@@ -6,7 +6,7 @@ import {
createMemory, createMemory,
getMemory, getMemory,
} from '@immich/sdk'; } from '@immich/sdk';
import { createUserDto } from 'src/fixtures'; import { createUserDto, uuidDto } from 'src/fixtures';
import { errorDto } from 'src/responses'; import { errorDto } from 'src/responses';
import { app, asBearerAuth, utils } from 'src/utils'; import { app, asBearerAuth, utils } from 'src/utils';
import request from 'supertest'; import request from 'supertest';
@@ -17,6 +17,7 @@ describe('/memories', () => {
let user: LoginResponseDto; let user: LoginResponseDto;
let adminAsset: AssetMediaResponseDto; let adminAsset: AssetMediaResponseDto;
let userAsset1: AssetMediaResponseDto; let userAsset1: AssetMediaResponseDto;
let userAsset2: AssetMediaResponseDto;
let userMemory: MemoryResponseDto; let userMemory: MemoryResponseDto;
beforeAll(async () => { beforeAll(async () => {
@@ -24,9 +25,10 @@ describe('/memories', () => {
admin = await utils.adminSetup(); admin = await utils.adminSetup();
user = await utils.userSetup(admin.accessToken, createUserDto.user1); user = await utils.userSetup(admin.accessToken, createUserDto.user1);
[adminAsset, userAsset1] = await Promise.all([ [adminAsset, userAsset1, userAsset2] = await Promise.all([
utils.createAsset(admin.accessToken), utils.createAsset(admin.accessToken),
utils.createAsset(user.accessToken), utils.createAsset(user.accessToken),
utils.createAsset(user.accessToken),
]); ]);
userMemory = await createMemory( userMemory = await createMemory(
{ {
@@ -41,7 +43,121 @@ describe('/memories', () => {
); );
}); });
describe('GET /memories', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/memories');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
});
describe('POST /memories', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post('/memories');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should validate data when type is on this day', async () => {
const { status, body } = await request(app)
.post('/memories')
.set('Authorization', `Bearer ${user.accessToken}`)
.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']),
);
});
it('should create a new memory', async () => {
const { status, body } = await request(app)
.post('/memories')
.set('Authorization', `Bearer ${user.accessToken}`)
.send({
type: 'on_this_day',
data: { year: 2021 },
memoryAt: new Date(2021).toISOString(),
});
expect(status).toBe(201);
expect(body).toEqual({
id: expect.any(String),
type: 'on_this_day',
data: { year: 2021 },
createdAt: expect.any(String),
updatedAt: expect.any(String),
isSaved: false,
memoryAt: expect.any(String),
ownerId: user.userId,
assets: [],
});
});
it('should create a new memory (with assets)', async () => {
const { status, body } = await request(app)
.post('/memories')
.set('Authorization', `Bearer ${user.accessToken}`)
.send({
type: 'on_this_day',
data: { year: 2021 },
memoryAt: new Date(2021).toISOString(),
assetIds: [userAsset1.id, userAsset2.id],
});
expect(status).toBe(201);
expect(body).toMatchObject({
id: expect.any(String),
assets: expect.arrayContaining([
expect.objectContaining({ id: userAsset1.id }),
expect.objectContaining({ id: userAsset2.id }),
]),
});
expect(body.assets).toHaveLength(2);
});
it('should create a new memory and ignore assets the user does not have access to', async () => {
const { status, body } = await request(app)
.post('/memories')
.set('Authorization', `Bearer ${user.accessToken}`)
.send({
type: 'on_this_day',
data: { year: 2021 },
memoryAt: new Date(2021).toISOString(),
assetIds: [userAsset1.id, adminAsset.id],
});
expect(status).toBe(201);
expect(body).toMatchObject({
id: expect.any(String),
assets: [expect.objectContaining({ id: userAsset1.id })],
});
expect(body.assets).toHaveLength(1);
});
});
describe('GET /memories/:id', () => { describe('GET /memories/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get(`/memories/${uuidDto.invalid}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should require a valid id', async () => {
const { status, body } = await request(app)
.get(`/memories/${uuidDto.invalid}`)
.set('Authorization', `Bearer ${user.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['id must be a UUID']));
});
it('should require access', async () => { it('should require access', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.get(`/memories/${userMemory.id}`) .get(`/memories/${userMemory.id}`)
@@ -60,6 +176,22 @@ describe('/memories', () => {
}); });
describe('PUT /memories/:id', () => { describe('PUT /memories/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).put(`/memories/${uuidDto.invalid}`).send({ isSaved: true });
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should require a valid id', async () => {
const { status, body } = await request(app)
.put(`/memories/${uuidDto.invalid}`)
.send({ isSaved: true })
.set('Authorization', `Bearer ${user.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['id must be a UUID']));
});
it('should require access', async () => { it('should require access', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.put(`/memories/${userMemory.id}`) .put(`/memories/${userMemory.id}`)
@@ -86,6 +218,23 @@ describe('/memories', () => {
}); });
describe('PUT /memories/:id/assets', () => { describe('PUT /memories/:id/assets', () => {
it('should require authentication', async () => {
const { status, body } = await request(app)
.put(`/memories/${userMemory.id}/assets`)
.send({ ids: [userAsset1.id] });
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should require a valid id', async () => {
const { status, body } = await request(app)
.put(`/memories/${uuidDto.invalid}/assets`)
.send({ ids: [userAsset1.id] })
.set('Authorization', `Bearer ${user.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['id must be a UUID']));
});
it('should require access', async () => { it('should require access', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.put(`/memories/${userMemory.id}/assets`) .put(`/memories/${userMemory.id}/assets`)
@@ -95,6 +244,15 @@ describe('/memories', () => {
expect(body).toEqual(errorDto.noPermission); expect(body).toEqual(errorDto.noPermission);
}); });
it('should require a valid asset id', async () => {
const { status, body } = await request(app)
.put(`/memories/${userMemory.id}/assets`)
.send({ ids: [uuidDto.invalid] })
.set('Authorization', `Bearer ${user.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['each value in ids must be a UUID']));
});
it('should require asset access', async () => { it('should require asset access', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.put(`/memories/${userMemory.id}/assets`) .put(`/memories/${userMemory.id}/assets`)
@@ -121,6 +279,23 @@ describe('/memories', () => {
}); });
describe('DELETE /memories/:id/assets', () => { describe('DELETE /memories/:id/assets', () => {
it('should require authentication', async () => {
const { status, body } = await request(app)
.delete(`/memories/${userMemory.id}/assets`)
.send({ ids: [userAsset1.id] });
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should require a valid id', async () => {
const { status, body } = await request(app)
.delete(`/memories/${uuidDto.invalid}/assets`)
.send({ ids: [userAsset1.id] })
.set('Authorization', `Bearer ${user.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['id must be a UUID']));
});
it('should require access', async () => { it('should require access', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.delete(`/memories/${userMemory.id}/assets`) .delete(`/memories/${userMemory.id}/assets`)
@@ -130,6 +305,15 @@ describe('/memories', () => {
expect(body).toEqual(errorDto.noPermission); expect(body).toEqual(errorDto.noPermission);
}); });
it('should require a valid asset id', async () => {
const { status, body } = await request(app)
.delete(`/memories/${userMemory.id}/assets`)
.send({ ids: [uuidDto.invalid] })
.set('Authorization', `Bearer ${user.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['each value in ids must be a UUID']));
});
it('should only remove assets in the memory', async () => { it('should only remove assets in the memory', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.delete(`/memories/${userMemory.id}/assets`) .delete(`/memories/${userMemory.id}/assets`)
@@ -156,6 +340,21 @@ describe('/memories', () => {
}); });
describe('DELETE /memories/:id', () => { describe('DELETE /memories/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).delete(`/memories/${uuidDto.invalid}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should require a valid id', async () => {
const { status, body } = await request(app)
.delete(`/memories/${uuidDto.invalid}`)
.set('Authorization', `Bearer ${user.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['id must be a UUID']));
});
it('should require access', async () => { it('should require access', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.delete(`/memories/${userMemory.id}`) .delete(`/memories/${userMemory.id}`)

View File

@@ -227,21 +227,6 @@ describe(`/oauth`, () => {
expect(user.storageLabel).toBe('user-username'); expect(user.storageLabel).toBe('user-username');
}); });
it('should set the admin status from a role claim', async () => {
const callbackParams = await loginWithOAuth(OAuthUser.WITH_ROLE);
const { status, body } = await request(app).post('/oauth/callback').send(callbackParams);
expect(status).toBe(201);
expect(body).toMatchObject({
accessToken: expect.any(String),
userId: expect.any(String),
userEmail: 'oauth-with-role@immich.app',
isAdmin: true,
});
const user = await getMyUser({ headers: asBearerAuth(body.accessToken) });
expect(user.isAdmin).toBe(true);
});
it('should work with RS256 signed tokens', async () => { it('should work with RS256 signed tokens', async () => {
await setupOAuth(admin.accessToken, { await setupOAuth(admin.accessToken, {
enabled: true, enabled: true,

View File

@@ -14,11 +14,7 @@ describe('/people', () => {
let nameAlicePerson: PersonResponseDto; let nameAlicePerson: PersonResponseDto;
let nameBobPerson: PersonResponseDto; let nameBobPerson: PersonResponseDto;
let nameCharliePerson: PersonResponseDto; let nameCharliePerson: PersonResponseDto;
let nameNullPerson4Assets: PersonResponseDto; let nameNullPerson: PersonResponseDto;
let nameNullPerson3Assets: PersonResponseDto;
let nameNullPerson1Asset: PersonResponseDto;
let nameBillPersonFavourite: PersonResponseDto;
let nameFreddyPersonFavourite: PersonResponseDto;
beforeAll(async () => { beforeAll(async () => {
await utils.resetDatabase(); await utils.resetDatabase();
@@ -31,11 +27,7 @@ describe('/people', () => {
nameCharliePerson, nameCharliePerson,
nameBobPerson, nameBobPerson,
nameAlicePerson, nameAlicePerson,
nameNullPerson4Assets, nameNullPerson,
nameNullPerson3Assets,
nameNullPerson1Asset,
nameBillPersonFavourite,
nameFreddyPersonFavourite,
] = await Promise.all([ ] = await Promise.all([
utils.createPerson(admin.accessToken, { utils.createPerson(admin.accessToken, {
name: 'visible_person', name: 'visible_person',
@@ -60,26 +52,11 @@ describe('/people', () => {
utils.createPerson(admin.accessToken, { utils.createPerson(admin.accessToken, {
name: '', name: '',
}), }),
utils.createPerson(admin.accessToken, {
name: '',
}),
utils.createPerson(admin.accessToken, {
name: '',
}),
utils.createPerson(admin.accessToken, {
name: 'Bill',
isFavorite: true,
}),
utils.createPerson(admin.accessToken, {
name: 'Freddy',
isFavorite: true,
}),
]); ]);
const asset1 = await utils.createAsset(admin.accessToken); const asset1 = await utils.createAsset(admin.accessToken);
const asset2 = await utils.createAsset(admin.accessToken); const asset2 = await utils.createAsset(admin.accessToken);
const asset3 = await utils.createAsset(admin.accessToken); const asset3 = await utils.createAsset(admin.accessToken);
const asset4 = await utils.createAsset(admin.accessToken);
await Promise.all([ await Promise.all([
utils.createFace({ assetId: asset1.id, personId: visiblePerson.id }), utils.createFace({ assetId: asset1.id, personId: visiblePerson.id }),
@@ -87,27 +64,15 @@ describe('/people', () => {
utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }), utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }),
utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }), utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }),
utils.createFace({ assetId: asset2.id, personId: multipleAssetsPerson.id }), utils.createFace({ assetId: asset2.id, personId: multipleAssetsPerson.id }),
utils.createFace({ assetId: asset3.id, personId: multipleAssetsPerson.id }), // 4 assets utils.createFace({ assetId: asset3.id, personId: multipleAssetsPerson.id }),
// Named persons // Named persons
utils.createFace({ assetId: asset1.id, personId: nameCharliePerson.id }), // 1 asset utils.createFace({ assetId: asset1.id, personId: nameCharliePerson.id }), // 1 asset
utils.createFace({ assetId: asset1.id, personId: nameBobPerson.id }), utils.createFace({ assetId: asset1.id, personId: nameBobPerson.id }),
utils.createFace({ assetId: asset2.id, personId: nameBobPerson.id }), // 2 assets utils.createFace({ assetId: asset2.id, personId: nameBobPerson.id }), // 2 assets
utils.createFace({ assetId: asset1.id, personId: nameAlicePerson.id }), // 1 asset utils.createFace({ assetId: asset1.id, personId: nameAlicePerson.id }), // 1 asset
// Null-named person 4 assets // Null-named person
utils.createFace({ assetId: asset1.id, personId: nameNullPerson4Assets.id }), utils.createFace({ assetId: asset1.id, personId: nameNullPerson.id }),
utils.createFace({ assetId: asset2.id, personId: nameNullPerson4Assets.id }), utils.createFace({ assetId: asset2.id, personId: nameNullPerson.id }), // 2 assets
utils.createFace({ assetId: asset3.id, personId: nameNullPerson4Assets.id }),
utils.createFace({ assetId: asset4.id, personId: nameNullPerson4Assets.id }), // 4 assets
// Null-named person 3 assets
utils.createFace({ assetId: asset1.id, personId: nameNullPerson3Assets.id }),
utils.createFace({ assetId: asset2.id, personId: nameNullPerson3Assets.id }),
utils.createFace({ assetId: asset3.id, personId: nameNullPerson3Assets.id }), // 3 assets
// Null-named person 1 asset
utils.createFace({ assetId: asset3.id, personId: nameNullPerson1Asset.id }),
// Favourite People
utils.createFace({ assetId: asset1.id, personId: nameFreddyPersonFavourite.id }),
utils.createFace({ assetId: asset2.id, personId: nameFreddyPersonFavourite.id }),
utils.createFace({ assetId: asset1.id, personId: nameBillPersonFavourite.id }),
]); ]);
}); });
@@ -122,19 +87,15 @@ describe('/people', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ expect(body).toEqual({
hasNextPage: false, hasNextPage: false,
total: 11, total: 7,
hidden: 1, hidden: 1,
people: [ people: [
expect.objectContaining({ name: 'Freddy' }),
expect.objectContaining({ name: 'Bill' }),
expect.objectContaining({ name: 'multiple_assets_person' }), expect.objectContaining({ name: 'multiple_assets_person' }),
expect.objectContaining({ name: 'Bob' }), expect.objectContaining({ name: 'Bob' }),
expect.objectContaining({ name: 'Alice' }), expect.objectContaining({ name: 'Alice' }),
expect.objectContaining({ name: 'Charlie' }), expect.objectContaining({ name: 'Charlie' }),
expect.objectContaining({ name: 'visible_person' }), expect.objectContaining({ name: 'visible_person' }),
expect.objectContaining({ id: nameNullPerson4Assets.id, name: '' }), expect.objectContaining({ name: 'hidden_person' }),
expect.objectContaining({ id: nameNullPerson3Assets.id, name: '' }),
expect.objectContaining({ name: 'hidden_person' }), // Should really be before the null names
], ],
}); });
}); });
@@ -144,21 +105,17 @@ describe('/people', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body.hasNextPage).toBe(false); expect(body.hasNextPage).toBe(false);
expect(body.total).toBe(11); // All persons expect(body.total).toBe(7); // All persons
expect(body.hidden).toBe(1); // 'hidden_person' expect(body.hidden).toBe(1); // 'hidden_person'
const people = body.people as PersonResponseDto[]; const people = body.people as PersonResponseDto[];
expect(people.map((p) => p.id)).toEqual([ expect(people.map((p) => p.id)).toEqual([
nameFreddyPersonFavourite.id, // name: 'Freddy', count: 2
nameBillPersonFavourite.id, // name: 'Bill', count: 1
multipleAssetsPerson.id, // name: 'multiple_assets_person', count: 3 multipleAssetsPerson.id, // name: 'multiple_assets_person', count: 3
nameBobPerson.id, // name: 'Bob', count: 2 nameBobPerson.id, // name: 'Bob', count: 2
nameAlicePerson.id, // name: 'Alice', count: 1 nameAlicePerson.id, // name: 'Alice', count: 1
nameCharliePerson.id, // name: 'Charlie', count: 1 nameCharliePerson.id, // name: 'Charlie', count: 1
visiblePerson.id, // name: 'visible_person', count: 1 visiblePerson.id, // name: 'visible_person', count: 1
nameNullPerson4Assets.id, // name: '', count: 4
nameNullPerson3Assets.id, // name: '', count: 3
]); ]);
expect(people.some((p) => p.id === hiddenPerson.id)).toBe(false); expect(people.some((p) => p.id === hiddenPerson.id)).toBe(false);
@@ -170,18 +127,14 @@ describe('/people', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ expect(body).toEqual({
hasNextPage: false, hasNextPage: false,
total: 11, total: 7,
hidden: 1, hidden: 1,
people: [ people: [
expect.objectContaining({ name: 'Freddy' }),
expect.objectContaining({ name: 'Bill' }),
expect.objectContaining({ name: 'multiple_assets_person' }), expect.objectContaining({ name: 'multiple_assets_person' }),
expect.objectContaining({ name: 'Bob' }), expect.objectContaining({ name: 'Bob' }),
expect.objectContaining({ name: 'Alice' }), expect.objectContaining({ name: 'Alice' }),
expect.objectContaining({ name: 'Charlie' }), expect.objectContaining({ name: 'Charlie' }),
expect.objectContaining({ name: 'visible_person' }), expect.objectContaining({ name: 'visible_person' }),
expect.objectContaining({ id: nameNullPerson4Assets.id, name: '' }),
expect.objectContaining({ id: nameNullPerson3Assets.id, name: '' }),
], ],
}); });
}); });
@@ -195,9 +148,9 @@ describe('/people', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ expect(body).toEqual({
hasNextPage: true, hasNextPage: true,
total: 11, total: 7,
hidden: 1, hidden: 1,
people: [expect.objectContaining({ name: 'Alice' })], people: [expect.objectContaining({ name: 'visible_person' })],
}); });
}); });
}); });

View File

@@ -117,25 +117,8 @@ describe('/shared-links', () => {
const resp = await request(shareUrl).get(`/${linkWithAssets.key}`); const resp = await request(shareUrl).get(`/${linkWithAssets.key}`);
expect(resp.status).toBe(200); expect(resp.status).toBe(200);
expect(resp.header['content-type']).toContain('text/html'); expect(resp.header['content-type']).toContain('text/html');
expect(resp.text).toContain(`<meta property="og:image" content="http://127.0.0.1:2285`);
});
it('should fall back to my.immich.app og:image meta tag for shared asset if Host header is not present', async () => {
const resp = await request(shareUrl).get(`/${linkWithAssets.key}`).set('Host', '');
expect(resp.status).toBe(200);
expect(resp.header['content-type']).toContain('text/html');
expect(resp.text).toContain(`<meta property="og:image" content="https://my.immich.app`); expect(resp.text).toContain(`<meta property="og:image" content="https://my.immich.app`);
}); });
it('should return 404 for an invalid shared link', async () => {
const resp = await request(shareUrl).get(`/invalid-key`);
expect(resp.status).toBe(404);
expect(resp.header['content-type']).toContain('text/html');
expect(resp.text).not.toContain(`og:type`);
expect(resp.text).not.toContain(`og:title`);
expect(resp.text).not.toContain(`og:description`);
expect(resp.text).not.toContain(`og:image`);
});
}); });
describe('GET /shared-links', () => { describe('GET /shared-links', () => {

View File

@@ -15,6 +15,12 @@ describe('/system-config', () => {
}); });
describe('PUT /system-config', () => { describe('PUT /system-config', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).put('/system-config');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should always return the new config', async () => { it('should always return the new config', async () => {
const config = await getSystemConfig(admin.accessToken); const config = await getSystemConfig(admin.accessToken);

View File

@@ -37,7 +37,7 @@ describe('/tags', () => {
beforeEach(async () => { beforeEach(async () => {
// tagging assets eventually triggers metadata extraction which can impact other tests // tagging assets eventually triggers metadata extraction which can impact other tests
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction'); await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.resetDatabase(['tag']); await utils.resetDatabase(['tags']);
}); });
describe('POST /tags', () => { describe('POST /tags', () => {

View File

@@ -0,0 +1,230 @@
import {
AssetMediaResponseDto,
AssetVisibility,
LoginResponseDto,
SharedLinkType,
TimeBucketAssetResponseDto,
} from '@immich/sdk';
import { DateTime } from 'luxon';
import { createUserDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { app, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, describe, expect, it } from 'vitest';
// TODO this should probably be a test util function
const today = DateTime.fromObject({
year: 2023,
month: 11,
day: 3,
}) as DateTime<true>;
const yesterday = today.minus({ days: 1 });
describe('/timeline', () => {
let admin: LoginResponseDto;
let user: LoginResponseDto;
let timeBucketUser: LoginResponseDto;
let user1Assets: AssetMediaResponseDto[];
let user2Assets: AssetMediaResponseDto[];
beforeAll(async () => {
await utils.resetDatabase();
admin = await utils.adminSetup({ onboarding: false });
[user, timeBucketUser] = await Promise.all([
utils.userSetup(admin.accessToken, createUserDto.create('1')),
utils.userSetup(admin.accessToken, createUserDto.create('time-bucket')),
]);
user1Assets = await Promise.all([
utils.createAsset(user.accessToken),
utils.createAsset(user.accessToken),
utils.createAsset(user.accessToken, {
isFavorite: true,
fileCreatedAt: yesterday.toISO(),
fileModifiedAt: yesterday.toISO(),
assetData: { filename: 'example.mp4' },
}),
utils.createAsset(user.accessToken),
utils.createAsset(user.accessToken),
]);
user2Assets = await Promise.all([
utils.createAsset(timeBucketUser.accessToken, { fileCreatedAt: new Date('1970-01-01').toISOString() }),
utils.createAsset(timeBucketUser.accessToken, { fileCreatedAt: new Date('1970-02-10').toISOString() }),
utils.createAsset(timeBucketUser.accessToken, { fileCreatedAt: new Date('1970-02-11').toISOString() }),
utils.createAsset(timeBucketUser.accessToken, { fileCreatedAt: new Date('1970-02-11').toISOString() }),
utils.createAsset(timeBucketUser.accessToken, { fileCreatedAt: new Date('1970-02-12').toISOString() }),
]);
await utils.deleteAssets(timeBucketUser.accessToken, [user2Assets[4].id]);
});
describe('GET /timeline/buckets', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/timeline/buckets');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should get time buckets by month', async () => {
const { status, body } = await request(app)
.get('/timeline/buckets')
.set('Authorization', `Bearer ${timeBucketUser.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual(
expect.arrayContaining([
{ count: 3, timeBucket: '1970-02-01' },
{ count: 1, timeBucket: '1970-01-01' },
]),
);
});
it('should not allow access for unrelated shared links', async () => {
const sharedLink = await utils.createSharedLink(user.accessToken, {
type: SharedLinkType.Individual,
assetIds: user1Assets.map(({ id }) => id),
});
const { status, body } = await request(app).get('/timeline/buckets').query({ key: sharedLink.key });
expect(status).toBe(400);
expect(body).toEqual(errorDto.noPermission);
});
it('should return error if time bucket is requested with partners asset and archived', async () => {
const req1 = await request(app)
.get('/timeline/buckets')
.set('Authorization', `Bearer ${timeBucketUser.accessToken}`)
.query({ withPartners: true, visibility: AssetVisibility.Archive });
expect(req1.status).toBe(400);
expect(req1.body).toEqual(errorDto.badRequest());
const req2 = await request(app)
.get('/timeline/buckets')
.set('Authorization', `Bearer ${user.accessToken}`)
.query({ withPartners: true, visibility: undefined });
expect(req2.status).toBe(400);
expect(req2.body).toEqual(errorDto.badRequest());
});
it('should return error if time bucket is requested with partners asset and favorite', async () => {
const req1 = await request(app)
.get('/timeline/buckets')
.set('Authorization', `Bearer ${timeBucketUser.accessToken}`)
.query({ withPartners: true, isFavorite: true });
expect(req1.status).toBe(400);
expect(req1.body).toEqual(errorDto.badRequest());
const req2 = await request(app)
.get('/timeline/buckets')
.set('Authorization', `Bearer ${timeBucketUser.accessToken}`)
.query({ withPartners: true, isFavorite: false });
expect(req2.status).toBe(400);
expect(req2.body).toEqual(errorDto.badRequest());
});
it('should return error if time bucket is requested with partners asset and trash', async () => {
const req = await request(app)
.get('/timeline/buckets')
.set('Authorization', `Bearer ${user.accessToken}`)
.query({ withPartners: true, isTrashed: true });
expect(req.status).toBe(400);
expect(req.body).toEqual(errorDto.badRequest());
});
});
describe('GET /timeline/bucket', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/timeline/bucket').query({
timeBucket: '1900-01-01',
});
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should handle 5 digit years', async () => {
const { status, body } = await request(app)
.get('/timeline/bucket')
.query({ timeBucket: '012345-01-01' })
.set('Authorization', `Bearer ${timeBucketUser.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({
city: [],
country: [],
duration: [],
id: [],
visibility: [],
isFavorite: [],
isImage: [],
isTrashed: [],
livePhotoVideoId: [],
fileCreatedAt: [],
localOffsetHours: [],
ownerId: [],
projectionType: [],
ratio: [],
status: [],
thumbhash: [],
});
});
// TODO enable date string validation while still accepting 5 digit years
// it('should fail if time bucket is invalid', async () => {
// const { status, body } = await request(app)
// .get('/timeline/bucket')
// .set('Authorization', `Bearer ${user.accessToken}`)
// .query({ timeBucket: 'foo' });
// expect(status).toBe(400);
// expect(body).toEqual(errorDto.badRequest);
// });
it('should return time bucket', async () => {
const { status, body } = await request(app)
.get('/timeline/bucket')
.set('Authorization', `Bearer ${timeBucketUser.accessToken}`)
.query({ timeBucket: '1970-02-10' });
expect(status).toBe(200);
expect(body).toEqual({
city: [],
country: [],
duration: [],
id: [],
visibility: [],
isFavorite: [],
isImage: [],
isTrashed: [],
livePhotoVideoId: [],
fileCreatedAt: [],
localOffsetHours: [],
ownerId: [],
projectionType: [],
ratio: [],
status: [],
thumbhash: [],
});
});
it('should return time bucket in trash', async () => {
const { status, body } = await request(app)
.get('/timeline/bucket')
.set('Authorization', `Bearer ${timeBucketUser.accessToken}`)
.query({ timeBucket: '1970-02-01', isTrashed: true });
expect(status).toBe(200);
const timeBucket: TimeBucketAssetResponseDto = body;
expect(timeBucket.isTrashed).toEqual([true]);
});
});
});

View File

@@ -97,7 +97,7 @@ describe(`immich upload`, () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await utils.resetDatabase(['asset', 'album']); await utils.resetDatabase(['assets', 'albums']);
}); });
describe(`immich upload /path/to/file.jpg`, () => { describe(`immich upload /path/to/file.jpg`, () => {

View File

@@ -1,178 +0,0 @@
#!/usr/bin/env node
/**
* Script to generate test images with additional EXIF date tags
* This creates actual JPEG images with embedded metadata for testing
* Images are generated into e2e/test-assets/metadata/dates/
*/
import { execSync } from 'node:child_process';
import { writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import sharp from 'sharp';
interface TestImage {
filename: string;
description: string;
exifTags: Record<string, string>;
}
const testImages: TestImage[] = [
{
filename: 'time-created.jpg',
description: 'Image with TimeCreated tag',
exifTags: {
TimeCreated: '2023:11:15 14:30:00',
Make: 'Canon',
Model: 'EOS R5',
},
},
{
filename: 'gps-datetime.jpg',
description: 'Image with GPSDateTime and coordinates',
exifTags: {
GPSDateTime: '2023:11:15 12:30:00Z',
GPSLatitude: '37.7749',
GPSLongitude: '-122.4194',
GPSLatitudeRef: 'N',
GPSLongitudeRef: 'W',
},
},
{
filename: 'datetime-utc.jpg',
description: 'Image with DateTimeUTC tag',
exifTags: {
DateTimeUTC: '2023:11:15 10:30:00',
Make: 'Nikon',
Model: 'D850',
},
},
{
filename: 'gps-datestamp.jpg',
description: 'Image with GPSDateStamp and GPSTimeStamp',
exifTags: {
GPSDateStamp: '2023:11:15',
GPSTimeStamp: '08:30:00',
GPSLatitude: '51.5074',
GPSLongitude: '-0.1278',
GPSLatitudeRef: 'N',
GPSLongitudeRef: 'W',
},
},
{
filename: 'sony-datetime2.jpg',
description: 'Sony camera image with SonyDateTime2 tag',
exifTags: {
SonyDateTime2: '2023:11:15 06:30:00',
Make: 'SONY',
Model: 'ILCE-7RM5',
},
},
{
filename: 'date-priority-test.jpg',
description: 'Image with multiple date tags to test priority',
exifTags: {
SubSecDateTimeOriginal: '2023:01:01 01:00:00',
DateTimeOriginal: '2023:02:02 02:00:00',
SubSecCreateDate: '2023:03:03 03:00:00',
CreateDate: '2023:04:04 04:00:00',
CreationDate: '2023:05:05 05:00:00',
DateTimeCreated: '2023:06:06 06:00:00',
TimeCreated: '2023:07:07 07:00:00',
GPSDateTime: '2023:08:08 08:00:00',
DateTimeUTC: '2023:09:09 09:00:00',
GPSDateStamp: '2023:10:10',
SonyDateTime2: '2023:11:11 11:00:00',
},
},
{
filename: 'new-tags-only.jpg',
description: 'Image with only additional date tags (no standard tags)',
exifTags: {
TimeCreated: '2023:12:01 15:45:30',
GPSDateTime: '2023:12:01 13:45:30Z',
DateTimeUTC: '2023:12:01 13:45:30',
GPSDateStamp: '2023:12:01',
SonyDateTime2: '2023:12:01 08:45:30',
GPSLatitude: '40.7128',
GPSLongitude: '-74.0060',
GPSLatitudeRef: 'N',
GPSLongitudeRef: 'W',
},
},
];
const generateTestImages = async (): Promise<void> => {
// Target directory: e2e/test-assets/metadata/dates/
// Current file is in: e2e/src/
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const targetDir = join(__dirname, '..', 'test-assets', 'metadata', 'dates');
console.log('Generating test images with additional EXIF date tags...');
console.log(`Target directory: ${targetDir}`);
for (const image of testImages) {
try {
const imagePath = join(targetDir, image.filename);
// Create unique JPEG file using Sharp
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
const jpegData = await sharp({
create: {
width: 100,
height: 100,
channels: 3,
background: { r, g, b },
},
})
.jpeg({ quality: 90 })
.toBuffer();
writeFileSync(imagePath, jpegData);
// Build exiftool command to add EXIF data
const exifArgs = Object.entries(image.exifTags)
.map(([tag, value]) => `-${tag}="${value}"`)
.join(' ');
const command = `exiftool ${exifArgs} -overwrite_original "${imagePath}"`;
console.log(`Creating ${image.filename}: ${image.description}`);
execSync(command, { stdio: 'pipe' });
// Verify the tags were written
const verifyCommand = `exiftool -json "${imagePath}"`;
const result = execSync(verifyCommand, { encoding: 'utf8' });
const metadata = JSON.parse(result)[0];
console.log(` ✓ Created with ${Object.keys(image.exifTags).length} EXIF tags`);
// Log first date tag found for verification
const firstDateTag = Object.keys(image.exifTags).find(
(tag) => tag.includes('Date') || tag.includes('Time') || tag.includes('Created'),
);
if (firstDateTag && metadata[firstDateTag]) {
console.log(` ✓ Verified ${firstDateTag}: ${metadata[firstDateTag]}`);
}
} catch (error) {
console.error(`Failed to create ${image.filename}:`, (error as Error).message);
}
}
console.log('\nTest image generation complete!');
console.log('Files created in:', targetDir);
console.log('\nTo test these images:');
console.log(`cd ${targetDir} && exiftool -time:all -gps:all *.jpg`);
};
export { generateTestImages };
// Run the generator if this file is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
generateTestImages().catch(console.error);
}

View File

@@ -116,7 +116,6 @@ export const deviceDto = {
createdAt: expect.any(String), createdAt: expect.any(String),
updatedAt: expect.any(String), updatedAt: expect.any(String),
current: true, current: true,
isPendingSyncReset: false,
deviceOS: '', deviceOS: '',
deviceType: '', deviceType: '',
}, },

View File

@@ -12,7 +12,6 @@ export enum OAuthUser {
NO_NAME = 'no-name', NO_NAME = 'no-name',
WITH_QUOTA = 'with-quota', WITH_QUOTA = 'with-quota',
WITH_USERNAME = 'with-username', WITH_USERNAME = 'with-username',
WITH_ROLE = 'with-role',
} }
const claims = [ const claims = [
@@ -35,12 +34,6 @@ const claims = [
preferred_username: 'user-quota', preferred_username: 'user-quota',
immich_quota: 25, immich_quota: 25,
}, },
{
sub: OAuthUser.WITH_ROLE,
email: 'oauth-with-role@immich.app',
email_verified: true,
immich_role: 'admin',
},
]; ];
const withDefaultClaims = (sub: string) => ({ const withDefaultClaims = (sub: string) => ({
@@ -71,15 +64,7 @@ const setup = async () => {
claims: { claims: {
openid: ['sub'], openid: ['sub'],
email: ['email', 'email_verified'], email: ['email', 'email_verified'],
profile: [ profile: ['name', 'given_name', 'family_name', 'preferred_username', 'immich_quota', 'immich_username'],
'name',
'given_name',
'family_name',
'preferred_username',
'immich_quota',
'immich_username',
'immich_role',
],
}, },
features: { features: {
jwtUserinfo: { jwtUserinfo: {

View File

@@ -60,7 +60,6 @@ import { io, type Socket } from 'socket.io-client';
import { loginDto, signupDto } from 'src/fixtures'; import { loginDto, signupDto } from 'src/fixtures';
import { makeRandomImage } from 'src/generators'; import { makeRandomImage } from 'src/generators';
import request from 'supertest'; import request from 'supertest';
export type { Emitter } from '@socket.io/component-emitter';
type CommandResponse = { stdout: string; stderr: string; exitCode: number | null }; type CommandResponse = { stdout: string; stderr: string; exitCode: number | null };
type EventType = 'assetUpload' | 'assetUpdate' | 'assetDelete' | 'userDelete' | 'assetHidden'; type EventType = 'assetUpload' | 'assetUpdate' | 'assetDelete' | 'userDelete' | 'assetHidden';
@@ -85,10 +84,10 @@ export const immichAdmin = (args: string[]) =>
export const specialCharStrings = ["'", '"', ',', '{', '}', '*']; export const specialCharStrings = ["'", '"', ',', '{', '}', '*'];
export const TEN_TIMES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; export const TEN_TIMES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const executeCommand = (command: string, args: string[], options?: { cwd?: string }) => { const executeCommand = (command: string, args: string[]) => {
let _resolve: (value: CommandResponse) => void; let _resolve: (value: CommandResponse) => void;
const promise = new Promise<CommandResponse>((resolve) => (_resolve = resolve)); const promise = new Promise<CommandResponse>((resolve) => (_resolve = resolve));
const child = spawn(command, args, { stdio: 'pipe', cwd: options?.cwd }); const child = spawn(command, args, { stdio: 'pipe' });
let stdout = ''; let stdout = '';
let stderr = ''; let stderr = '';
@@ -154,19 +153,19 @@ export const utils = {
tables = tables || [ tables = tables || [
// TODO e2e test for deleting a stack, since it is quite complex // TODO e2e test for deleting a stack, since it is quite complex
'stack', 'asset_stack',
'library', 'libraries',
'shared_link', 'shared_links',
'person', 'person',
'album', 'albums',
'asset', 'assets',
'asset_face', 'asset_faces',
'activity', 'activity',
'api_key', 'api_keys',
'session', 'sessions',
'user', 'users',
'system_metadata', 'system_metadata',
'tag', 'tags',
]; ];
const sql: string[] = []; const sql: string[] = [];
@@ -175,7 +174,7 @@ export const utils = {
if (table === 'system_metadata') { if (table === 'system_metadata') {
sql.push(`DELETE FROM "system_metadata" where "key" NOT IN ('reverse-geocoding-state', 'system-flags');`); sql.push(`DELETE FROM "system_metadata" where "key" NOT IN ('reverse-geocoding-state', 'system-flags');`);
} else { } else {
sql.push(`DELETE FROM "${table}" CASCADE;`); sql.push(`DELETE FROM ${table} CASCADE;`);
} }
} }
@@ -217,7 +216,11 @@ export const utils = {
websocket websocket
.on('connect', () => resolve(websocket)) .on('connect', () => resolve(websocket))
.on('on_upload_success', (data: AssetResponseDto) => onEvent({ event: 'assetUpload', id: data.id })) .on('on_upload_success', (data: AssetResponseDto) => onEvent({ event: 'assetUpload', id: data.id }))
.on('on_asset_update', (data: AssetResponseDto) => onEvent({ event: 'assetUpdate', id: data.id })) .on('on_asset_update', (assetId: string[]) => {
for (const id of assetId) {
onEvent({ event: 'assetUpdate', id });
}
})
.on('on_asset_hidden', (assetId: string) => onEvent({ event: 'assetHidden', id: assetId })) .on('on_asset_hidden', (assetId: string) => onEvent({ event: 'assetHidden', id: assetId }))
.on('on_asset_delete', (assetId: string) => onEvent({ event: 'assetDelete', id: assetId })) .on('on_asset_delete', (assetId: string) => onEvent({ event: 'assetDelete', id: assetId }))
.on('on_user_delete', (userId: string) => onEvent({ event: 'userDelete', id: userId })) .on('on_user_delete', (userId: string) => onEvent({ event: 'userDelete', id: userId }))
@@ -451,7 +454,7 @@ export const utils = {
return; return;
} }
await client.query('INSERT INTO asset_face ("assetId", "personId") VALUES ($1, $2)', [assetId, personId]); await client.query('INSERT INTO asset_faces ("assetId", "personId") VALUES ($1, $2)', [assetId, personId]);
}, },
setPersonThumbnail: async (personId: string) => { setPersonThumbnail: async (personId: string) => {

View File

@@ -232,6 +232,7 @@
"storage_template_migration_info": "تغييرات القالب ستنطبق فقط على المحتويات الجديدة. لتطبيق القالب على المحتويات التي تم رفعها سابقًا، قم بتشغيل <link>{job}</link>.", "storage_template_migration_info": "تغييرات القالب ستنطبق فقط على المحتويات الجديدة. لتطبيق القالب على المحتويات التي تم رفعها سابقًا، قم بتشغيل <link>{job}</link>.",
"storage_template_migration_job": "وظيفة تهجير قالب التخزين", "storage_template_migration_job": "وظيفة تهجير قالب التخزين",
"storage_template_more_details": "لمزيد من التفاصيل حول هذه الميزة، يرجى الرجوع إلى <template-link>Storage Template</template-link> و<implications-link>implications</implications-link>", "storage_template_more_details": "لمزيد من التفاصيل حول هذه الميزة، يرجى الرجوع إلى <template-link>Storage Template</template-link> و<implications-link>implications</implications-link>",
"storage_template_onboarding_description": "عند تفعيل هذه الميزة، سيقوم بتنظيم الملفات تلقائيًا بناءً على قالب محدد من قبل المستخدم. بسبب مشاكل الاستقرار، تم تعطيل الميزة افتراضيًا. للمزيد من المعلومات، يرجى الرجوع إلى <link>الوثائق</link>.",
"storage_template_path_length": "الحد التقريبي لطول المسار: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "الحد التقريبي لطول المسار: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "قالب التخزين", "storage_template_settings": "قالب التخزين",
"storage_template_settings_description": "إدارة هيكل المجلد واسم الملف للأصول المرفوعة", "storage_template_settings_description": "إدارة هيكل المجلد واسم الملف للأصول المرفوعة",

View File

@@ -34,7 +34,6 @@
"added_to_favorites_count": "Добавени {count, number} към любими", "added_to_favorites_count": "Добавени {count, number} към любими",
"admin": { "admin": {
"add_exclusion_pattern_description": "Добави модели за изключване. Поддържа се \"globbing\" с помощта на *, ** и ?. За да игнорирате всички файлове в директория с име \"Raw\", използвайте \"**/Raw/**\". За да игнорирате всички файлове, завършващи на \".tif\", използвайте \"**/*.tif\". За да игнорирате абсолютен път, използвайте \"/path/to/ignore/**\".", "add_exclusion_pattern_description": "Добави модели за изключване. Поддържа се \"globbing\" с помощта на *, ** и ?. За да игнорирате всички файлове в директория с име \"Raw\", използвайте \"**/Raw/**\". За да игнорирате всички файлове, завършващи на \".tif\", използвайте \"**/*.tif\". За да игнорирате абсолютен път, използвайте \"/path/to/ignore/**\".",
"admin_user": "Администратор",
"asset_offline_description": "Този външен библиотечен елемент не може да бъде открит на диска и е преместен в кошчето за боклук. Ако файлът е преместен в библиотеката, проверете вашата история за нов съответстващ елемент. За да възстановите елемента, моля проверете дали файловият път отдолу може да бъде достъпен от Immich и сканирайте библиотеката.", "asset_offline_description": "Този външен библиотечен елемент не може да бъде открит на диска и е преместен в кошчето за боклук. Ако файлът е преместен в библиотеката, проверете вашата история за нов съответстващ елемент. За да възстановите елемента, моля проверете дали файловият път отдолу може да бъде достъпен от Immich и сканирайте библиотеката.",
"authentication_settings": "Настройки за удостоверяване", "authentication_settings": "Настройки за удостоверяване",
"authentication_settings_description": "Управление на парола, OAuth и други настройки за удостоверяване", "authentication_settings_description": "Управление на парола, OAuth и други настройки за удостоверяване",
@@ -53,7 +52,7 @@
"confirm_email_below": "За потвърждение, моля въведете \"{email}\" отдолу", "confirm_email_below": "За потвърждение, моля въведете \"{email}\" отдолу",
"confirm_reprocess_all_faces": "Сигурни ли сте, че искате да се обработят лицата отново? Това ще изчисти всички именувани хора.", "confirm_reprocess_all_faces": "Сигурни ли сте, че искате да се обработят лицата отново? Това ще изчисти всички именувани хора.",
"confirm_user_password_reset": "Сигурни ли сте, че искате да нулирате паролата на {user}?", "confirm_user_password_reset": "Сигурни ли сте, че искате да нулирате паролата на {user}?",
"confirm_user_pin_code_reset": "Наистина ли искате да смените PIN кода на потребителя {user}?", "confirm_user_pin_code_reset": "Наистина ли искаш да смениш PIN-кода на потребителя {user}?",
"create_job": "Създайте задача", "create_job": "Създайте задача",
"cron_expression": "Cron израз", "cron_expression": "Cron израз",
"cron_expression_description": "Настрой интервала на сканиране използвайки cron формата. За повече информация <link>Crontab Guru</link>", "cron_expression_description": "Настрой интервала на сканиране използвайки cron формата. За повече информация <link>Crontab Guru</link>",
@@ -171,7 +170,7 @@
"note_apply_storage_label_previous_assets": "Забележка: За да приложите етикета за съхранение към предварително качени файлове, стартирайте", "note_apply_storage_label_previous_assets": "Забележка: За да приложите етикета за съхранение към предварително качени файлове, стартирайте",
"note_cannot_be_changed_later": "ВНИМАНИЕ: Това не може да бъде променено по-късно!", "note_cannot_be_changed_later": "ВНИМАНИЕ: Това не може да бъде променено по-късно!",
"notification_email_from_address": "От адрес", "notification_email_from_address": "От адрес",
"notification_email_from_address_description": "Електронна поща на изпращача, например: \"Immich Photo Server <noreply@example.com>\". Използвайте адрес, от който може да изпращате имейли.", "notification_email_from_address_description": "Електронна поща на изпращача, например: \"Immich Photo Server <noreply@example.com>\". Използвай адрес, от който може да изпращаш имейли.",
"notification_email_host_description": "Хост на сървъра за електронна поща (например: smtp.immich.app)", "notification_email_host_description": "Хост на сървъра за електронна поща (например: smtp.immich.app)",
"notification_email_ignore_certificate_errors": "Игнорирайте сертификационни грешки", "notification_email_ignore_certificate_errors": "Игнорирайте сертификационни грешки",
"notification_email_ignore_certificate_errors_description": "Игнорирай грешки свързани с валидация на TLS сертификат (не се препоръчва)", "notification_email_ignore_certificate_errors_description": "Игнорирай грешки свързани с валидация на TLS сертификат (не се препоръчва)",
@@ -180,7 +179,7 @@
"notification_email_sent_test_email_button": "Изпрати тестов имейл и запази", "notification_email_sent_test_email_button": "Изпрати тестов имейл и запази",
"notification_email_setting_description": "Настройки за изпращане на имейл известия", "notification_email_setting_description": "Настройки за изпращане на имейл известия",
"notification_email_test_email": "Изпрати тестов имейл", "notification_email_test_email": "Изпрати тестов имейл",
"notification_email_test_email_failed": "Неуспешно изпращане на тестов имейл, проверете настройките", "notification_email_test_email_failed": "Неуспешно изпращане на тестов имейл, провери променливите",
"notification_email_test_email_sent": "Тестов имейл беше изпратен на {email}. Проверете входящата си пощa.", "notification_email_test_email_sent": "Тестов имейл беше изпратен на {email}. Проверете входящата си пощa.",
"notification_email_username_description": "Потребителско име за удостоверяване пред имейл сървъра", "notification_email_username_description": "Потребителско име за удостоверяване пред имейл сървъра",
"notification_enable_email_notifications": "Включване на имейл известията", "notification_enable_email_notifications": "Включване на имейл известията",
@@ -204,7 +203,7 @@
"oauth_storage_quota_claim": "Заявка за квота за съхранение", "oauth_storage_quota_claim": "Заявка за квота за съхранение",
"oauth_storage_quota_claim_description": "Автоматично задайте квотата за съхранение на потребителя със стойността от тази заявка.", "oauth_storage_quota_claim_description": "Автоматично задайте квотата за съхранение на потребителя със стойността от тази заявка.",
"oauth_storage_quota_default": "Стандартна квота за съхранение (GiB)", "oauth_storage_quota_default": "Стандартна квота за съхранение (GiB)",
"oauth_storage_quota_default_description": "Квота в GiB, която да се използва, когато не е посочено друго.", "oauth_storage_quota_default_description": "Квота в GiB, която да се използва, когато не е предоставена заявка (Въведете 0 за неограничена квота).",
"oauth_timeout": "Време на изчакване при заявка", "oauth_timeout": "Време на изчакване при заявка",
"oauth_timeout_description": "Време за изчакване на отговор на заявка, в милисекунди", "oauth_timeout_description": "Време за изчакване на отговор на заявка, в милисекунди",
"password_enable_description": "Влизане с имейл и парола", "password_enable_description": "Влизане с имейл и парола",
@@ -244,7 +243,7 @@
"storage_template_migration_info": "Шаблона ще преобразува всички разширения на имената на файловете в долен регистър. Промените в шаблоните ще се прилагат само за нови елементи. За да приложите принудително шаблона към вече качени елементи, изпълнете <link>{job}</link>.", "storage_template_migration_info": "Шаблона ще преобразува всички разширения на имената на файловете в долен регистър. Промените в шаблоните ще се прилагат само за нови елементи. За да приложите принудително шаблона към вече качени елементи, изпълнете <link>{job}</link>.",
"storage_template_migration_job": "Задача за миграция на шаблона за съхранение", "storage_template_migration_job": "Задача за миграция на шаблона за съхранение",
"storage_template_more_details": "За повече подробности относно тази функция се обърнете към шаблона <template-link>Storage Template</template-link> и неговите <implications-link> последствия </implications-link>", "storage_template_more_details": "За повече подробности относно тази функция се обърнете към шаблона <template-link>Storage Template</template-link> и неговите <implications-link> последствия </implications-link>",
"storage_template_onboarding_description_v2": "Когато е разрешена, тази функция ще организира автоматично файловете, според шаблон, дефиниран от потребителя. За допълнителна информация, моля вижте <link>документацията</link>.", "storage_template_onboarding_description": "Когато е активирана, тази функция ще организира автоматично файлове въз основа на дефиниран от потребителя шаблон. Поради проблеми със стабилността, функцията е изключена по подразбиране. За повече информация, моля, вижте <link>документацията</link>.",
"storage_template_path_length": "Ограничение на дължината на пътя: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "Ограничение на дължината на пътя: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "Шаблон за съхранение", "storage_template_settings": "Шаблон за съхранение",
"storage_template_settings_description": "Управление на структурата на папките и името на файла за качване", "storage_template_settings_description": "Управление на структурата на папките и името на файла за качване",
@@ -357,7 +356,7 @@
"admin_password": "Администраторска парола", "admin_password": "Администраторска парола",
"administration": "Администрация", "administration": "Администрация",
"advanced": "Разширено", "advanced": "Разширено",
"advanced_settings_enable_alternate_media_filter_subtitle": "При синхронизация, използвайте тази опция като филтър, основан на промяна на даден критерии. Опитайте само в случай, че приложението има проблем с откриване на всички албуми.", "advanced_settings_enable_alternate_media_filter_subtitle": "При синхронизация, използвай тази опция като филтър, основан на промяна на даден критерии. Опитай само в случай, че приложението има проблем с откриване на всички албуми.",
"advanced_settings_enable_alternate_media_filter_title": "[ЕКСПЕРИМЕНТАЛНО] Използвай филтъра на алтернативното устройство за синхронизация на албуми", "advanced_settings_enable_alternate_media_filter_title": "[ЕКСПЕРИМЕНТАЛНО] Използвай филтъра на алтернативното устройство за синхронизация на албуми",
"advanced_settings_log_level_title": "Ниво на запис в дневника: {level}", "advanced_settings_log_level_title": "Ниво на запис в дневника: {level}",
"advanced_settings_prefer_remote_subtitle": "Някои устройства са твърде бавни за да генерират миниатюри. Активирай тази опция за да се зареждат винаги от сървъра.", "advanced_settings_prefer_remote_subtitle": "Някои устройства са твърде бавни за да генерират миниатюри. Активирай тази опция за да се зареждат винаги от сървъра.",
@@ -393,7 +392,7 @@
"album_updated_setting_description": "Получавайте известие по имейл, когато споделен албум има нови файлове", "album_updated_setting_description": "Получавайте известие по имейл, когато споделен албум има нови файлове",
"album_user_left": "Напусна {album}", "album_user_left": "Напусна {album}",
"album_user_removed": "Премахнат {user}", "album_user_removed": "Премахнат {user}",
"album_viewer_appbar_delete_confirm": "Сигурни ли сте, че искате да изтриете този албум от своя профил?", "album_viewer_appbar_delete_confirm": "Сигурен ли си, че искаш да изтриеш този албум от своя профил?",
"album_viewer_appbar_share_err_delete": "Неуспешно изтриване на албум", "album_viewer_appbar_share_err_delete": "Неуспешно изтриване на албум",
"album_viewer_appbar_share_err_leave": "Неуспешно напускане на албум", "album_viewer_appbar_share_err_leave": "Неуспешно напускане на албум",
"album_viewer_appbar_share_err_remove": "Проблем при пермахване на обекти от албума", "album_viewer_appbar_share_err_remove": "Проблем при пермахване на обекти от албума",
@@ -465,12 +464,9 @@
"assets_added_count": "Добавено {count, plural, one {# asset} other {# assets}}", "assets_added_count": "Добавено {count, plural, one {# asset} other {# assets}}",
"assets_added_to_album_count": "Добавен(и) са {count, plural, one {# актив} other {# актива}} в албума", "assets_added_to_album_count": "Добавен(и) са {count, plural, one {# актив} other {# актива}} в албума",
"assets_added_to_name_count": "Добавен(и) са {count, plural, one {# актив} other {# актива}} към {hasName, select, true {<b>{name}</b>} other {нов албум}}", "assets_added_to_name_count": "Добавен(и) са {count, plural, one {# актив} other {# актива}} към {hasName, select, true {<b>{name}</b>} other {нов албум}}",
"assets_cannot_be_added_to_album_count": "{count, plural, one {Обекта не може да се добави} other {Обектите не може да се добавят}} в албума",
"assets_count": "{count, plural, one {# актив} other {# актива}}", "assets_count": "{count, plural, one {# актив} other {# актива}}",
"assets_deleted_permanently": "{count} обекта са изтрити завинаги", "assets_deleted_permanently": "{count} обекта са изтрити завинаги",
"assets_deleted_permanently_from_server": "{count} обекта са изтити от Immich сървъра завинаги", "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_moved_to_trash_count": "Преместен(и) са {count, plural, one {# актив} other {# актива}} в кошчето",
"assets_permanently_deleted_count": "Постоянно изтрит(и) са {count, plural, one {# актив} other {# актива}}", "assets_permanently_deleted_count": "Постоянно изтрит(и) са {count, plural, one {# актив} other {# актива}}",
"assets_removed_count": "Премахнат(и) са {count, plural, one {# актив} other {# актива}}", "assets_removed_count": "Премахнат(и) са {count, plural, one {# актив} other {# актива}}",
@@ -509,7 +505,7 @@
"backup_controller_page_background_app_refresh_disabled_title": "Фоново обновяване е изключено", "backup_controller_page_background_app_refresh_disabled_title": "Фоново обновяване е изключено",
"backup_controller_page_background_app_refresh_enable_button_text": "Иди в настройки", "backup_controller_page_background_app_refresh_enable_button_text": "Иди в настройки",
"backup_controller_page_background_battery_info_link": "Покажи ми как", "backup_controller_page_background_battery_info_link": "Покажи ми как",
"backup_controller_page_background_battery_info_message": "За успешно архивиране във фонов режим, моля изключете оптимизациите на батерията, ограничаващи фоновата активност на Immich.\n\nТази настройка е според устройството, моля потърсете информация според производителя на устройството.", "backup_controller_page_background_battery_info_message": "За успешно архивиране във фонов режим, моля изключи оптимизациите на батерията, ограничаващи фоновата активност на Immich.\n\nТази настройка е според устройството, моля потърси информация според производителя на устройството.",
"backup_controller_page_background_battery_info_ok": "Ок", "backup_controller_page_background_battery_info_ok": "Ок",
"backup_controller_page_background_battery_info_title": "Оптимизация на батерията", "backup_controller_page_background_battery_info_title": "Оптимизация на батерията",
"backup_controller_page_background_charging": "Само при зареждане", "backup_controller_page_background_charging": "Само при зареждане",
@@ -590,8 +586,8 @@
"cannot_merge_people": "Не може да обединява хора", "cannot_merge_people": "Не може да обединява хора",
"cannot_undo_this_action": "Не можете да отмените това действие!", "cannot_undo_this_action": "Не можете да отмените това действие!",
"cannot_update_the_description": "Описанието не може да бъде актуализирано", "cannot_update_the_description": "Описанието не може да бъде актуализирано",
"cast": оточно предаване", "cast": ромяна на регистъра",
"cast_description": "Настройка на наличните цели за предаване", "cast_description": "Настройка на наличните цели за промяна на регистъра",
"change_date": "Промени датата", "change_date": "Промени датата",
"change_description": "Промени описанието", "change_description": "Промени описанието",
"change_display_order": "Промени реда на показване", "change_display_order": "Промени реда на показване",
@@ -602,11 +598,11 @@
"change_password": "Промени паролата", "change_password": "Промени паролата",
"change_password_description": "Това е или първият път, когато влизате в системата, или е направена заявка за промяна на паролата ви. Моля, въведете новата парола по-долу.", "change_password_description": "Това е или първият път, когато влизате в системата, или е направена заявка за промяна на паролата ви. Моля, въведете новата парола по-долу.",
"change_password_form_confirm_password": "Потвърди паролата", "change_password_form_confirm_password": "Потвърди паролата",
"change_password_form_description": "Здравейте {name},\n\nТова или е първото ви вписване в системата или има подадена заявка за смяна на паролата. Моля, въведете нова парола в полето по-долу.", "change_password_form_description": "Здравей {name},\n\nТова или е първото ти вписване в системата или има подадена заявка за смяна на паролата. Моля, въведи нова парола в полето по-долу.",
"change_password_form_new_password": "Нова парола", "change_password_form_new_password": "Нова парола",
"change_password_form_password_mismatch": "Паролите не съвпадат", "change_password_form_password_mismatch": "Паролите не съвпадат",
"change_password_form_reenter_new_password": "Повтори новата парола", "change_password_form_reenter_new_password": "Повтори новата парола",
"change_pin_code": "Смени PIN кода", "change_pin_code": "Смени PIN-кода",
"change_your_password": "Променете паролата си", "change_your_password": "Променете паролата си",
"changed_visibility_successfully": "Видимостта е променена успешно", "changed_visibility_successfully": "Видимостта е променена успешно",
"check_corrupt_asset_backup": "Провери за повредени архивни копия", "check_corrupt_asset_backup": "Провери за повредени архивни копия",
@@ -639,17 +635,17 @@
"comments_and_likes": "Коментари и харесвания", "comments_and_likes": "Коментари и харесвания",
"comments_are_disabled": "Коментарите са деактивирани", "comments_are_disabled": "Коментарите са деактивирани",
"common_create_new_album": "Създай нов албум", "common_create_new_album": "Създай нов албум",
"common_server_error": "Моля, проверете мрежовата връзка, убедете се, че сървъра е достъпен и версиите на сървъра и приложението са съвместими.", "common_server_error": "Моля, провери мрежовата връзка, убеди се, че сървъра е достъпен и версиите на сървъра и приложението са съвместими.",
"completed": "Завършено", "completed": "Завършено",
"confirm": "Потвърди", "confirm": "Потвърди",
"confirm_admin_password": "Потвърждаване на паролата на администратора", "confirm_admin_password": "Потвърждаване на паролата на администратора",
"confirm_delete_face": "Сигурни ли сте, че искате да изтриете лицето на {name} от актива?", "confirm_delete_face": "Сигурни ли сте, че искате да изтриете лицето на {name} от актива?",
"confirm_delete_shared_link": "Сигурни ли сте, че искате да изтриете тази споделена връзка?", "confirm_delete_shared_link": "Сигурни ли сте, че искате да изтриете тази споделена връзка?",
"confirm_keep_this_delete_others": "Всички останали файлове в стека ще бъдат изтрити, с изключение на този файл. Сигурни ли сте, че искате да продължите?", "confirm_keep_this_delete_others": "Всички останали файлове в стека ще бъдат изтрити, с изключение на този файл. Сигурни ли сте, че искате да продължите?",
"confirm_new_pin_code": "Потвърди новия PIN код", "confirm_new_pin_code": "Потвърди новия PIN-код",
"confirm_password": "Потвърдете паролата", "confirm_password": "Потвърдете паролата",
"confirm_tag_face": "Искате ли да отбележите това лице като {name}?", "confirm_tag_face": "Искаш ли да отбележиш това лице като {name}?",
"confirm_tag_face_unnamed": "Искате ли да отбележите това лице?", "confirm_tag_face_unnamed": "Искаш ли да отбележиш това лице?",
"connected_device": "Свързано устройство", "connected_device": "Свързано устройство",
"connected_to": "Свързан към", "connected_to": "Свързан към",
"contain": "В рамките на", "contain": "В рамките на",
@@ -696,7 +692,7 @@
"crop": "Изрежи", "crop": "Изрежи",
"curated_object_page_title": "Неща", "curated_object_page_title": "Неща",
"current_device": "Текущо устройство", "current_device": "Текущо устройство",
"current_pin_code": "Сегашен PIN код", "current_pin_code": "Сегашен PIN-код",
"current_server_address": "Настоящ адрес на сървъра", "current_server_address": "Настоящ адрес на сървъра",
"custom_locale": "Персонализиран локал", "custom_locale": "Персонализиран локал",
"custom_locale_description": "Форматиране на дати и числа в зависимост от езика и региона", "custom_locale_description": "Форматиране на дати и числа в зависимост от езика и региона",
@@ -717,7 +713,7 @@
"deduplication_info": "Информация за дедупликацията", "deduplication_info": "Информация за дедупликацията",
"deduplication_info_description": "За автоматично предварително избиране на ресурси и премахване на дубликати на едро, разглеждаме:", "deduplication_info_description": "За автоматично предварително избиране на ресурси и премахване на дубликати на едро, разглеждаме:",
"default_locale": "Локализация по подразбиране", "default_locale": "Локализация по подразбиране",
"default_locale_description": "Форматиране на дати и числа в зависимост от езиковата настройка на браузъра", "default_locale_description": "Форматиране на дати и числа в зависимост от местоположението на браузъра",
"delete": "Изтрий", "delete": "Изтрий",
"delete_album": "Изтрий албум", "delete_album": "Изтрий албум",
"delete_api_key_prompt": "Сигурни ли сте, че искате да изтриете този API ключ?", "delete_api_key_prompt": "Сигурни ли сте, че искате да изтриете този API ключ?",
@@ -817,13 +813,13 @@
"empty_trash": "Изпразване на кош", "empty_trash": "Изпразване на кош",
"empty_trash_confirmation": "Сигурни ли сте, че искате да изпразните кошчето? Това ще премахне всичко в кошчето за постоянно от Immich.\nНе можете да отмените това действие!", "empty_trash_confirmation": "Сигурни ли сте, че искате да изпразните кошчето? Това ще премахне всичко в кошчето за постоянно от Immich.\nНе можете да отмените това действие!",
"enable": "Включване", "enable": "Включване",
"enable_biometric_auth_description": "Въведете вашия PIN код, за да разрешите биометрично удостоверяване", "enable_biometric_auth_description": "Въведи своя ПИН-код, за да разрешиш биометрично удостоверяване",
"enabled": "Включено", "enabled": "Включено",
"end_date": "Крайна дата", "end_date": "Крайна дата",
"enqueued": "Наредено в опашката", "enqueued": "Наредено в опашката",
"enter_wifi_name": "Въведи име на Wi-Fi", "enter_wifi_name": "Въведи име на Wi-Fi",
"enter_your_pin_code": "Въведете вашия PIN код", "enter_your_pin_code": "Въведи твоя PIN-код",
"enter_your_pin_code_subtitle": "Въвеждане на PIN код, за достъп до заключена папка", "enter_your_pin_code_subtitle": "Въведи твоя PIN-код, за да достъпиш заключена папка",
"error": "Грешка", "error": "Грешка",
"error_change_sort_album": "Неуспешна промяна на реда на сортиране на албум", "error_change_sort_album": "Неуспешна промяна на реда на сортиране на албум",
"error_delete_face": "Грешка при изтриване на лице от актива", "error_delete_face": "Грешка при изтриване на лице от актива",
@@ -923,7 +919,7 @@
"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": "Неуспешно нулиране на PIN кода", "unable_to_reset_pin_code": "Неуспешно нулиране на PIN-кода",
"unable_to_resolve_duplicate": "Неуспешно справяне с дублирането", "unable_to_resolve_duplicate": "Неуспешно справяне с дублирането",
"unable_to_restore_assets": "Неуспешно възстановяване на елементи", "unable_to_restore_assets": "Неуспешно възстановяване на елементи",
"unable_to_restore_trash": "Неуспешно възстановяване от кошчето", "unable_to_restore_trash": "Неуспешно възстановяване от кошчето",
@@ -1008,7 +1004,7 @@
"gcast_enabled_description": "За да работи тази функция зарежда външни ресурси от Google.", "gcast_enabled_description": "За да работи тази функция зарежда външни ресурси от Google.",
"general": "Общи", "general": "Общи",
"get_help": "Помощ", "get_help": "Помощ",
"get_wifiname_error": "Неуспешно получаване името на Wi-Fi мрежата. Моля, убедете се, че са предоставени нужните разрешения на приложението и има връзка с Wi-Fi", "get_wifiname_error": "Неуспешно получаване името на Wi-Fi мрежата. Моля, убеди се, че са предоставени нужните разрешения на приложението и има връзка с Wi-Fi",
"getting_started": "Как да започнем", "getting_started": "Как да започнем",
"go_back": "Връщане назад", "go_back": "Връщане назад",
"go_to_folder": "Отиди в папката", "go_to_folder": "Отиди в папката",
@@ -1047,7 +1043,7 @@
"home_page_delete_remote_err_local": "Локални обекти не могат да се изтриват от сървъра, пропускане", "home_page_delete_remote_err_local": "Локални обекти не могат да се изтриват от сървъра, пропускане",
"home_page_favorite_err_local": "Локални обекти все още не могат да се правят любими, пропускане", "home_page_favorite_err_local": "Локални обекти все още не могат да се правят любими, пропускане",
"home_page_favorite_err_partner": "Партньорски обекти все още не могат да се правят любими, пропускане", "home_page_favorite_err_partner": "Партньорски обекти все още не могат да се правят любими, пропускане",
"home_page_first_time_notice": "Ако за първи път използвате приложението, моля изберете албум за архивиране, за да може обектите от времевата линия да се записват в него", "home_page_first_time_notice": "Ако за първи път използваш приложението, моля избери албум за архивиране, за да може обектите от времевата линия да се записват в него",
"home_page_locked_error_local": "Локални обекти не могат да се преместят в заключена папка, пропускане", "home_page_locked_error_local": "Локални обекти не могат да се преместят в заключена папка, пропускане",
"home_page_locked_error_partner": "Партньорски обекти не могат да се преместят в заключена папка, пропускане", "home_page_locked_error_partner": "Партньорски обекти не могат да се преместят в заключена папка, пропускане",
"home_page_share_err_local": "Локални обекти не могат да се споделят чрез връзка, пропускане", "home_page_share_err_local": "Локални обекти не могат да се споделят чрез връзка, пропускане",
@@ -1098,7 +1094,6 @@
"ios_debug_info_last_sync_at": "Синхронизирано на {dateTime}", "ios_debug_info_last_sync_at": "Синхронизирано на {dateTime}",
"ios_debug_info_no_processes_queued": "Няма фонови процеси в опашката", "ios_debug_info_no_processes_queued": "Няма фонови процеси в опашката",
"ios_debug_info_no_sync_yet": "Все още не е изпълнявана задача за фонова синхронизация", "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}", "ios_debug_info_processing_ran_at": "Започната обработка на {dateTime}",
"items_count": "{count, plural, one {# елемент} other {# елементи}}", "items_count": "{count, plural, one {# елемент} other {# елементи}}",
"jobs": "Задачи", "jobs": "Задачи",
@@ -1108,7 +1103,7 @@
"kept_this_deleted_others": "Запази този елемент и другите изтрити {count, plural, one {# елемент} other {# елемента}}", "kept_this_deleted_others": "Запази този елемент и другите изтрити {count, plural, one {# елемент} other {# елемента}}",
"keyboard_shortcuts": "Бързи клавишни комбинации", "keyboard_shortcuts": "Бързи клавишни комбинации",
"language": "Език", "language": "Език",
"language_no_results_subtitle": "Опитайте да коригирате термина си за търсене", "language_no_results_subtitle": "Опитай да коригираш термина си за търсене",
"language_no_results_title": "Не са намерени езици", "language_no_results_title": "Не са намерени езици",
"language_search_hint": "Търсене на езици...", "language_search_hint": "Търсене на езици...",
"language_setting_description": "Изберете предпочитан език", "language_setting_description": "Изберете предпочитан език",
@@ -1143,14 +1138,13 @@
"location_permission_content": "За да работи функцията автоматично превключване, Immich се нуждае от разрешение за точно местоположение, за да може да чете името на текущата Wi-Fi мрежа", "location_permission_content": "За да работи функцията автоматично превключване, Immich се нуждае от разрешение за точно местоположение, за да може да чете името на текущата Wi-Fi мрежа",
"location_picker_choose_on_map": "Избери на карта", "location_picker_choose_on_map": "Избери на карта",
"location_picker_latitude_error": "Въведи правилна ширина", "location_picker_latitude_error": "Въведи правилна ширина",
"location_picker_latitude_hint": "Въведете географска ширина тук", "location_picker_latitude_hint": "Въведи ширината тук",
"location_picker_longitude_error": "Въведи правилна дължина", "location_picker_longitude_error": "Въведи правилна дължина",
"location_picker_longitude_hint": "Въведете географска дължина тук", "location_picker_longitude_hint": "Въведи дължината тук",
"lock": "Заключи", "lock": "Заключи",
"locked_folder": "Заключена папка", "locked_folder": "Заключена папка",
"log_out": "Излизане", "log_out": "Излизане",
"log_out_all_devices": "Излизане с всички устройства", "log_out_all_devices": "Излизане с всички устройства",
"logged_in_as": "Вписан като {user}",
"logged_out_all_devices": "Успешно излизане от всички устройства", "logged_out_all_devices": "Успешно излизане от всички устройства",
"logged_out_device": "Успешно излизане от устройство", "logged_out_device": "Успешно излизане от устройство",
"login": "Вписване", "login": "Вписване",
@@ -1167,8 +1161,8 @@
"login_form_err_trailing_whitespace": "Интервал в края", "login_form_err_trailing_whitespace": "Интервал в края",
"login_form_failed_get_oauth_server_config": "Грешка при вписване с OAuth, провери URL на сървъра", "login_form_failed_get_oauth_server_config": "Грешка при вписване с OAuth, провери URL на сървъра",
"login_form_failed_get_oauth_server_disable": "На този сървър OAuth не е достъпна", "login_form_failed_get_oauth_server_disable": "На този сървър OAuth не е достъпна",
"login_form_failed_login": "Грешка при вписване, проверете URL, имейла и паролата", "login_form_failed_login": "Грешка при вписване, провери URL, имейла и паролата",
"login_form_handshake_exception": "Грешка при договаряне на връзката със сървъра. Ако използвате самоподписан сертификат, разрешете в настройкте използване на самоподписан сертификат.", "login_form_handshake_exception": "Грешка при договаряне на връзката със сървъра. Ако използваш самоподписан сертификат, разреши в настройкте използване на самоподписан сертификат.",
"login_form_password_hint": "парола", "login_form_password_hint": "парола",
"login_form_save_login": "Остани вписан", "login_form_save_login": "Остани вписан",
"login_form_server_empty": "Въведи URL на сървъра.", "login_form_server_empty": "Въведи URL на сървъра.",
@@ -1198,12 +1192,12 @@
"map_cannot_get_user_location": "Не можах да получа местоположението", "map_cannot_get_user_location": "Не можах да получа местоположението",
"map_location_dialog_yes": "Да", "map_location_dialog_yes": "Да",
"map_location_picker_page_use_location": "Използвай това местоположение", "map_location_picker_page_use_location": "Използвай това местоположение",
"map_location_service_disabled_content": "За да се показват обектите от текущото място, трябва да бъде включена услугата за местоположение. Искате ли да я включите сега?", "map_location_service_disabled_content": "За да се показват обектите от текущото място, трябва да бъде включена услугата за местоположение. Искаш ли да я включиш сега?",
"map_location_service_disabled_title": "Услугата за местоположение е изключена", "map_location_service_disabled_title": "Услугата за местоположение е изключена",
"map_marker_for_images": "Маркери на картата за снимки направени в {city}, {country}", "map_marker_for_images": "Маркери на картата за снимки направени в {city}, {country}",
"map_marker_with_image": "Маркер на картата с изображение", "map_marker_with_image": "Маркер на картата с изображение",
"map_no_assets_in_bounds": "Няма снимки от този район", "map_no_assets_in_bounds": "Няма снимки от този район",
"map_no_location_permission_content": "За да се показват обектите от текущото място, трябва разрешение за определяне на местоположението. Искате ли да предоставите разрешение сега?", "map_no_location_permission_content": "За да се показват обектите от текущото място, трябва разрешение за определяне на местоположението. Искаш ли да предоставиш разрешение сега?",
"map_no_location_permission_title": "Отказан достъп до местоположение", "map_no_location_permission_title": "Отказан достъп до местоположение",
"map_settings": "Настройки на картата", "map_settings": "Настройки на картата",
"map_settings_dark_mode": "Тъмен режим", "map_settings_dark_mode": "Тъмен режим",
@@ -1248,8 +1242,6 @@
"move_off_locked_folder": "Извади от заключената папка", "move_off_locked_folder": "Извади от заключената папка",
"move_to_locked_folder": "Премести в заключена папка", "move_to_locked_folder": "Премести в заключена папка",
"move_to_locked_folder_confirmation": "Тези снимки и видеа ще бъдат изтрити от всички албуми и ще са достъпни само в заключената папка", "move_to_locked_folder_confirmation": "Тези снимки и видеа ще бъдат изтрити от всички албуми и ще са достъпни само в заключената папка",
"moved_to_archive": "{count, plural, one {# обект е преместен} many {# обекта са преместени} other {# обекта са преместени}} в архива",
"moved_to_library": "{count, plural, one {# обект е преместен} many {# обекта са преместени} other {# обекта са преместени}} в библиотеката",
"moved_to_trash": "Преместено в кошчето", "moved_to_trash": "Преместено в кошчето",
"multiselect_grid_edit_date_time_err_read_only": "Не може да се редактира датата на обект само за четене, пропускане", "multiselect_grid_edit_date_time_err_read_only": "Не може да се редактира датата на обект само за четене, пропускане",
"multiselect_grid_edit_gps_err_read_only": "Не може да се редактира местоположението на обект само за четене, пропускане", "multiselect_grid_edit_gps_err_read_only": "Не може да се редактира местоположението на обект само за четене, пропускане",
@@ -1265,14 +1257,14 @@
"new_password": "Нова парола", "new_password": "Нова парола",
"new_person": "Нов човек", "new_person": "Нов човек",
"new_pin_code": "Нов PIN код", "new_pin_code": "Нов PIN код",
"new_pin_code_subtitle": "Това е първи достъп до заключена папка. Създайте PIN код за защитен достъп до тази страница", "new_pin_code_subtitle": "Това е първи достъп до заключена папка. Създай PIN код за защитен достъп до тази страница",
"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_albums_message": "Създайте албум за организиране на снимки и видеоклипове", "no_albums_message": "Създаване на албум за организиране на снимки и видеоклипове",
"no_albums_with_name_yet": "Изглежда, че все още нямате албуми с това име.", "no_albums_with_name_yet": "Изглежда, че все още нямате албуми с това име.",
"no_albums_yet": "Изглежда, че все още нямате албуми.", "no_albums_yet": "Изглежда, че все още нямате албуми.",
"no_archived_assets_message": "Архивирайте снимки и видеоклипове, за да ги скриете от изгледа на Снимки", "no_archived_assets_message": "Архивирайте снимки и видеоклипове, за да ги скриете от изгледа на Снимки",
@@ -1282,8 +1274,8 @@
"no_duplicates_found": "Не бяха открити дубликати.", "no_duplicates_found": "Не бяха открити дубликати.",
"no_exif_info_available": "Няма exif информация", "no_exif_info_available": "Няма exif информация",
"no_explore_results_message": "Качете още снимки, за да разгледате колекцията си.", "no_explore_results_message": "Качете още снимки, за да разгледате колекцията си.",
"no_favorites_message": "Добавете в любими, за да намирате бързо най-добрите си снимки и видеоклипове", "no_favorites_message": "Добавяне на любими, за да намерите бързо най-добрите си снимки и видеоклипове",
"no_libraries_message": "Създайте външна библиотека за да разглеждате снимки и видеоклипове", "no_libraries_message": "Създаване на външна библиотека за разглеждане на снимки и видеоклипове",
"no_locked_photos_message": "Снимките и видеата в заключената папка са скрити и не се показват при разглеждане на библиотеката.", "no_locked_photos_message": "Снимките и видеата в заключената папка са скрити и не се показват при разглеждане на библиотеката.",
"no_name": "Без име", "no_name": "Без име",
"no_notifications": "Няма известия", "no_notifications": "Няма известия",
@@ -1311,7 +1303,7 @@
"oldest_first": "Най-старите първи", "oldest_first": "Най-старите първи",
"on_this_device": "На това устройство", "on_this_device": "На това устройство",
"onboarding": "Въвеждане", "onboarding": "Въвеждане",
"onboarding_locale_description": "Изберете предпочитан език. По-късно може да го промените в Настройки.", "onboarding_locale_description": "Избери предпочитан език. По-късно може да го промениш в Настройки.",
"onboarding_privacy_description": "Следните (незадължителни) функции разчитат на външни услуги и могат да бъдат деактивирани по всяко време в настройките.", "onboarding_privacy_description": "Следните (незадължителни) функции разчитат на външни услуги и могат да бъдат деактивирани по всяко време в настройките.",
"onboarding_server_welcome_description": "Да направим общите настройки на вашата инсталация.", "onboarding_server_welcome_description": "Да направим общите настройки на вашата инсталация.",
"onboarding_theme_description": "Изберете цветова тема. Може да я промените по-късно в настройките.", "onboarding_theme_description": "Изберете цветова тема. Може да я промените по-късно в настройките.",
@@ -1343,7 +1335,7 @@
"partner_page_partner_add_failed": "Неуспешно добавяне на партньор", "partner_page_partner_add_failed": "Неуспешно добавяне на партньор",
"partner_page_select_partner": "Избери партньор", "partner_page_select_partner": "Избери партньор",
"partner_page_shared_to_title": "Споделено с", "partner_page_shared_to_title": "Споделено с",
"partner_page_stop_sharing_content": "{partner} вече няма да има достъп до вашите снимки.", "partner_page_stop_sharing_content": "{partner} вече няма да има достъп до твоите снимки.",
"partner_sharing": "Споделяне с партньори", "partner_sharing": "Споделяне с партньори",
"partners": "Партньори", "partners": "Партньори",
"password": "Парола", "password": "Парола",
@@ -1380,7 +1372,7 @@
"permission_onboarding_go_to_settings": "Отиди в настройки", "permission_onboarding_go_to_settings": "Отиди в настройки",
"permission_onboarding_permission_denied": "Отказан достъп. За да ползваш Immich, разреши в настройките достъп до снимки и видео.", "permission_onboarding_permission_denied": "Отказан достъп. За да ползваш Immich, разреши в настройките достъп до снимки и видео.",
"permission_onboarding_permission_granted": "Предоставено е разрешение! Всичко е готово.", "permission_onboarding_permission_granted": "Предоставено е разрешение! Всичко е готово.",
"permission_onboarding_permission_limited": "Ограничен достъп. За да може Immich да архивира и управлява галерията, предоставете достъп до снимки и видео в настройките.", "permission_onboarding_permission_limited": "Ограничен достъп. За да може Immich да архивира и управлява галерията, предостави достъп до снимки и видео в настройките.",
"permission_onboarding_request": "Immich се нуждае от разрешение за преглед на снимки и видео.", "permission_onboarding_request": "Immich се нуждае от разрешение за преглед на снимки и видео.",
"person": "Човек", "person": "Човек",
"person_birthdate": "Дата на раждане {date}", "person_birthdate": "Дата на раждане {date}",
@@ -1391,10 +1383,10 @@
"photos_count": "{count, plural, one {{count, number} Снимка} other {{count, number} Снимки}}", "photos_count": "{count, plural, one {{count, number} Снимка} other {{count, number} Снимки}}",
"photos_from_previous_years": "Снимки от предходни години", "photos_from_previous_years": "Снимки от предходни години",
"pick_a_location": "Избери локация", "pick_a_location": "Избери локация",
"pin_code_changed_successfully": "Успешно сменен PIN код", "pin_code_changed_successfully": "Успешно сменен PIN-код",
"pin_code_reset_successfully": "Успешно нулиран PIN код", "pin_code_reset_successfully": "Успешно нулиран PIN-код",
"pin_code_setup_successfully": "Успешно зададен PIN код", "pin_code_setup_successfully": "Успешно зададен PIN-код",
"pin_verification": "Проверка на PIN кода", "pin_verification": "Проверка на PIN-кода",
"place": "Местоположение", "place": "Местоположение",
"places": "Местоположения", "places": "Местоположения",
"places_count": "{count, plural, one {{count, number} Място} other {{count, number} Места}}", "places_count": "{count, plural, one {{count, number} Място} other {{count, number} Места}}",
@@ -1448,7 +1440,7 @@
"purchase_lifetime_description": "Покупка за цял живот", "purchase_lifetime_description": "Покупка за цял живот",
"purchase_option_title": "ОПЦИИ ЗА ЗАКУПУВАНЕ", "purchase_option_title": "ОПЦИИ ЗА ЗАКУПУВАНЕ",
"purchase_panel_info_1": "Създаването на Immich отнема много време и усилия, и имаме инженери на пълно работно време, които работят по него, за да го направим възможно най-добро. Нашата мисия е софтуерът с отворен код и етичните бизнес практики да се превърнат в устойчив източник на доходи за разработчиците и да създадем екосистема, която уважава личната неприкосновеност и предлага истински алтернативи на експлоататорските облачни услуги.", "purchase_panel_info_1": "Създаването на Immich отнема много време и усилия, и имаме инженери на пълно работно време, които работят по него, за да го направим възможно най-добро. Нашата мисия е софтуерът с отворен код и етичните бизнес практики да се превърнат в устойчив източник на доходи за разработчиците и да създадем екосистема, която уважава личната неприкосновеност и предлага истински алтернативи на експлоататорските облачни услуги.",
"purchase_panel_info_2": "Тъй като сме обещали да не добавяме платени функции, тази покупка няма да ви предостави допълнителни функции в Immich. Ние разчитаме на потребители като вас, за да подкрепите продължаване на развитието на Immich.", "purchase_panel_info_2": "Тъй като сме ангажирани да не добавяме платени стени, тази покупка няма да ви предостави допълнителни функции в Immich. Ние разчитаме на потребители като вас, за да подкрепяте продължаващото развитие на Immich.",
"purchase_panel_title": "Поддържайте проекта", "purchase_panel_title": "Поддържайте проекта",
"purchase_per_server": "на сървър", "purchase_per_server": "на сървър",
"purchase_per_user": "на потребител", "purchase_per_user": "на потребител",
@@ -1497,7 +1489,7 @@
"remove_from_album": "Премахни от албума", "remove_from_album": "Премахни от албума",
"remove_from_favorites": "Премахни от Любими", "remove_from_favorites": "Премахни от Любими",
"remove_from_locked_folder": "Махни от заключената папка", "remove_from_locked_folder": "Махни от заключената папка",
"remove_from_locked_folder_confirmation": "Сигурни ли си, че искате тези снимки и видеа да бъдат извадени от заключената папка? Те ще бъдат видими в библиотеката.", "remove_from_locked_folder_confirmation": "Сигурен ли си, че искаш тези снимки и видеа да бъдат извадени от заключената папка? Те ще бъдат видими в библиотеката.",
"remove_from_shared_link": "Премахни от споделения линк", "remove_from_shared_link": "Премахни от споделения линк",
"remove_memory": "Премахни спомените", "remove_memory": "Премахни спомените",
"remove_photo_from_memory": "Премахни снимките от спомените", "remove_photo_from_memory": "Премахни снимките от спомените",
@@ -1522,7 +1514,7 @@
"reset": "Нулиране", "reset": "Нулиране",
"reset_password": "Нулиране на паролата", "reset_password": "Нулиране на паролата",
"reset_people_visibility": "Нулиране на видимостта на хората", "reset_people_visibility": "Нулиране на видимостта на хората",
"reset_pin_code": "Нулирай PIN кода", "reset_pin_code": "Нулирай PIN-кода",
"reset_to_default": "Връщане на фабрични настройки", "reset_to_default": "Връщане на фабрични настройки",
"resolve_duplicates": "Реши дубликатите", "resolve_duplicates": "Реши дубликатите",
"resolved_all_duplicates": "Всички дубликати са решени", "resolved_all_duplicates": "Всички дубликати са решени",
@@ -1587,8 +1579,8 @@
"search_page_selfies": "Селфита", "search_page_selfies": "Селфита",
"search_page_things": "Предмети", "search_page_things": "Предмети",
"search_page_view_all_button": "Виж всички", "search_page_view_all_button": "Виж всички",
"search_page_your_activity": "Вашите действия", "search_page_your_activity": "Твоите действия",
"search_page_your_map": "Вашата карта", "search_page_your_map": "Твоята карта",
"search_people": "Търсете на хора", "search_people": "Търсете на хора",
"search_places": "Търсене на места", "search_places": "Търсене на места",
"search_rating": "Търси по рейтинг…", "search_rating": "Търси по рейтинг…",
@@ -1596,7 +1588,7 @@
"search_settings": "Търсене на настройки", "search_settings": "Търсене на настройки",
"search_state": "Търсене на щат...", "search_state": "Търсене на щат...",
"search_suggestion_list_smart_search_hint_1": "Умното търсене е включено по подразбиране, за търсене в метаданните използвай специален синтакс ", "search_suggestion_list_smart_search_hint_1": "Умното търсене е включено по подразбиране, за търсене в метаданните използвай специален синтакс ",
"search_suggestion_list_smart_search_hint_2": "m:вашия-термин-за-търсене", "search_suggestion_list_smart_search_hint_2": "m:твоя-термин-за-търсене",
"search_tags": "Търсене на етикети...", "search_tags": "Търсене на етикети...",
"search_timezone": "Търсене на часова зона...", "search_timezone": "Търсене на часова зона...",
"search_type": "Тип на търсене", "search_type": "Тип на търсене",
@@ -1608,7 +1600,6 @@
"select_album_cover": "Изберете обложка на албум", "select_album_cover": "Изберете обложка на албум",
"select_all": "Изберете всички", "select_all": "Изберете всички",
"select_all_duplicates": "Избери всички дубликати", "select_all_duplicates": "Избери всички дубликати",
"select_all_in": "Избери всички от групата {group}",
"select_avatar_color": "Изберете цвят на аватара", "select_avatar_color": "Изберете цвят на аватара",
"select_face": "Изберете лице", "select_face": "Изберете лице",
"select_featured_photo": "Избери представителна снимка", "select_featured_photo": "Избери представителна снимка",
@@ -1665,7 +1656,7 @@
"settings": "Настройки", "settings": "Настройки",
"settings_require_restart": "Моля, за да се приложи настройката рестартирай Immich", "settings_require_restart": "Моля, за да се приложи настройката рестартирай Immich",
"settings_saved": "Настройките са запазени", "settings_saved": "Настройките са запазени",
"setup_pin_code": "Задай PIN код", "setup_pin_code": "Задай PIN-код",
"share": "Споделяне", "share": "Споделяне",
"share_add_photos": "Добави снимки", "share_add_photos": "Добави снимки",
"share_assets_selected": "{count} избрани", "share_assets_selected": "{count} избрани",
@@ -1673,7 +1664,7 @@
"share_link": "Връзка за споделяне", "share_link": "Връзка за споделяне",
"shared": "Споделено", "shared": "Споделено",
"shared_album_activities_input_disable": "Коментарите са изключени", "shared_album_activities_input_disable": "Коментарите са изключени",
"shared_album_activity_remove_content": "Искате ли да изтриете тази активност?", "shared_album_activity_remove_content": "Искаш ли да изтриеш тази активност?",
"shared_album_activity_remove_title": "Изтрий", "shared_album_activity_remove_title": "Изтрий",
"shared_album_section_people_action_error": "Грешка при напускане/премахване от албума", "shared_album_section_people_action_error": "Грешка при напускане/премахване от албума",
"shared_album_section_people_action_leave": "Премахване на потребител от албума", "shared_album_section_people_action_leave": "Премахване на потребител от албума",
@@ -1681,7 +1672,7 @@
"shared_album_section_people_title": "ХОРА", "shared_album_section_people_title": "ХОРА",
"shared_by": "Споделено от", "shared_by": "Споделено от",
"shared_by_user": "Споделено от {user}", "shared_by_user": "Споделено от {user}",
"shared_by_you": "Споделено от вас", "shared_by_you": "Споделено от теб",
"shared_from_partner": "Снимки от {partner}", "shared_from_partner": "Снимки от {partner}",
"shared_intent_upload_button_progress_text": "{current} / {total} Заредено", "shared_intent_upload_button_progress_text": "{current} / {total} Заредено",
"shared_link_app_bar_title": "Споделени връзки", "shared_link_app_bar_title": "Споделени връзки",
@@ -1721,7 +1712,7 @@
"sharing": "Споделени", "sharing": "Споделени",
"sharing_enter_password": "Моля, въведете паролата, за да видите тази страница.", "sharing_enter_password": "Моля, въведете паролата, за да видите тази страница.",
"sharing_page_album": "Споделени албуми", "sharing_page_album": "Споделени албуми",
"sharing_page_description": "Създайте споделени албуми за да споделите снимки и видеа с хора от вашата мрежа.", "sharing_page_description": "Създай споделени албуми за да споделиш снимки и видеа с хора от твоята мрежа.",
"sharing_page_empty_list": "ПРАЗЕН СПИСЪК", "sharing_page_empty_list": "ПРАЗЕН СПИСЪК",
"sharing_sidebar_description": "Покажи връзка към Споделяне в страничната лента", "sharing_sidebar_description": "Покажи връзка към Споделяне в страничната лента",
"sharing_silver_appbar_create_shared_album": "Нов споделен албум", "sharing_silver_appbar_create_shared_album": "Нов споделен албум",
@@ -1796,7 +1787,6 @@
"sync": "Синхронизиране", "sync": "Синхронизиране",
"sync_albums": "Синхронизиране на албуми", "sync_albums": "Синхронизиране на албуми",
"sync_albums_manual_subtitle": "Синхронизирай всички заредени видеа и снимки в избраните архивни албуми", "sync_albums_manual_subtitle": "Синхронизирай всички заредени видеа и снимки в избраните архивни албуми",
"sync_upload_album_setting_subtitle": "Създавайте и зареждайте снимки и видеа в избрани албуми в Immich",
"tag": "Таг", "tag": "Таг",
"tag_assets": "Тагни елементи", "tag_assets": "Тагни елементи",
"tag_created": "Създаден етикет: {tag}", "tag_created": "Създаден етикет: {tag}",
@@ -1810,19 +1800,6 @@
"theme": "Тема", "theme": "Тема",
"theme_selection": "Избор на тема", "theme_selection": "Избор на тема",
"theme_selection_description": "Автоматично задаване на светла или тъмна тема въз основа на системните предпочитания на вашия браузър", "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": "Те ще бъдат обединени", "they_will_be_merged_together": "Те ще бъдат обединени",
"third_party_resources": "Ресурси от трети страни", "third_party_resources": "Ресурси от трети страни",
"time_based_memories": "Спомени, базирани на времето", "time_based_memories": "Спомени, базирани на времето",
@@ -1841,22 +1818,11 @@
"trash_all": "Изхвърли всички", "trash_all": "Изхвърли всички",
"trash_count": "В Кошчето {count, number}", "trash_count": "В Кошчето {count, number}",
"trash_delete_asset": "Вкарай в Кошчето/Изтрий елемент", "trash_delete_asset": "Вкарай в Кошчето/Изтрий елемент",
"trash_emptied": "Коша е изпразнен",
"trash_no_results_message": "Изтритите снимки и видеоклипове ще се показват тук.", "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 {# дни}}.", "trashed_items_will_be_permanently_deleted_after": "Изхвърлените в кошчето елементи ще бъдат изтрити за постоянно след {days, plural, one {# ден} other {# дни}}.",
"type": "Тип", "type": "Тип",
"unable_to_change_pin_code": "Невъзможна промяна на PIN кода",
"unable_to_setup_pin_code": "Неуспешно задаване на PIN кода",
"unarchive": "Разархивирай", "unarchive": "Разархивирай",
"unarchived_count": "{count, plural, other {Неархивирани #}}", "unarchived_count": "{count, plural, other {Неархивирани #}}",
"undo": "Отмени",
"unfavorite": "Премахване от любимите", "unfavorite": "Премахване от любимите",
"unhide_person": "Покажи отново човека", "unhide_person": "Покажи отново човека",
"unknown": "Неизвестно", "unknown": "Неизвестно",
@@ -1873,16 +1839,12 @@
"unsaved_change": "Незапазена промяна", "unsaved_change": "Незапазена промяна",
"unselect_all": "Деселектирайте всички", "unselect_all": "Деселектирайте всички",
"unselect_all_duplicates": "От маркирай всички дубликати", "unselect_all_duplicates": "От маркирай всички дубликати",
"unselect_all_in": "Премахни избора на всички от групата {group}",
"unstack": "Разкачи", "unstack": "Разкачи",
"unstacked_assets_count": "Разкачени {count, plural, one {# елемент} other {# елементи}}", "unstacked_assets_count": "Разкачени {count, plural, one {# елемент} other {# елементи}}",
"up_next": "Следващ", "up_next": "Следващ",
"updated_at": "Обновено",
"updated_password": "Паролата е актуализирана", "updated_password": "Паролата е актуализирана",
"upload": "Качване", "upload": "Качване",
"upload_concurrency": "Успоредни качвания", "upload_concurrency": "Успоредни качвания",
"upload_dialog_info": "Искате ли да архивирате на сървъра избраните обекти?",
"upload_dialog_title": "Качи обект",
"upload_errors": "Качването е завъшено с {count, plural, one {# грешка} other {# грешки}}, обновете страницата за да видите новите елементи.", "upload_errors": "Качването е завъшено с {count, plural, one {# грешка} other {# грешки}}, обновете страницата за да видите новите елементи.",
"upload_progress": "Остават {remaining, number} - Обработени {processed, number}/{total, number}", "upload_progress": "Остават {remaining, number} - Обработени {processed, number}/{total, number}",
"upload_skipped_duplicates": "Прескочени {count, plural, one {# дублиран елемент} other {# дублирани елементи}}", "upload_skipped_duplicates": "Прескочени {count, plural, one {# дублиран елемент} other {# дублирани елементи}}",
@@ -1890,22 +1852,13 @@
"upload_status_errors": "Грешки", "upload_status_errors": "Грешки",
"upload_status_uploaded": "Качено", "upload_status_uploaded": "Качено",
"upload_success": "Качването е успешно, опреснете страницата, за да видите новите файлове.", "upload_success": "Качването е успешно, опреснете страницата, за да видите новите файлове.",
"upload_to_immich": "Казване в Immich ({count})",
"uploading": "Качваме",
"url": "URL",
"usage": "Потребление", "usage": "Потребление",
"use_biometric": "Използвай биометрия",
"use_current_connection": "използвай текущата връзка",
"use_custom_date_range": "Използвайте собствен диапазон от дати вместо това", "use_custom_date_range": "Използвайте собствен диапазон от дати вместо това",
"user": "Потребител", "user": "Потребител",
"user_has_been_deleted": "Този потребител е премахнат.",
"user_id": "Потребител ИД", "user_id": "Потребител ИД",
"user_liked": "{user} хареса {type, select, photo {тази снимка} video {това видео} asset {този елемент} other {}}", "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": "Покупка",
"user_purchase_settings_description": "Управлявайте покупката си", "user_purchase_settings_description": "Управлявай покупката си",
"user_role_set": "Задай {user} като {role}", "user_role_set": "Задай {user} като {role}",
"user_usage_detail": "Подробности за използването на потребителя", "user_usage_detail": "Подробности за използването на потребителя",
"user_usage_stats": "Статистика за използването на акаунта", "user_usage_stats": "Статистика за използването на акаунта",
@@ -1914,7 +1867,6 @@
"users": "Потребители", "users": "Потребители",
"utilities": "Инструменти", "utilities": "Инструменти",
"validate": "Валидиране", "validate": "Валидиране",
"validate_endpoint_error": "Моля, въведи правилен URL",
"variables": "Променливи", "variables": "Променливи",
"version": "Версия", "version": "Версия",
"version_announcement_closing": "Твой приятел, Алекс", "version_announcement_closing": "Твой приятел, Алекс",
@@ -1936,24 +1888,16 @@
"view_name": "Прегледай", "view_name": "Прегледай",
"view_next_asset": "Преглед на следващия файл", "view_next_asset": "Преглед на следващия файл",
"view_previous_asset": "Преглед на предишния файл", "view_previous_asset": "Преглед на предишния файл",
"view_qr_code": "Виж QR кода",
"view_stack": "Покажи в стек", "view_stack": "Покажи в стек",
"view_user": "Виж потребителя",
"viewer_remove_from_stack": "Премахване от опашката",
"viewer_stack_use_as_main_asset": "Използвай като основен",
"viewer_unstack": "Премахни от опашката",
"visibility_changed": "Видимостта е променена за {count, plural, one {# човек} other {# човека}}", "visibility_changed": "Видимостта е променена за {count, plural, one {# човек} other {# човека}}",
"waiting": "в изчакване", "waiting": "в изчакване",
"warning": "Внимание", "warning": "Внимание",
"week": "Седмица", "week": "Седмица",
"welcome": "Добре дошли", "welcome": "Добре дошли",
"welcome_to_immich": "Добре дошли в Immich", "welcome_to_immich": "Добре дошли в Immich",
"wifi_name": "Wi-Fi мрежа",
"wrong_pin_code": "Грешен PIN код",
"year": "Година", "year": "Година",
"years_ago": "преди {years, plural, one {# година} other {# години}}", "years_ago": "преди {years, plural, one {# година} other {# години}}",
"yes": "Да", "yes": "Да",
"you_dont_have_any_shared_links": "Нямате споделени връзки", "you_dont_have_any_shared_links": "Нямате споделени връзки",
"your_wifi_name": "Вашата Wi-Fi мрежа",
"zoom_image": "Увеличаване на изображението" "zoom_image": "Увеличаване на изображението"
} }

View File

@@ -13,7 +13,5 @@
"add_a_location": "একটি অবস্থান যোগ করুন", "add_a_location": "একটি অবস্থান যোগ করুন",
"add_a_name": "একটি নাম যোগ করুন", "add_a_name": "একটি নাম যোগ করুন",
"add_a_title": "একটি শিরোনাম যোগ করুন", "add_a_title": "একটি শিরোনাম যোগ করুন",
"add_endpoint": "এন্ডপয়েন্ট যোগ করুন", "add_endpoint": "এন্ডপয়েন্ট যোগ করুন"
"add_exclusion_pattern": "বহির্ভূতকরণ নমুনা",
"add_url": "লিঙ্ক যোগ করুন"
} }

View File

@@ -34,7 +34,6 @@
"added_to_favorites_count": "{count, number} afegits als preferits", "added_to_favorites_count": "{count, number} afegits als preferits",
"admin": { "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/**\".", "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.", "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": "Configuració de l'autenticació",
"authentication_settings_description": "Gestiona la contrasenya, OAuth i altres configuracions de l'autenticació", "authentication_settings_description": "Gestiona la contrasenya, OAuth i altres configuracions de l'autenticació",
@@ -59,7 +58,7 @@
"cron_expression_description": "Estableix l'interval d'escaneig amb el format cron. Per obtenir més informació, consulteu, p.e <link>Crontab Guru</link>", "cron_expression_description": "Estableix l'interval d'escaneig amb el format cron. Per obtenir més informació, consulteu, p.e <link>Crontab Guru</link>",
"cron_expression_presets": "Ajustos predefinits d'expressions Cron", "cron_expression_presets": "Ajustos predefinits d'expressions Cron",
"disable_login": "Deshabiliteu l'inici de sessió", "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", "duplicate_detection_job_description": "Executa l'aprenentatge automàtic en els elements per a detectar imatges semblants. Fa servir l'Smart Search",
"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.", "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.",
"external_library_management": "Gestió de llibreries externes", "external_library_management": "Gestió de llibreries externes",
"face_detection": "Detecció de cares", "face_detection": "Detecció de cares",
@@ -89,7 +88,7 @@
"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_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_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 miniatures", "image_thumbnail_title": "Configuració de miniatures",
"job_concurrency": "{job} simultàniament", "job_concurrency": "{job} concurrència",
"job_created": "Tasca creada", "job_created": "Tasca creada",
"job_not_concurrency_safe": "Aquesta tasca no és segura per a la conconcurrència.", "job_not_concurrency_safe": "Aquesta tasca no és segura per a la conconcurrència.",
"job_settings": "Configuració de les tasques", "job_settings": "Configuració de les tasques",
@@ -105,7 +104,7 @@
"library_scanning_enable_description": "Habilita l'escaneig periòdic de biblioteques", "library_scanning_enable_description": "Habilita l'escaneig periòdic de biblioteques",
"library_settings": "Llibreria externes", "library_settings": "Llibreria externes",
"library_settings_description": "Gestiona la configuració de les llibreries 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_tasks_description": "Escaneja biblioteques externes per arxius nous o canviats",
"library_watching_enable_description": "Consultar llibreries externes per detectar canvis en fitxers", "library_watching_enable_description": "Consultar llibreries externes per detectar canvis en fitxers",
"library_watching_settings": "Monitoratge de la llibreria (EXPERIMENTAL)", "library_watching_settings": "Monitoratge de la llibreria (EXPERIMENTAL)",
"library_watching_settings_description": "Monitorització automàtica de fitxers modificats", "library_watching_settings_description": "Monitorització automàtica de fitxers modificats",
@@ -113,19 +112,19 @@
"logging_level_description": "Quan està habilitat, quin nivell de registre es vol emprar.", "logging_level_description": "Quan està habilitat, quin nivell de registre es vol emprar.",
"logging_settings": "Registre", "logging_settings": "Registre",
"machine_learning_clip_model": "Model CLIP", "machine_learning_clip_model": "Model CLIP",
"machine_learning_clip_model_description": "El nom d'un model CLIP que apareix a <link>aquí</link>. 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_clip_model_description": "El nom d'un model CLIP que apareix a <link>aquí</link>. Tingues en compte que has de tornar a executar l'Smart Search' per a totes les imatges quan es canvia de model.",
"machine_learning_duplicate_detection": "Detecció de duplicats", "machine_learning_duplicate_detection": "Detecció de duplicats",
"machine_learning_duplicate_detection_enabled": "Activa la detecció de duplicats", "machine_learning_duplicate_detection_enabled": "Activa detecció de duplicats",
"machine_learning_duplicate_detection_enabled_description": "Si es desactivada, els elements idèntics encara es desduplicaran.", "machine_learning_duplicate_detection_enabled_description": "Si es deshabilitat, els elements exactament idèntics encara es desduplicaran.",
"machine_learning_duplicate_detection_setting_description": "Usa incrustacions CLIP per a trobar prossibles duplicats", "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": "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_enabled_description": "Si està deshabilitat, totes les funcions ML es deshabilitaran sense tenir en compte la configuració següent.",
"machine_learning_facial_recognition": "Reconeixement facial", "machine_learning_facial_recognition": "Reconeixement facial",
"machine_learning_facial_recognition_description": "Detecta, reconeix i agrupa cares a les imatges", "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": "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_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": "Activa 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_facial_recognition_setting_description": "Si està deshabilitat, 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": "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_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": "Màxima diferència de reconeixement",
@@ -136,17 +135,17 @@
"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_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_settings": "Configuració d'aprenentatge automàtic", "machine_learning_settings": "Configuració d'aprenentatge automàtic",
"machine_learning_settings_description": "Gestiona funcions i 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": "Cerca Intel·ligent",
"machine_learning_smart_search_description": "Cerca imatges semànticament emprant incrustacions CLIP", "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": "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_smart_search_enabled_description": "Si està deshabilitat 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.", "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.",
"manage_concurrency": "Gestiona la concurrència", "manage_concurrency": "Gestiona la concurrència",
"manage_log_settings": "Gestiona la configuració del registre", "manage_log_settings": "Gestiona la configuració del registre",
"map_dark_style": "Tema fosc", "map_dark_style": "Tema fosc",
"map_enable_description": "Habilita característiques del mapa", "map_enable_description": "Habilita característiques del mapa",
"map_gps_settings": "Configuració del mapa i GPS", "map_gps_settings": "Configuració de mapa i GPS",
"map_gps_settings_description": "Gestiona la configuració del mapa i GPS (Geocodificació inversa)", "map_gps_settings_description": "Gestiona la configuració de mapa i GPS (Geocodificació inversa)",
"map_implications": "La funció mapa depèn del servei extern de tesel·les (tiles.immich.cloud)", "map_implications": "La funció mapa depèn del servei extern de tesel·les (tiles.immich.cloud)",
"map_light_style": "Tema clar", "map_light_style": "Tema clar",
"map_manage_reverse_geocoding_settings": "Gestiona els paràmetres de <link>geocodificació inversa</link>", "map_manage_reverse_geocoding_settings": "Gestiona els paràmetres de <link>geocodificació inversa</link>",
@@ -244,6 +243,7 @@
"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 <link>{job}</link>.", "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 <link>{job}</link>.",
"storage_template_migration_job": "Tasca de migració de la plantilla d'emmagatzematge", "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 <template-link>Storage Template</template-link> i les seves <implications-link>implications</implications-link>", "storage_template_more_details": "Per obtenir més detalls sobre aquesta funció, consulteu la <template-link>Storage Template</template-link> i les seves <implications-link>implications</implications-link>",
"storage_template_onboarding_description": "Quan està activada, aquesta funció organitzarà automàticament els fitxers en funció d'una plantilla definida per l'usuari. A causa de problemes d'estabilitat, la funció s'ha desactivat de manera predeterminada. Per obtenir més informació, consulteu la <link>documentation</link>.",
"storage_template_path_length": "Límit aproximat de longitud de la ruta: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "Límit aproximat de longitud de la ruta: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "Plantilla d'emmagatzematge", "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_settings_description": "Gestiona l'estructura de les carpetes i el nom del fitxers dels elements pujats",
@@ -260,7 +260,7 @@
"template_settings": "Plantilles de notificació", "template_settings": "Plantilles de notificació",
"template_settings_description": "Gestiona les plantilles personalitzades per les notificacions", "template_settings_description": "Gestiona les plantilles personalitzades per les notificacions",
"theme_custom_css_settings": "CSS personalitzat", "theme_custom_css_settings": "CSS personalitzat",
"theme_custom_css_settings_description": "Els fulls d'estil en cascada permeten personalitzar el disseny d'Immich.", "theme_custom_css_settings_description": "Els Fulls d'Estil en Cascada permeten personalitzar el disseny d'Immich.",
"theme_settings": "Configuració del tema", "theme_settings": "Configuració del tema",
"theme_settings_description": "Gestiona la personalització de la interfície web Immich", "theme_settings_description": "Gestiona la personalització de la interfície web Immich",
"thumbnail_generation_job": "Generar miniatures", "thumbnail_generation_job": "Generar miniatures",
@@ -354,7 +354,7 @@
}, },
"admin_email": "Correu de l'administrador", "admin_email": "Correu de l'administrador",
"admin_password": "Contrasenya de l'administrador", "admin_password": "Contrasenya de l'administrador",
"administration": "Administració", "administration": "Administrador",
"advanced": "Avançat", "advanced": "Avançat",
"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_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_enable_alternate_media_filter_title": "Utilitza el filtre de sincronització d'àlbums de dispositius alternatius",
@@ -468,8 +468,6 @@
"assets_count": "{count, plural, one {# recurs} other {# recursos}}", "assets_count": "{count, plural, one {# recurs} other {# recursos}}",
"assets_deleted_permanently": "{count} element(s) esborrats permanentment", "assets_deleted_permanently": "{count} element(s) esborrats permanentment",
"assets_deleted_permanently_from_server": "{count} element(s) esborrats permanentment del servidor d'Immich", "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_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_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_count": "{count, plural, one {# element eliminat} other {# elements eliminats}}",
@@ -553,7 +551,7 @@
"backup_setting_subtitle": "Gestiona la configuració de càrrega en segon pla i en primer pla", "backup_setting_subtitle": "Gestiona la configuració de càrrega en segon pla i en primer pla",
"backward": "Enrere", "backward": "Enrere",
"biometric_auth_enabled": "Autentificació biomètrica activada", "biometric_auth_enabled": "Autentificació biomètrica activada",
"biometric_locked_out": "Esteu bloquejats fora de l'autenticació biomètrica", "biometric_locked_out": "Esteu bloquejat fora de l'autenticació biomètrica",
"biometric_no_options": "No hi ha opcions biomètriques disponibles", "biometric_no_options": "No hi ha opcions biomètriques disponibles",
"biometric_not_available": "L'autenticació biomètrica no està disponible en aquest dispositiu", "biometric_not_available": "L'autenticació biomètrica no està disponible en aquest dispositiu",
"birthdate_saved": "Data de naixement guardada amb èxit", "birthdate_saved": "Data de naixement guardada amb èxit",
@@ -1097,7 +1095,6 @@
"ios_debug_info_last_sync_at": "Darrera sincronització {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_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_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}", "ios_debug_info_processing_ran_at": "El processament s'ha executat {dateTime}",
"items_count": "{count, plural, one {# element} other {# elements}}", "items_count": "{count, plural, one {# element} other {# elements}}",
"jobs": "Tasques", "jobs": "Tasques",
@@ -1135,7 +1132,7 @@
"list": "Llista", "list": "Llista",
"loading": "Carregant", "loading": "Carregant",
"loading_search_results_failed": "No s'han pogut carregar els resultats de la cerca", "loading_search_results_failed": "No s'han pogut carregar els resultats de la cerca",
"local_asset_cast_failed": "No es pot convertir un actiu que no s'ha penjat al servidor", "local_asset_cast_failed": "No es pot convertir un actiu que no s'ha penjat al servidor.",
"local_network": "Xarxa local", "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", "local_network_sheet_info": "L'aplicació es connectarà al servidor mitjançant aquest URL quan utilitzeu la xarxa Wi-Fi especificada",
"location_permission": "Permís d'ubicació", "location_permission": "Permís d'ubicació",

View File

@@ -34,7 +34,6 @@
"added_to_favorites_count": "Přidáno {count, number} do oblíbených", "added_to_favorites_count": "Přidáno {count, number} do oblíbených",
"admin": { "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/**\".", "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.", "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": "Přihlašování",
"authentication_settings_description": "Správa hesel, OAuth a dalších nastavení ověření", "authentication_settings_description": "Správa hesel, OAuth a dalších nastavení ověření",
@@ -204,7 +203,7 @@
"oauth_storage_quota_claim": "Deklarace kvóty úložiště", "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_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": "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_storage_quota_default_description": "Kvóta v GiB, která se použije, pokud není poskytnuta žádná deklarace (pro neomezenou kvótu zadejte 0).",
"oauth_timeout": "Časový limit požadavku", "oauth_timeout": "Časový limit požadavku",
"oauth_timeout_description": "Časový limit pro požadavky v milisekundách", "oauth_timeout_description": "Časový limit pro požadavky v milisekundách",
"password_enable_description": "Přihlášení pomocí e-mailu a hesla", "password_enable_description": "Přihlášení pomocí e-mailu a hesla",
@@ -244,7 +243,7 @@
"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 <link>{job}</link>.", "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 <link>{job}</link>.",
"storage_template_migration_job": "Úloha migrace šablony úložiště", "storage_template_migration_job": "Úloha migrace šablony úložiště",
"storage_template_more_details": "Další podrobnosti o této funkci naleznete v sekci <template-link>Šablona úložiště</template-link> včetně jejích <implications-link>důsledků</implications-link>", "storage_template_more_details": "Další podrobnosti o této funkci naleznete v sekci <template-link>Šablona úložiště</template-link> včetně jejích <implications-link>důsledků</implications-link>",
"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 <link>dokumentaci</link>.", "storage_template_onboarding_description": "Je-li tato funkce povolena, automaticky uspořádá soubory na základě uživatelem definované šablony. Z důvodu problémů se stabilitou byla tato funkce ve výchozím nastavení vypnuta. Další informace naleznete v <link>dokumentaci</link>.",
"storage_template_path_length": "Přibližný limit délky cesty: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "Přibližný limit délky cesty: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "Šablona úložiště", "storage_template_settings": "Šablona úložiště",
"storage_template_settings_description": "Správa struktury složek a názvů nahraných souborů", "storage_template_settings_description": "Správa struktury složek a názvů nahraných souborů",
@@ -1150,7 +1149,6 @@
"locked_folder": "Uzamčená složka", "locked_folder": "Uzamčená složka",
"log_out": "Odhlásit", "log_out": "Odhlásit",
"log_out_all_devices": "Odhlásit všechna zařízení", "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_all_devices": "Všechna zařízení odhlášena",
"logged_out_device": "Zařízení odhlášeno", "logged_out_device": "Zařízení odhlášeno",
"login": "Přihlášení", "login": "Přihlášení",
@@ -1608,7 +1606,6 @@
"select_album_cover": "Vybrat obal alba", "select_album_cover": "Vybrat obal alba",
"select_all": "Vybrat vše", "select_all": "Vybrat vše",
"select_all_duplicates": "Vybrat všechny duplicity", "select_all_duplicates": "Vybrat všechny duplicity",
"select_all_in": "Vybrat vše ve skupině {group}",
"select_avatar_color": "Vyberte barvu avatara", "select_avatar_color": "Vyberte barvu avatara",
"select_face": "Vybrat obličej", "select_face": "Vybrat obličej",
"select_featured_photo": "Vybrat hlavní fotografii", "select_featured_photo": "Vybrat hlavní fotografii",
@@ -1873,7 +1870,6 @@
"unsaved_change": "Neuložená změna", "unsaved_change": "Neuložená změna",
"unselect_all": "Zrušit výběr všech", "unselect_all": "Zrušit výběr všech",
"unselect_all_duplicates": "Zrušit výběr všech duplicit", "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": "Zrušit seskupení",
"unstacked_assets_count": "{count, plural, one {Rozložená # položka} few {Rozložené # položky} other {Rozložených # položiek}}", "unstacked_assets_count": "{count, plural, one {Rozložená # položka} few {Rozložené # položky} other {Rozložených # položiek}}",
"up_next": "To je prozatím vše", "up_next": "To je prozatím vše",

View File

@@ -243,6 +243,7 @@
"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 <link>{job}</link>.", "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 <link>{job}</link>.",
"storage_template_migration_job": "Lager Skabelon Migreringsjob", "storage_template_migration_job": "Lager Skabelon Migreringsjob",
"storage_template_more_details": "For flere detaljer om denne funktion, referer til <template-link>Lager Skabelonen</template-link> og dens <implications-link>implikationer</implications-link>", "storage_template_more_details": "For flere detaljer om denne funktion, referer til <template-link>Lager Skabelonen</template-link> og dens <implications-link>implikationer</implications-link>",
"storage_template_onboarding_description": "Når denne funktion er aktiveret, vil den automatisk organisere filer baseret på en brugerdefineret skabelon. På grund af stabilitetsproblemer er funktionen som standard slået fra. For mere information, se <link>dokumentation</link>.",
"storage_template_path_length": "Anslået sti-længde begrænsning <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "Anslået sti-længde begrænsning <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "Lagringsskabelon", "storage_template_settings": "Lagringsskabelon",
"storage_template_settings_description": "Administrer mappestrukturen og filnavnet for den uploadede mediefil", "storage_template_settings_description": "Administrer mappestrukturen og filnavnet for den uploadede mediefil",

View File

@@ -34,7 +34,6 @@
"added_to_favorites_count": "{count, number} zu Favoriten hinzugefügt", "added_to_favorites_count": "{count, number} zu Favoriten hinzugefügt",
"admin": { "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.", "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.", "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": "Authentifizierungseinstellungen",
"authentication_settings_description": "Passwort-, OAuth- und sonstige Authentifizierungseinstellungen verwalten", "authentication_settings_description": "Passwort-, OAuth- und sonstige Authentifizierungseinstellungen verwalten",
@@ -204,7 +203,7 @@
"oauth_storage_quota_claim": "Speicherkontingentangabe", "oauth_storage_quota_claim": "Speicherkontingentangabe",
"oauth_storage_quota_claim_description": "Setzen Sie das Speicherkontingent des Benutzers automatisch auf den angegebenen Wert.", "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": "Standard-Speicherplatzkontingent (GiB)",
"oauth_storage_quota_default_description": "Kontingent in GiB, das verwendet werden soll, wenn keines übermittelt wird.", "oauth_storage_quota_default_description": "Kontingent in GiB, das verwendet werden soll, wenn keines übermittelt wird (gib 0 für ein unbegrenztes Kontingent ein).",
"oauth_timeout": "Zeitüberschreitung bei Anfrage", "oauth_timeout": "Zeitüberschreitung bei Anfrage",
"oauth_timeout_description": "Zeitüberschreitung für Anfragen in Millisekunden", "oauth_timeout_description": "Zeitüberschreitung für Anfragen in Millisekunden",
"password_enable_description": "Mit E-Mail und Passwort anmelden", "password_enable_description": "Mit E-Mail und Passwort anmelden",
@@ -244,6 +243,7 @@
"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 <link>{job}</link> aus.", "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 <link>{job}</link> aus.",
"storage_template_migration_job": "Speichervorlagenmigrations-Aufgabe", "storage_template_migration_job": "Speichervorlagenmigrations-Aufgabe",
"storage_template_more_details": "Weitere Details zu dieser Funktion findest du unter <template-link>Speichervorlage</template-link> und dessen <implications-link>Implikationen</implications-link>", "storage_template_more_details": "Weitere Details zu dieser Funktion findest du unter <template-link>Speichervorlage</template-link> und dessen <implications-link>Implikationen</implications-link>",
"storage_template_onboarding_description": "Wenn aktiviert, sortiert diese Funktion Dateien automatisch basierend auf einer benutzerdefinierten Vorlage. Aufgrund von Stabilitätsproblemen ist die Funktion standardmäßig deaktiviert. Weitere Informationen findest du in der <link>Dokumentation</link>.",
"storage_template_path_length": "Ungefähres Pfadlängen-Limit: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "Ungefähres Pfadlängen-Limit: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "Speichervorlage", "storage_template_settings": "Speichervorlage",
"storage_template_settings_description": "Die Ordnerstruktur und den Dateinamen der hochgeladenen Datei verwalten", "storage_template_settings_description": "Die Ordnerstruktur und den Dateinamen der hochgeladenen Datei verwalten",
@@ -1149,7 +1149,6 @@
"locked_folder": "Gesperrter Ordner", "locked_folder": "Gesperrter Ordner",
"log_out": "Abmelden", "log_out": "Abmelden",
"log_out_all_devices": "Alle Geräte 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_all_devices": "Alle Geräte abgemeldet",
"logged_out_device": "Gerät abgemeldet", "logged_out_device": "Gerät abgemeldet",
"login": "Anmelden", "login": "Anmelden",
@@ -1607,7 +1606,6 @@
"select_album_cover": "Album-Cover auswählen", "select_album_cover": "Album-Cover auswählen",
"select_all": "Alles auswählen", "select_all": "Alles auswählen",
"select_all_duplicates": "Alle Duplikate 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_avatar_color": "Avatar-Farbe auswählen",
"select_face": "Gesicht auswählen", "select_face": "Gesicht auswählen",
"select_featured_photo": "Anzeigebild auswählen", "select_featured_photo": "Anzeigebild auswählen",
@@ -1872,7 +1870,6 @@
"unsaved_change": "Ungespeicherte Änderung", "unsaved_change": "Ungespeicherte Änderung",
"unselect_all": "Alles abwählen", "unselect_all": "Alles abwählen",
"unselect_all_duplicates": "Alle Duplikate abwählen", "unselect_all_duplicates": "Alle Duplikate abwählen",
"unselect_all_in": "Alle in {group} abwählen",
"unstack": "Entstapeln", "unstack": "Entstapeln",
"unstacked_assets_count": "{count, plural, one {# Datei} other {# Dateien}} entstapelt", "unstacked_assets_count": "{count, plural, one {# Datei} other {# Dateien}} entstapelt",
"up_next": "Weiter", "up_next": "Weiter",

View File

@@ -22,7 +22,6 @@
"add_partner": "Προσθήκη συνεργάτη", "add_partner": "Προσθήκη συνεργάτη",
"add_path": "Προσθήκη διαδρομής", "add_path": "Προσθήκη διαδρομής",
"add_photos": "Προσθήκη φωτογραφιών", "add_photos": "Προσθήκη φωτογραφιών",
"add_tag": "Προσθήκη ετικέτας",
"add_to": "Προσθήκη σε…", "add_to": "Προσθήκη σε…",
"add_to_album": "Προσθήκη σε άλμπουμ", "add_to_album": "Προσθήκη σε άλμπουμ",
"add_to_album_bottom_sheet_added": "Προστέθηκε στο {album}", "add_to_album_bottom_sheet_added": "Προστέθηκε στο {album}",
@@ -34,7 +33,6 @@
"added_to_favorites_count": "Προστέθηκαν {count, number} στα αγαπημένα", "added_to_favorites_count": "Προστέθηκαν {count, number} στα αγαπημένα",
"admin": { "admin": {
"add_exclusion_pattern_description": "Προσθέστε μοτίβα αποκλεισμού. Υποστηρίζεται η επιλογή πολλών με *, **, και ?. Για να αγνοηθούν όλα τα αρχεία σε έναν φάκελο με το όνομα \"Raw\", χρησιμοποιήστε \"**/Raw/**\". Για να αγνοηθούν όλα τα αρχεία με κατάληξη \".tif\", χρησιμοποιήστε \"**/*.tif\". Για να αγνοηθεί μία απόλυτη διαδρομή, χρησιμοποιήστε \"/path/to/ignore/**\".", "add_exclusion_pattern_description": "Προσθέστε μοτίβα αποκλεισμού. Υποστηρίζεται η επιλογή πολλών με *, **, και ?. Για να αγνοηθούν όλα τα αρχεία σε έναν φάκελο με το όνομα \"Raw\", χρησιμοποιήστε \"**/Raw/**\". Για να αγνοηθούν όλα τα αρχεία με κατάληξη \".tif\", χρησιμοποιήστε \"**/*.tif\". Για να αγνοηθεί μία απόλυτη διαδρομή, χρησιμοποιήστε \"/path/to/ignore/**\".",
"admin_user": "Διαχειριστής",
"asset_offline_description": "Αυτό το στοιχείο εξωτερικής βιβλιοθήκης δε βρίσκεται πλέον στο δίσκο και έχει μεταφερθεί στα απορρίμματα. Εάν το αρχείο έχει μετακινηθεί εντός της βιβλιοθήκης, ελέγξτε το χρονολόγιο φωτογραφιών σας για το νέο αντίστοιχο στοιχείο. Για να επαναφέρετε αυτό το στοιχείο, βεβαιωθείτε ότι το παρακάτω μονοπάτι αρχείου είναι προσβάσιμο από το Immich και σαρώστε τη βιβλιοθήκη.", "asset_offline_description": "Αυτό το στοιχείο εξωτερικής βιβλιοθήκης δε βρίσκεται πλέον στο δίσκο και έχει μεταφερθεί στα απορρίμματα. Εάν το αρχείο έχει μετακινηθεί εντός της βιβλιοθήκης, ελέγξτε το χρονολόγιο φωτογραφιών σας για το νέο αντίστοιχο στοιχείο. Για να επαναφέρετε αυτό το στοιχείο, βεβαιωθείτε ότι το παρακάτω μονοπάτι αρχείου είναι προσβάσιμο από το Immich και σαρώστε τη βιβλιοθήκη.",
"authentication_settings": "Ρυθμίσεις Ελέγχου Ταυτότητας", "authentication_settings": "Ρυθμίσεις Ελέγχου Ταυτότητας",
"authentication_settings_description": "Διαχείριση κωδικού πρόσβασης, OAuth και άλλων ρυθμίσεων ελέγχου ταυτότητας", "authentication_settings_description": "Διαχείριση κωδικού πρόσβασης, OAuth και άλλων ρυθμίσεων ελέγχου ταυτότητας",
@@ -45,7 +43,7 @@
"backup_database_enable_description": "Ενεργοποίηση dumps βάσης δεδομένων", "backup_database_enable_description": "Ενεργοποίηση dumps βάσης δεδομένων",
"backup_keep_last_amount": "Ποσότητα προηγούμενων dumps που πρέπει να διατηρηθούν", "backup_keep_last_amount": "Ποσότητα προηγούμενων dumps που πρέπει να διατηρηθούν",
"backup_settings": "Ρυθμίσεις dump βάσης δεδομένων", "backup_settings": "Ρυθμίσεις dump βάσης δεδομένων",
"backup_settings_description": "Διαχείριση ρυθμίσεων dump της βάσης δεδομένων.", "backup_settings_description": "Διαχείριση ρυθμίσεων dump της βάσης δεδομένων. Σημείωση: Αυτές οι εργασίες δεν παρακολουθούνται και δεν θα ειδοποιηθείτε για αποτυχία.",
"cleared_jobs": "Εκκαθαρίστηκαν οι εργασίες για: {job}", "cleared_jobs": "Εκκαθαρίστηκαν οι εργασίες για: {job}",
"config_set_by_file": "Η παραμετροποίηση γίνεται, προς το παρόν, μέσω ενός αρχείου παραμέτρων", "config_set_by_file": "Η παραμετροποίηση γίνεται, προς το παρόν, μέσω ενός αρχείου παραμέτρων",
"confirm_delete_library": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τη βιβλιοθήκη {library};", "confirm_delete_library": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τη βιβλιοθήκη {library};",
@@ -171,7 +169,7 @@
"note_apply_storage_label_previous_assets": "Σημείωση: Για να εφαρμοστεί η Ετικέτα Αποθήκευσης σε στοιχεία που είχαν αναρτηθεί παλαιότερα, εκτέλεσε το", "note_apply_storage_label_previous_assets": "Σημείωση: Για να εφαρμοστεί η Ετικέτα Αποθήκευσης σε στοιχεία που είχαν αναρτηθεί παλαιότερα, εκτέλεσε το",
"note_cannot_be_changed_later": "ΣΗΜΕΊΩΣΗ: Αυτό δεν μπορεί να τροποποιηθεί αργότερα!", "note_cannot_be_changed_later": "ΣΗΜΕΊΩΣΗ: Αυτό δεν μπορεί να τροποποιηθεί αργότερα!",
"notification_email_from_address": "Διεύθυνση αποστολέα", "notification_email_from_address": "Διεύθυνση αποστολέα",
"notification_email_from_address_description": "Διεύθυνση αποστολέα, πχ: \"Immich Photo Server <noreply@example.com>\". Βεβαιωθείτε ότι έχετε δικαίωμα χρήσης της διεύθυνσης που χρησιμοποιείτε.", "notification_email_from_address_description": "Διεύθυνση αποστολέα, πχ: \"Immich Photo Server <noreply@example.com>\"",
"notification_email_host_description": "Πάροχος του email server (πχ smtp.immich.app)", "notification_email_host_description": "Πάροχος του email server (πχ smtp.immich.app)",
"notification_email_ignore_certificate_errors": "Παράβλεψη των σφαλμάτων πιστοποίησης", "notification_email_ignore_certificate_errors": "Παράβλεψη των σφαλμάτων πιστοποίησης",
"notification_email_ignore_certificate_errors_description": "Παράβλεψη σφαλμάτων επικύρωσης της πιστοποίησης TLS (δεν προτείνεται)", "notification_email_ignore_certificate_errors_description": "Παράβλεψη σφαλμάτων επικύρωσης της πιστοποίησης TLS (δεν προτείνεται)",
@@ -204,7 +202,7 @@
"oauth_storage_quota_claim": "Δήλωση ποσοστού αποθήκευσης", "oauth_storage_quota_claim": "Δήλωση ποσοστού αποθήκευσης",
"oauth_storage_quota_claim_description": "Ορίζει αυτόματα το ποσοστό αποθήκευσης του χρήστη στη δηλωμένη τιμή.", "oauth_storage_quota_claim_description": "Ορίζει αυτόματα το ποσοστό αποθήκευσης του χρήστη στη δηλωμένη τιμή.",
"oauth_storage_quota_default": "Προεπιλεγμένο όριο αποθήκευσης (GiB)", "oauth_storage_quota_default": "Προεπιλεγμένο όριο αποθήκευσης (GiB)",
"oauth_storage_quota_default_description": "Ποσοστό σε GiB που θα χρησιμοποιηθεί όταν δεν ορίζεται από τη δηλωμένη τιμή.", "oauth_storage_quota_default_description": "Ποσοστό σε GiB που θα χρησιμοποιηθεί όταν δεν ορίζεται από τη δηλωμένη τιμή (Εισάγετε 0 για απεριόριστο ποσοστό).",
"oauth_timeout": "Χρονικό όριο Αιτήματος", "oauth_timeout": "Χρονικό όριο Αιτήματος",
"oauth_timeout_description": "Χρονικό όριο Αιτήματος σε milliseconds", "oauth_timeout_description": "Χρονικό όριο Αιτήματος σε milliseconds",
"password_enable_description": "Σύνδεση με ηλεκτρονικό ταχυδρομείο", "password_enable_description": "Σύνδεση με ηλεκτρονικό ταχυδρομείο",
@@ -244,7 +242,7 @@
"storage_template_migration_info": "Το πρότυπο αποθήκευσης θα μετατρέψει όλες τις επεκτάσεις σε πεζά γράμματα. Οι αλλαγές στο πρότυπο θα ισχύουν μόνο για νέα περιουσιακά στοιχεία. Για να εφαρμόσετε αναδρομικά το πρότυπο σε περιουσιακά στοιχεία που έχουν μεταφορτωθεί προηγουμένως, εκτελέστε το <link>{job}</link>.", "storage_template_migration_info": "Το πρότυπο αποθήκευσης θα μετατρέψει όλες τις επεκτάσεις σε πεζά γράμματα. Οι αλλαγές στο πρότυπο θα ισχύουν μόνο για νέα περιουσιακά στοιχεία. Για να εφαρμόσετε αναδρομικά το πρότυπο σε περιουσιακά στοιχεία που έχουν μεταφορτωθεί προηγουμένως, εκτελέστε το <link>{job}</link>.",
"storage_template_migration_job": "Εργασία Μεταφοράς Προτύπων Αποθήκευσης", "storage_template_migration_job": "Εργασία Μεταφοράς Προτύπων Αποθήκευσης",
"storage_template_more_details": "Για περισσότερες λεπτομέρειες σχετικά με αυτήν τη δυνατότητα, ανατρέξτε στο <template-link>Πρότυπο Αποθήκευσης</template-link> και στις <implications-link>συνέπειές</implications-link> του", "storage_template_more_details": "Για περισσότερες λεπτομέρειες σχετικά με αυτήν τη δυνατότητα, ανατρέξτε στο <template-link>Πρότυπο Αποθήκευσης</template-link> και στις <implications-link>συνέπειές</implications-link> του",
"storage_template_onboarding_description_v2": "Όταν είναι ενεργοποιημένη, αυτή η λειτουργία θα οργανώνει αυτόματα τα αρχεία με βάση ένα πρότυπο που ορίζεται από το χρήστη. Για περισσότερες πληροφορίες, παρακαλώ ανατρέξτε στις <link>οδηγίες χρήσης</link>.", "storage_template_onboarding_description": "Όταν ενεργοποιηθεί, αυτή η δυνατότητα θα οργανώνει αυτόματα τα αρχεία με βάση ένα πρότυπο που καθορίζεται από τον χρήστη. Λόγω θεμάτων σταθερότητας, η δυνατότητα είναι απενεργοποιημένη από προεπιλογή. Για περισσότερες πληροφορίες, παρακαλώ δείτε την <link>τεκμηρίωση</link>.",
"storage_template_path_length": "Όριο μήκους διαδρομής: <b>{length, number}</b>/{limit, number}, κατά προσέγγιση", "storage_template_path_length": "Όριο μήκους διαδρομής: <b>{length, number}</b>/{limit, number}, κατά προσέγγιση",
"storage_template_settings": "Πρότυπο Αποθήκευσης", "storage_template_settings": "Πρότυπο Αποθήκευσης",
"storage_template_settings_description": "Διαχείριση της δομής φακέλου και του ονόματος, του ανεβασμένου αρχείου", "storage_template_settings_description": "Διαχείριση της δομής φακέλου και του ονόματος, του ανεβασμένου αρχείου",
@@ -404,9 +402,6 @@
"album_with_link_access": "Επιτρέψτε σε οποιονδήποτε έχει τον σύνδεσμο, να δει τις φωτογραφίες και τα άτομα σε αυτό το άλμπουμ.", "album_with_link_access": "Επιτρέψτε σε οποιονδήποτε έχει τον σύνδεσμο, να δει τις φωτογραφίες και τα άτομα σε αυτό το άλμπουμ.",
"albums": "Άλμπουμ", "albums": "Άλμπουμ",
"albums_count": "{count, plural, one {{count, number} Άλμπουμ} other {{count, number} Άλμπουμ}}", "albums_count": "{count, plural, one {{count, number} Άλμπουμ} other {{count, number} Άλμπουμ}}",
"albums_default_sort_order": "Προεπιλεγμένη ταξινόμηση άλμπουμ",
"albums_default_sort_order_description": "Αρχική ταξινόμηση κατά τη δημιουργία νέων άλμπουμ.",
"albums_feature_description": "Συλλογές στοιχείων που μπορούν να κοινοποιηθούν σε άλλους χρήστες.",
"all": "Όλα", "all": "Όλα",
"all_albums": "Όλα τα άλμπουμ", "all_albums": "Όλα τα άλμπουμ",
"all_people": "Όλα τα άτομα", "all_people": "Όλα τα άτομα",
@@ -465,12 +460,9 @@
"assets_added_count": "Προστέθηκε {count, plural, one {# αρχείο} other {# αρχεία}}", "assets_added_count": "Προστέθηκε {count, plural, one {# αρχείο} other {# αρχεία}}",
"assets_added_to_album_count": "Προστέθηκε {count, plural, one {# αρχείο} other {# αρχεία}} στο άλμπουμ", "assets_added_to_album_count": "Προστέθηκε {count, plural, one {# αρχείο} other {# αρχεία}} στο άλμπουμ",
"assets_added_to_name_count": "Προστέθηκε {count, plural, one {# αρχείο} other {# αρχεία}} στο {hasName, select, true {<b>{name}</b>} other {νέο άλμπουμ}}", "assets_added_to_name_count": "Προστέθηκε {count, plural, one {# αρχείο} other {# αρχεία}} στο {hasName, select, true {<b>{name}</b>} other {νέο άλμπουμ}}",
"assets_cannot_be_added_to_album_count": "{count, plural, one {Στοιχείο} other {Στοιχεία}} δεν μπορούν να προστεθούν στο άλμπουμ",
"assets_count": "{count, plural, one {# αρχείο} other {# αρχεία}}", "assets_count": "{count, plural, one {# αρχείο} other {# αρχεία}}",
"assets_deleted_permanently": "{count} τα στοιχεία διαγράφηκαν οριστικά", "assets_deleted_permanently": "{count} τα στοιχεία διαγράφηκαν οριστικά",
"assets_deleted_permanently_from_server": "{count} στοιχεία διαγράφηκαν οριστικά από το διακομιστή Immich", "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_moved_to_trash_count": "Μετακινήθηκαν {count, plural, one {# αρχείο} other {# αρχεία}} στον κάδο απορριμμάτων",
"assets_permanently_deleted_count": "Διαγράφηκαν μόνιμα {count, plural, one {# αρχείο} other {# αρχεία}}", "assets_permanently_deleted_count": "Διαγράφηκαν μόνιμα {count, plural, one {# αρχείο} other {# αρχεία}}",
"assets_removed_count": "Αφαιρέθηκαν {count, plural, one {# αρχείο} other {# αρχεία}}", "assets_removed_count": "Αφαιρέθηκαν {count, plural, one {# αρχείο} other {# αρχεία}}",
@@ -485,7 +477,6 @@
"authorized_devices": "Εξουσιοδοτημένες Συσκευές", "authorized_devices": "Εξουσιοδοτημένες Συσκευές",
"automatic_endpoint_switching_subtitle": "Σύνδεση τοπικά μέσω του καθορισμένου Wi-Fi όταν είναι διαθέσιμο και χρήση εναλλακτικών συνδέσεων αλλού", "automatic_endpoint_switching_subtitle": "Σύνδεση τοπικά μέσω του καθορισμένου Wi-Fi όταν είναι διαθέσιμο και χρήση εναλλακτικών συνδέσεων αλλού",
"automatic_endpoint_switching_title": "Αυτόματη εναλλαγή URL", "automatic_endpoint_switching_title": "Αυτόματη εναλλαγή URL",
"autoplay_slideshow": "Αυτόματη αναπαραγωγή παρουσίασης",
"back": "Πίσω", "back": "Πίσω",
"back_close_deselect": "Πίσω, κλείσιμο ή αποεπιλογή", "back_close_deselect": "Πίσω, κλείσιμο ή αποεπιλογή",
"background_location_permission": "Άδεια τοποθεσίας στο παρασκήνιο", "background_location_permission": "Άδεια τοποθεσίας στο παρασκήνιο",
@@ -650,7 +641,6 @@
"confirm_password": "Επιβεβαίωση κωδικού", "confirm_password": "Επιβεβαίωση κωδικού",
"confirm_tag_face": "Θέλετε να επισημάνετε αυτό το πρόσωπο ως {name};", "confirm_tag_face": "Θέλετε να επισημάνετε αυτό το πρόσωπο ως {name};",
"confirm_tag_face_unnamed": "Θέλετε να επισημάνετε αυτό το πρόσωπο;", "confirm_tag_face_unnamed": "Θέλετε να επισημάνετε αυτό το πρόσωπο;",
"connected_device": "Συνδεδεμένη συσκευή",
"connected_to": "Συνδεδεμένο με", "connected_to": "Συνδεδεμένο με",
"contain": "Περιέχει", "contain": "Περιέχει",
"context": "Συμφραζόμενα", "context": "Συμφραζόμενα",
@@ -703,7 +693,6 @@
"daily_title_text_date": "Ε, MMM dd", "daily_title_text_date": "Ε, MMM dd",
"daily_title_text_date_year": "Ε, MMM dd, yyyy", "daily_title_text_date_year": "Ε, MMM dd, yyyy",
"dark": "Σκούρο", "dark": "Σκούρο",
"darkTheme": "Εναλλαγή σκούρου θέματος",
"date_after": "Ημερομηνία μετά", "date_after": "Ημερομηνία μετά",
"date_and_time": "Ημερομηνία και ώρα", "date_and_time": "Ημερομηνία και ώρα",
"date_before": "Ημερομηνία πριν", "date_before": "Ημερομηνία πριν",
@@ -751,7 +740,6 @@
"disallow_edits": "Απαγόρευση επεξεργασιών", "disallow_edits": "Απαγόρευση επεξεργασιών",
"discord": "Πλατφόρμα Discord", "discord": "Πλατφόρμα Discord",
"discover": "Ανίχνευση", "discover": "Ανίχνευση",
"discovered_devices": "Διαθέσιμες συσκευές",
"dismiss_all_errors": "Παράβλεψη όλων των σφαλμάτων", "dismiss_all_errors": "Παράβλεψη όλων των σφαλμάτων",
"dismiss_error": "Παράβλεψη σφάλματος", "dismiss_error": "Παράβλεψη σφάλματος",
"display_options": "Επιλογές εμφάνισης", "display_options": "Επιλογές εμφάνισης",
@@ -1108,9 +1096,6 @@
"kept_this_deleted_others": "Διατηρήθηκε αυτό το στοιχείο και διαγράφηκε/καν {count, plural, one {# στοιχείο} other {# στοιχεία}}", "kept_this_deleted_others": "Διατηρήθηκε αυτό το στοιχείο και διαγράφηκε/καν {count, plural, one {# στοιχείο} other {# στοιχεία}}",
"keyboard_shortcuts": "Συντομεύσεις πληκτρολογίου", "keyboard_shortcuts": "Συντομεύσεις πληκτρολογίου",
"language": "Γλώσσα", "language": "Γλώσσα",
"language_no_results_subtitle": "Δοκιμάστε να αλλάξετε τον όρο αναζήτησης",
"language_no_results_title": "Δε βρέθηκαν γλώσσες",
"language_search_hint": "Αναζήτηση γλωσσών...",
"language_setting_description": "Επιλέξτε τη γλώσσα που προτιμάτε", "language_setting_description": "Επιλέξτε τη γλώσσα που προτιμάτε",
"last_seen": "Τελευταία προβολή", "last_seen": "Τελευταία προβολή",
"latest_version": "Τελευταία Έκδοση", "latest_version": "Τελευταία Έκδοση",
@@ -1136,7 +1121,6 @@
"list": "Λίστα", "list": "Λίστα",
"loading": "Φόρτωση", "loading": "Φόρτωση",
"loading_search_results_failed": "Η φόρτωση αποτελεσμάτων αναζήτησης απέτυχε", "loading_search_results_failed": "Η φόρτωση αποτελεσμάτων αναζήτησης απέτυχε",
"local_asset_cast_failed": "Αδυναμία μετάδοσης στοιχείου που δεν έχει ανέβει στον διακομιστή",
"local_network": "Τοπικό δίκτυο", "local_network": "Τοπικό δίκτυο",
"local_network_sheet_info": "Η εφαρμογή θα συνδεθεί με τον διακομιστή μέσω αυτού του URL όταν χρησιμοποιείται το καθορισμένο δίκτυο Wi-Fi", "local_network_sheet_info": "Η εφαρμογή θα συνδεθεί με τον διακομιστή μέσω αυτού του URL όταν χρησιμοποιείται το καθορισμένο δίκτυο Wi-Fi",
"location_permission": "Άδεια τοποθεσίας", "location_permission": "Άδεια τοποθεσίας",
@@ -1150,7 +1134,6 @@
"locked_folder": "Κλειδωμένος φάκελος", "locked_folder": "Κλειδωμένος φάκελος",
"log_out": "Αποσύνδεση", "log_out": "Αποσύνδεση",
"log_out_all_devices": "Αποσύνδεση από Όλες τις Συσκευές", "log_out_all_devices": "Αποσύνδεση από Όλες τις Συσκευές",
"logged_in_as": "Συνδεδεμένος ως {user}",
"logged_out_all_devices": "Όλες οι συσκευές αποσυνδέθηκαν", "logged_out_all_devices": "Όλες οι συσκευές αποσυνδέθηκαν",
"logged_out_device": "Αποσυνδεδεμένη συσκευή", "logged_out_device": "Αποσυνδεδεμένη συσκευή",
"login": "Είσοδος", "login": "Είσοδος",
@@ -1182,7 +1165,7 @@
"look": "Εμφάνιση", "look": "Εμφάνιση",
"loop_videos": "Επανάληψη βίντεο", "loop_videos": "Επανάληψη βίντεο",
"loop_videos_description": "Ενεργοποιήστε την αυτόματη επανάληψη ενός βίντεο στο πρόγραμμα προβολής λεπτομερειών.", "loop_videos_description": "Ενεργοποιήστε την αυτόματη επανάληψη ενός βίντεο στο πρόγραμμα προβολής λεπτομερειών.",
"main_branch_warning": "Χρησιμοποιείτε μια έκδοση σε ανάπτυξη· συνιστούμε ανεπιφύλακτα τη χρήση μιας τελικής έκδοσης!", "main_branch_warning": "Χρησιμοποιείτε μια έκδοση σε ανάπτυξη· συνιστούμε ανεπιφύλακτα τη χρήση μιας επίσημης έκδοσης!",
"main_menu": "Κύριο μενού", "main_menu": "Κύριο μενού",
"make": "Κατασκευαστής", "make": "Κατασκευαστής",
"manage_shared_links": "Διαχείριση κοινόχρηστων συνδέσμων", "manage_shared_links": "Διαχείριση κοινόχρηστων συνδέσμων",
@@ -1278,7 +1261,6 @@
"no_archived_assets_message": "Αρχειοθετήστε φωτογραφίες και βίντεο για να τα αποκρύψετε από την Προβολή Φωτογραφιών", "no_archived_assets_message": "Αρχειοθετήστε φωτογραφίες και βίντεο για να τα αποκρύψετε από την Προβολή Φωτογραφιών",
"no_assets_message": "ΚΑΝΤΕ ΚΛΙΚ ΓΙΑ ΝΑ ΑΝΕΒΑΣΕΤΕ ΤΗΝ ΠΡΩΤΗ ΣΑΣ ΦΩΤΟΓΡΑΦΙΑ", "no_assets_message": "ΚΑΝΤΕ ΚΛΙΚ ΓΙΑ ΝΑ ΑΝΕΒΑΣΕΤΕ ΤΗΝ ΠΡΩΤΗ ΣΑΣ ΦΩΤΟΓΡΑΦΙΑ",
"no_assets_to_show": "Δεν υπάρχουν στοιχεία προς εμφάνιση", "no_assets_to_show": "Δεν υπάρχουν στοιχεία προς εμφάνιση",
"no_cast_devices_found": "Δε βρέθηκαν συσκευές μετάδοσης",
"no_duplicates_found": "Δεν βρέθηκαν διπλότυπα.", "no_duplicates_found": "Δεν βρέθηκαν διπλότυπα.",
"no_exif_info_available": "Καμία πληροφορία exif διαθέσιμη", "no_exif_info_available": "Καμία πληροφορία exif διαθέσιμη",
"no_explore_results_message": "Ανεβάστε περισσότερες φωτογραφίες για να περιηγηθείτε στη συλλογή σας.", "no_explore_results_message": "Ανεβάστε περισσότερες φωτογραφίες για να περιηγηθείτε στη συλλογή σας.",
@@ -1311,11 +1293,8 @@
"oldest_first": "Τα παλαιότερα πρώτα", "oldest_first": "Τα παλαιότερα πρώτα",
"on_this_device": "Σε αυτή τη συσκευή", "on_this_device": "Σε αυτή τη συσκευή",
"onboarding": "Οδηγός εκκίνησης", "onboarding": "Οδηγός εκκίνησης",
"onboarding_locale_description": "Επιλέξτε την γλώσσα που προτιμάτε. Μπορείτε να την αλλάξετε αργότερα από τις ρυθμίσεις.", "onboarding_privacy_description": "Οι παρακάτω (προαιρετικές) λειτουργίες βασίζονται σε εξωτερικές υπηρεσίες και μπορούν να απενεργοποιηθούν ανά πάσα στιγμή από τις ρυθμίσεις διαχείρισης.",
"onboarding_privacy_description": "Οι παρακάτω (προαιρετικές) λειτουργίες βασίζονται σε εξωτερικές υπηρεσίες και μπορούν να απενεργοποιηθούν ανά πάσα στιγμή από τις ρυθμίσεις.",
"onboarding_server_welcome_description": "Ας ξεκινήσουμε με μερικές συνηθισμένες ρυθμίσεις.",
"onboarding_theme_description": "Επιλέξτε ένα θέμα χρώματος για το προφίλ σας. Μπορείτε να το αλλάξετε αργότερα στις ρυθμίσεις σας.", "onboarding_theme_description": "Επιλέξτε ένα θέμα χρώματος για το προφίλ σας. Μπορείτε να το αλλάξετε αργότερα στις ρυθμίσεις σας.",
"onboarding_user_welcome_description": "Ας ξεκινήσουμε!",
"onboarding_welcome_user": "Καλωσόρισες, {user}", "onboarding_welcome_user": "Καλωσόρισες, {user}",
"online": "Σε σύνδεση", "online": "Σε σύνδεση",
"only_favorites": "Μόνο αγαπημένα", "only_favorites": "Μόνο αγαπημένα",
@@ -1501,7 +1480,6 @@
"remove_from_shared_link": "Αφαίρεση από τον κοινόχρηστο σύνδεσμο", "remove_from_shared_link": "Αφαίρεση από τον κοινόχρηστο σύνδεσμο",
"remove_memory": "Αφαίρεση ανάμνησης", "remove_memory": "Αφαίρεση ανάμνησης",
"remove_photo_from_memory": "Αφαίρεση φωτογραφίας από την ανάμνηση", "remove_photo_from_memory": "Αφαίρεση φωτογραφίας από την ανάμνηση",
"remove_tag": "Αφαίρεση ετικέτας",
"remove_url": "Αφαίρεση Συνδέσμου", "remove_url": "Αφαίρεση Συνδέσμου",
"remove_user": "Αφαίρεση χρήστη", "remove_user": "Αφαίρεση χρήστη",
"removed_api_key": "Αφαιρέθηκε το API Key: {name}", "removed_api_key": "Αφαιρέθηκε το API Key: {name}",
@@ -1608,7 +1586,6 @@
"select_album_cover": "Επιλέξτε εξώφυλλο άλμπουμ", "select_album_cover": "Επιλέξτε εξώφυλλο άλμπουμ",
"select_all": "Επιλογή όλων", "select_all": "Επιλογή όλων",
"select_all_duplicates": "Επιλογή όλων των διπλότυπων", "select_all_duplicates": "Επιλογή όλων των διπλότυπων",
"select_all_in": "Επιλογή όλων στο {group}",
"select_avatar_color": "Επιλέξτε χρώμα avatar", "select_avatar_color": "Επιλέξτε χρώμα avatar",
"select_face": "Επιλογή προσώπου", "select_face": "Επιλογή προσώπου",
"select_featured_photo": "Επιλέξτε φωτογραφία για προβολή", "select_featured_photo": "Επιλέξτε φωτογραφία για προβολή",
@@ -1629,7 +1606,6 @@
"server_info_box_server_url": "URL διακομιστή", "server_info_box_server_url": "URL διακομιστή",
"server_offline": "Διακομιστής Εκτός Σύνδεσης", "server_offline": "Διακομιστής Εκτός Σύνδεσης",
"server_online": "Διακομιστής Σε Σύνδεση", "server_online": "Διακομιστής Σε Σύνδεση",
"server_privacy": "Απόρρητο Διακομιστή",
"server_stats": "Στατιστικά Διακομιστή", "server_stats": "Στατιστικά Διακομιστή",
"server_version": "Έκδοση Διακομιστή", "server_version": "Έκδοση Διακομιστή",
"set": "Ορισμός", "set": "Ορισμός",
@@ -1639,7 +1615,6 @@
"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": "Ορισμός ως κύριο στοιχείο",
"setting_image_viewer_help": "Το πρόγραμμα προβολής λεπτομερειών φορτώνει πρώτα τη μικρογραφία, στη συνέχεια φορτώνει την προεπισκόπηση μεσαίου μεγέθους (αν είναι ενεργοποιημένη), τέλος φορτώνει το πρωτότυπο (αν είναι ενεργοποιημένο).", "setting_image_viewer_help": "Το πρόγραμμα προβολής λεπτομερειών φορτώνει πρώτα τη μικρογραφία, στη συνέχεια φορτώνει την προεπισκόπηση μεσαίου μεγέθους (αν είναι ενεργοποιημένη), τέλος φορτώνει το πρωτότυπο (αν είναι ενεργοποιημένο).",
"setting_image_viewer_original_subtitle": "Ενεργοποιήστε τη φόρτωση της πρωτότυπης εικόνας πλήρους ανάλυσης (μεγάλη!). Απενεργοποιήστε για να μειώσετε τη χρήση δεδομένων (τόσο στο δίκτυο όσο και στην κρυφή μνήμη της συσκευής).", "setting_image_viewer_original_subtitle": "Ενεργοποιήστε τη φόρτωση της πρωτότυπης εικόνας πλήρους ανάλυσης (μεγάλη!). Απενεργοποιήστε για να μειώσετε τη χρήση δεδομένων (τόσο στο δίκτυο όσο και στην κρυφή μνήμη της συσκευής).",
"setting_image_viewer_original_title": "Φόρτωση πρωτότυπης εικόνας", "setting_image_viewer_original_title": "Φόρτωση πρωτότυπης εικόνας",
@@ -1777,7 +1752,6 @@
"start_date": "Από", "start_date": "Από",
"state": "Νομός", "state": "Νομός",
"status": "Κατάσταση", "status": "Κατάσταση",
"stop_casting": "Διακοπή μετάδοσης",
"stop_motion_photo": "Διέκοψε την Φωτογραφία Κίνησης", "stop_motion_photo": "Διέκοψε την Φωτογραφία Κίνησης",
"stop_photo_sharing": "Διακοπή κοινής χρήσης των φωτογραφιών σας;", "stop_photo_sharing": "Διακοπή κοινής χρήσης των φωτογραφιών σας;",
"stop_photo_sharing_description": "Ο χρήστης {partner} δεν θα έχει πλέον πρόσβαση στις φωτογραφίες σας.", "stop_photo_sharing_description": "Ο χρήστης {partner} δεν θα έχει πλέον πρόσβαση στις φωτογραφίες σας.",
@@ -1836,7 +1810,7 @@
"to_trash": "Κάδος απορριμμάτων", "to_trash": "Κάδος απορριμμάτων",
"toggle_settings": "Εναλλαγή ρυθμίσεων", "toggle_settings": "Εναλλαγή ρυθμίσεων",
"total": "Σύνολο", "total": "Σύνολο",
"total_usage": "Συνολικη χρηση", "total_usage": "Συνολική χρήση",
"trash": "Κάδος απορριμμάτων", "trash": "Κάδος απορριμμάτων",
"trash_all": "Διαγραφή Όλων", "trash_all": "Διαγραφή Όλων",
"trash_count": "Διαγραφή {count, number}", "trash_count": "Διαγραφή {count, number}",
@@ -1856,7 +1830,6 @@
"unable_to_setup_pin_code": "Αδυναμία ρύθμισης κωδικού PIN", "unable_to_setup_pin_code": "Αδυναμία ρύθμισης κωδικού PIN",
"unarchive": "Αναίρεση αρχειοθέτησης", "unarchive": "Αναίρεση αρχειοθέτησης",
"unarchived_count": "{count, plural, other {Αρχειοθετήσεις αναιρέθηκαν #}}", "unarchived_count": "{count, plural, other {Αρχειοθετήσεις αναιρέθηκαν #}}",
"undo": "Αναίρεση",
"unfavorite": "Αποεπιλογή από τα αγαπημένα", "unfavorite": "Αποεπιλογή από τα αγαπημένα",
"unhide_person": "Αναίρεση απόκρυψης ατόμου", "unhide_person": "Αναίρεση απόκρυψης ατόμου",
"unknown": "Άγνωστο", "unknown": "Άγνωστο",
@@ -1873,7 +1846,6 @@
"unsaved_change": "Μη αποθηκευμένη αλλαγή", "unsaved_change": "Μη αποθηκευμένη αλλαγή",
"unselect_all": "Αποεπιλογή όλων", "unselect_all": "Αποεπιλογή όλων",
"unselect_all_duplicates": "Αποεπιλογή όλων των διπλότυπων", "unselect_all_duplicates": "Αποεπιλογή όλων των διπλότυπων",
"unselect_all_in": "Αποεπιλογή όλων στο {group}",
"unstack": "Αποστοίβαξη", "unstack": "Αποστοίβαξη",
"unstacked_assets_count": "Αποστοιβάξατε {count, plural, one {# στοιχείο} other {# στοιχεία}}", "unstacked_assets_count": "Αποστοιβάξατε {count, plural, one {# στοιχείο} other {# στοιχεία}}",
"up_next": "Ακολουθεί", "up_next": "Ακολουθεί",
@@ -1903,11 +1875,10 @@
"user_liked": "Στο χρήστη {user} αρέσει {type, select, photo {αυτή η φωτογραφία} video {αυτό το βίντεο} asset {αυτό το αντικείμενο} other {it}}", "user_liked": "Στο χρήστη {user} αρέσει {type, select, photo {αυτή η φωτογραφία} video {αυτό το βίντεο} asset {αυτό το αντικείμενο} other {it}}",
"user_pin_code_settings": "Κωδικός PIN", "user_pin_code_settings": "Κωδικός PIN",
"user_pin_code_settings_description": "Διαχειριστείτε τον κωδικό PIN σας", "user_pin_code_settings_description": "Διαχειριστείτε τον κωδικό PIN σας",
"user_privacy": "Απόρρητο Χρήστη",
"user_purchase_settings": "Αγορά", "user_purchase_settings": "Αγορά",
"user_purchase_settings_description": "Διαχείριση Αγοράς", "user_purchase_settings_description": "Διαχείριση Αγοράς",
"user_role_set": "Ορισμός {user} ως {role}", "user_role_set": "Ορισμός {user} ως {role}",
"user_usage_detail": "Λεπτομερειες χρησης του χρηστη", "user_usage_detail": "Λεπτομέρειες χρήσης του χρήστη",
"user_usage_stats": "Στατιστικά χρήσης λογαριασμού", "user_usage_stats": "Στατιστικά χρήσης λογαριασμού",
"user_usage_stats_description": "Προβολή στατιστικών χρήσης λογαριασμού", "user_usage_stats_description": "Προβολή στατιστικών χρήσης λογαριασμού",
"username": "Όνομα Χρήστη", "username": "Όνομα Χρήστη",

View File

@@ -166,20 +166,6 @@
"metadata_settings_description": "Manage metadata settings", "metadata_settings_description": "Manage metadata settings",
"migration_job": "Migration", "migration_job": "Migration",
"migration_job_description": "Migrate thumbnails for assets and faces to the latest folder structure", "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_paths_added": "No paths added",
"no_pattern_added": "No pattern 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_apply_storage_label_previous_assets": "Note: To apply the Storage Label to previously uploaded assets, run the",
@@ -210,8 +196,6 @@
"oauth_mobile_redirect_uri": "Mobile redirect URI", "oauth_mobile_redirect_uri": "Mobile redirect URI",
"oauth_mobile_redirect_uri_override": "Mobile redirect URI override", "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_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": "OAuth",
"oauth_settings_description": "Manage OAuth login settings", "oauth_settings_description": "Manage OAuth login settings",
"oauth_settings_more_details": "For more details about this feature, refer to the <link>docs</link>.", "oauth_settings_more_details": "For more details about this feature, refer to the <link>docs</link>.",
@@ -260,7 +244,7 @@
"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 <link>{job}</link>.", "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 <link>{job}</link>.",
"storage_template_migration_job": "Storage Template Migration Job", "storage_template_migration_job": "Storage Template Migration Job",
"storage_template_more_details": "For more details about this feature, refer to the <template-link>Storage Template</template-link> and its <implications-link>implications</implications-link>", "storage_template_more_details": "For more details about this feature, refer to the <template-link>Storage Template</template-link> and its <implications-link>implications</implications-link>",
"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 <link>documentation</link>.", "storage_template_onboarding_description": "When enabled, this feature will auto-organize files based on a user-defined template. Due to stability issues the feature has been turned off by default. For more information, please see the <link>documentation</link>.",
"storage_template_path_length": "Approximate path length limit: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "Approximate path length limit: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "Storage Template", "storage_template_settings": "Storage Template",
"storage_template_settings_description": "Manage the folder structure and file name of the upload asset", "storage_template_settings_description": "Manage the folder structure and file name of the upload asset",
@@ -443,7 +427,6 @@
"app_settings": "App Settings", "app_settings": "App Settings",
"appears_in": "Appears in", "appears_in": "Appears in",
"archive": "Archive", "archive": "Archive",
"archive_action_prompt": "{count} added to Archive",
"archive_or_unarchive_photo": "Archive or unarchive photo", "archive_or_unarchive_photo": "Archive or unarchive photo",
"archive_page_no_archived_assets": "No archived assets found", "archive_page_no_archived_assets": "No archived assets found",
"archive_page_title": "Archive ({count})", "archive_page_title": "Archive ({count})",
@@ -481,6 +464,7 @@
"assets": "Assets", "assets": "Assets",
"assets_added_count": "Added {count, plural, one {# asset} other {# 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_album_count": "Added {count, plural, one {# asset} other {# assets}} to the album",
"assets_added_to_name_count": "Added {count, plural, one {# asset} other {# assets}} to {hasName, select, true {<b>{name}</b>} other {new album}}",
"assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} cannot be added to the album", "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} cannot be added to the album",
"assets_count": "{count, plural, one {# asset} other {# assets}}", "assets_count": "{count, plural, one {# asset} other {# assets}}",
"assets_deleted_permanently": "{count} asset(s) deleted permanently", "assets_deleted_permanently": "{count} asset(s) deleted permanently",
@@ -719,7 +703,7 @@
"daily_title_text_date": "E, MMM dd", "daily_title_text_date": "E, MMM dd",
"daily_title_text_date_year": "E, MMM dd, yyyy", "daily_title_text_date_year": "E, MMM dd, yyyy",
"dark": "Dark", "dark": "Dark",
"dark_theme": "Toggle dark theme", "darkTheme": "Toggle dark theme",
"date_after": "Date after", "date_after": "Date after",
"date_and_time": "Date and Time", "date_and_time": "Date and Time",
"date_before": "Date before", "date_before": "Date before",
@@ -735,7 +719,6 @@
"default_locale": "Default Locale", "default_locale": "Default Locale",
"default_locale_description": "Format dates and numbers based on your browser locale", "default_locale_description": "Format dates and numbers based on your browser locale",
"delete": "Delete", "delete": "Delete",
"delete_action_prompt": "{count} deleted permanently",
"delete_album": "Delete album", "delete_album": "Delete album",
"delete_api_key_prompt": "Are you sure you want to delete this API key?", "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": "These items will be permanently deleted from Immich and from your device",
@@ -749,7 +732,6 @@
"delete_key": "Delete key", "delete_key": "Delete key",
"delete_library": "Delete Library", "delete_library": "Delete Library",
"delete_link": "Delete link", "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_backed_up_only": "Delete Backed Up Only",
"delete_local_dialog_ok_force": "Delete Anyway", "delete_local_dialog_ok_force": "Delete Anyway",
"delete_others": "Delete others", "delete_others": "Delete others",
@@ -817,7 +799,6 @@
"edit_key": "Edit key", "edit_key": "Edit key",
"edit_link": "Edit link", "edit_link": "Edit link",
"edit_location": "Edit location", "edit_location": "Edit location",
"edit_location_action_prompt": "{count} location edited",
"edit_location_dialog_title": "Location", "edit_location_dialog_title": "Location",
"edit_name": "Edit name", "edit_name": "Edit name",
"edit_people": "Edit people", "edit_people": "Edit people",
@@ -1003,7 +984,6 @@
"failed_to_load_assets": "Failed to load assets", "failed_to_load_assets": "Failed to load assets",
"failed_to_load_folder": "Failed to load folder", "failed_to_load_folder": "Failed to load folder",
"favorite": "Favorite", "favorite": "Favorite",
"favorite_action_prompt": "{count} added to Favorites",
"favorite_or_unfavorite_photo": "Favorite or unfavorite photo", "favorite_or_unfavorite_photo": "Favorite or unfavorite photo",
"favorites": "Favorites", "favorites": "Favorites",
"favorites_page_no_favorites": "No favorite assets found", "favorites_page_no_favorites": "No favorite assets found",
@@ -1147,7 +1127,6 @@
"library_page_sort_created": "Created date", "library_page_sort_created": "Created date",
"library_page_sort_last_modified": "Last modified", "library_page_sort_last_modified": "Last modified",
"library_page_sort_title": "Album title", "library_page_sort_title": "Album title",
"licenses": "Licenses",
"light": "Light", "light": "Light",
"like_deleted": "Like deleted", "like_deleted": "Like deleted",
"link_motion_video": "Link motion video", "link_motion_video": "Link motion video",
@@ -1267,7 +1246,6 @@
"more": "More", "more": "More",
"move": "Move", "move": "Move",
"move_off_locked_folder": "Move out of locked folder", "move_off_locked_folder": "Move out of locked folder",
"move_to_lock_folder_action_prompt": "{count} added to the locked folder",
"move_to_locked_folder": "Move to 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_to_locked_folder_confirmation": "These photos and video will be removed from all albums, and only viewable from the locked folder",
"moved_to_archive": "Moved {count, plural, one {# asset} other {# assets}} to archive", "moved_to_archive": "Moved {count, plural, one {# asset} other {# assets}} to archive",
@@ -1517,9 +1495,7 @@
"remove_custom_date_range": "Remove custom date range", "remove_custom_date_range": "Remove custom date range",
"remove_deleted_assets": "Remove Deleted Assets", "remove_deleted_assets": "Remove Deleted Assets",
"remove_from_album": "Remove from album", "remove_from_album": "Remove from album",
"remove_from_album_action_prompt": "{count} removed from the album",
"remove_from_favorites": "Remove from favorites", "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": "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_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_from_shared_link": "Remove from shared link",
@@ -1862,7 +1838,6 @@
"total": "Total", "total": "Total",
"total_usage": "Total usage", "total_usage": "Total usage",
"trash": "Trash", "trash": "Trash",
"trash_action_prompt": "{count} moved to trash",
"trash_all": "Trash All", "trash_all": "Trash All",
"trash_count": "Trash {count, number}", "trash_count": "Trash {count, number}",
"trash_delete_asset": "Trash/Delete Asset", "trash_delete_asset": "Trash/Delete Asset",
@@ -1880,11 +1855,9 @@
"unable_to_change_pin_code": "Unable to change PIN code", "unable_to_change_pin_code": "Unable to change PIN code",
"unable_to_setup_pin_code": "Unable to setup PIN code", "unable_to_setup_pin_code": "Unable to setup PIN code",
"unarchive": "Unarchive", "unarchive": "Unarchive",
"unarchive_action_prompt": "{count} removed from Archive",
"unarchived_count": "{count, plural, other {Unarchived #}}", "unarchived_count": "{count, plural, other {Unarchived #}}",
"undo": "Undo", "undo": "Undo",
"unfavorite": "Unfavorite", "unfavorite": "Unfavorite",
"unfavorite_action_prompt": "{count} removed from Favorites",
"unhide_person": "Unhide person", "unhide_person": "Unhide person",
"unknown": "Unknown", "unknown": "Unknown",
"unknown_country": "Unknown Country", "unknown_country": "Unknown Country",
@@ -1903,7 +1876,6 @@
"unselect_all_in": "Unselect all in {group}", "unselect_all_in": "Unselect all in {group}",
"unstack": "Un-stack", "unstack": "Un-stack",
"unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}", "unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}",
"untagged": "Untagged",
"up_next": "Up next", "up_next": "Up next",
"updated_at": "Updated", "updated_at": "Updated",
"updated_password": "Updated password", "updated_password": "Updated password",
@@ -1940,7 +1912,6 @@
"user_usage_stats_description": "View account usage statistics", "user_usage_stats_description": "View account usage statistics",
"username": "Username", "username": "Username",
"users": "Users", "users": "Users",
"users_added_to_album_count": "Added {count, plural, one {# user} other {# users}} to the album",
"utilities": "Utilities", "utilities": "Utilities",
"validate": "Validate", "validate": "Validate",
"validate_endpoint_error": "Please enter a valid URL", "validate_endpoint_error": "Please enter a valid URL",

View File

@@ -34,7 +34,6 @@
"added_to_favorites_count": "Agregado {count, number} a favoritos", "added_to_favorites_count": "Agregado {count, number} a favoritos",
"admin": { "admin": {
"add_exclusion_pattern_description": "Agrega 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/**\".", "add_exclusion_pattern_description": "Agrega 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 admin",
"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.", "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": "Parámetros de autenticación",
"authentication_settings_description": "Gestionar contraseñas, OAuth y otros parámetros de autenticación", "authentication_settings_description": "Gestionar contraseñas, OAuth y otros parámetros de autenticación",
@@ -204,7 +203,7 @@
"oauth_storage_quota_claim": "Reclamar quota de almacenamiento", "oauth_storage_quota_claim": "Reclamar quota de almacenamiento",
"oauth_storage_quota_claim_description": "Establezca automáticamente la cuota de almacenamiento del usuario al valor de esta solicitud.", "oauth_storage_quota_claim_description": "Establezca automáticamente la cuota de almacenamiento del usuario al valor de esta solicitud.",
"oauth_storage_quota_default": "Cuota de almacenamiento predeterminada (GiB)", "oauth_storage_quota_default": "Cuota de almacenamiento predeterminada (GiB)",
"oauth_storage_quota_default_description": "Cuota en GiB que se utilizará cuando no se proporcione ninguna por defecto.", "oauth_storage_quota_default_description": "Cuota en GiB que se utilizará cuando no se proporcione ninguna por defecto (ingrese 0 para una cuota ilimitada).",
"oauth_timeout": "Expiración de solicitud", "oauth_timeout": "Expiración de solicitud",
"oauth_timeout_description": "Tiempo de espera de solicitudes en milisegundos", "oauth_timeout_description": "Tiempo de espera de solicitudes en milisegundos",
"password_enable_description": "Iniciar sesión con correo electrónico y contraseña", "password_enable_description": "Iniciar sesión con correo electrónico y contraseña",
@@ -244,7 +243,7 @@
"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 <link>{job}</link>.", "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 <link>{job}</link>.",
"storage_template_migration_job": "Tarea de migración de la plantilla de almacenamiento", "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 <template-link>Plantilla de almacenamiento</template-link> y sus <implications-link>implicaciones</implications-link>", "storage_template_more_details": "Para obtener más detalles sobre esta función, consulte la <template-link>Plantilla de almacenamiento</template-link> y sus <implications-link>implicaciones</implications-link>",
"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 <link>documentación</link>.", "storage_template_onboarding_description": "Cuando está habilitada, esta función organizará automáticamente los archivos según una plantilla definida por el usuario. Debido a problemas de estabilidad, la función se ha desactivado de forma predeterminada. Para obtener más información, consulte la <link>documentación</link>.",
"storage_template_path_length": "Límite aproximado de la longitud de la ruta: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "Límite aproximado de la longitud de la ruta: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "Plantilla de almacenamiento", "storage_template_settings": "Plantilla de almacenamiento",
"storage_template_settings_description": "Administrar la estructura de carpetas y el nombre de archivo del recurso cargado", "storage_template_settings_description": "Administrar la estructura de carpetas y el nombre de archivo del recurso cargado",
@@ -465,12 +464,9 @@
"assets_added_count": "Añadido {count, plural, one {# asset} other {# assets}}", "assets_added_count": "Añadido {count, plural, one {# asset} other {# assets}}",
"assets_added_to_album_count": "Añadido {count, plural, one {# asset} other {# assets}} al álbum", "assets_added_to_album_count": "Añadido {count, plural, one {# asset} other {# assets}} al álbum",
"assets_added_to_name_count": "Añadido {count, plural, one {# asset} other {# assets}} a {hasName, select, true {<b>{name}</b>} other {new album}}", "assets_added_to_name_count": "Añadido {count, plural, one {# asset} other {# assets}} a {hasName, select, true {<b>{name}</b>} other {new album}}",
"assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} no pueden ser añadidos al album",
"assets_count": "{count, plural, one {# activo} other {# activos}}", "assets_count": "{count, plural, one {# activo} other {# activos}}",
"assets_deleted_permanently": "{count} elemento(s) eliminado(s) permanentemente", "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_deleted_permanently_from_server": "{count} recurso(s) eliminado(s) de forma permanente del servidor de Immich",
"assets_downloaded_failed": "{count, plural, one {Descargado archivo # - {error} archivo fallido} other {Descargados # archivos - {error} archivos fallidos}}",
"assets_downloaded_successfully": "{count, plural, one {Archivo # descargado correctamente} other {Archivos # descargados correctamente}}",
"assets_moved_to_trash_count": "{count, plural, one {# elemento movido} other {# elementos movidos}} a la papelera", "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_permanently_deleted_count": "Eliminado permanentemente {count, plural, one {# elemento} other {# elementos}}",
"assets_removed_count": "Eliminado {count, plural, one {# elemento} other {# elementos}}", "assets_removed_count": "Eliminado {count, plural, one {# elemento} other {# elementos}}",
@@ -749,6 +745,7 @@
"direction": "Dirección", "direction": "Dirección",
"disabled": "Deshabilitado", "disabled": "Deshabilitado",
"disallow_edits": "Bloquear edición", "disallow_edits": "Bloquear edición",
"discord": "Discord",
"discover": "Descubrir", "discover": "Descubrir",
"discovered_devices": "Dispositivos descubiertos", "discovered_devices": "Dispositivos descubiertos",
"dismiss_all_errors": "Descartar todos los errores", "dismiss_all_errors": "Descartar todos los errores",
@@ -1097,7 +1094,7 @@
"ios_debug_info_last_sync_at": "Última sincronizació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_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_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_processes_queued": "{count, plural, un {{count} proceso de segundo plano en cola} otros {{count} procesos de segundo plano en cola}}",
"ios_debug_info_processing_ran_at": "El procesamiento se ejecutó el {dateTime}", "ios_debug_info_processing_ran_at": "El procesamiento se ejecutó el {dateTime}",
"items_count": "{count, plural, one {# elemento} other {# elementos}}", "items_count": "{count, plural, one {# elemento} other {# elementos}}",
"jobs": "Tareas", "jobs": "Tareas",
@@ -1149,7 +1146,6 @@
"locked_folder": "Carpeta bloqueada", "locked_folder": "Carpeta bloqueada",
"log_out": "Cerrar sesión", "log_out": "Cerrar sesión",
"log_out_all_devices": "Cerrar sesión en todos los dispositivos", "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_all_devices": "Se ha cerrado la sesión en todos los dispositivos",
"logged_out_device": "Dispositivo desconectado", "logged_out_device": "Dispositivo desconectado",
"login": "Inicio de sesión", "login": "Inicio de sesión",
@@ -1277,7 +1273,7 @@
"no_archived_assets_message": "Archive fotos y videos para ocultarlos de su vista de Fotos", "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_message": "HAZ CLIC PARA SUBIR TU PRIMERA FOTO",
"no_assets_to_show": "No hay elementos a mostrar", "no_assets_to_show": "No hay elementos a mostrar",
"no_cast_devices_found": "Dispositivos de difusión no encontrados", "no_cast_devices_found": "Dispositivos de cast no encontrados",
"no_duplicates_found": "No se encontraron duplicados.", "no_duplicates_found": "No se encontraron duplicados.",
"no_exif_info_available": "No hay información exif disponible", "no_exif_info_available": "No hay información exif disponible",
"no_explore_results_message": "Sube más fotos para explorar tu colección.", "no_explore_results_message": "Sube más fotos para explorar tu colección.",
@@ -1500,7 +1496,6 @@
"remove_from_shared_link": "Eliminar desde enlace compartido", "remove_from_shared_link": "Eliminar desde enlace compartido",
"remove_memory": "Quitar memoria", "remove_memory": "Quitar memoria",
"remove_photo_from_memory": "Quitar foto de esta memoria", "remove_photo_from_memory": "Quitar foto de esta memoria",
"remove_tag": "Quitar etiqueta",
"remove_url": "Eliminar URL", "remove_url": "Eliminar URL",
"remove_user": "Eliminar usuario", "remove_user": "Eliminar usuario",
"removed_api_key": "Clave API eliminada: {name}", "removed_api_key": "Clave API eliminada: {name}",
@@ -1607,7 +1602,6 @@
"select_album_cover": "Seleccionar portada del álbum", "select_album_cover": "Seleccionar portada del álbum",
"select_all": "Seleccionar todo", "select_all": "Seleccionar todo",
"select_all_duplicates": "Seleccionar todos los duplicados", "select_all_duplicates": "Seleccionar todos los duplicados",
"select_all_in": "Selecciona todos en {group}",
"select_avatar_color": "Seleccionar color del avatar", "select_avatar_color": "Seleccionar color del avatar",
"select_face": "Seleccionar cara", "select_face": "Seleccionar cara",
"select_featured_photo": "Seleccionar foto principal", "select_featured_photo": "Seleccionar foto principal",
@@ -1776,7 +1770,6 @@
"start_date": "Fecha de inicio", "start_date": "Fecha de inicio",
"state": "Estado", "state": "Estado",
"status": "Estado", "status": "Estado",
"stop_casting": "Parar difusión",
"stop_motion_photo": "Parar foto en movimiento", "stop_motion_photo": "Parar foto en movimiento",
"stop_photo_sharing": "¿Dejar de compartir tus fotos?", "stop_photo_sharing": "¿Dejar de compartir tus fotos?",
"stop_photo_sharing_description": "{partner} ya no podrá acceder a tus fotos.", "stop_photo_sharing_description": "{partner} ya no podrá acceder a tus fotos.",
@@ -1872,7 +1865,6 @@
"unsaved_change": "Cambio no guardado", "unsaved_change": "Cambio no guardado",
"unselect_all": "Limpiar selección", "unselect_all": "Limpiar selección",
"unselect_all_duplicates": "Deseleccionar todos los duplicados", "unselect_all_duplicates": "Deseleccionar todos los duplicados",
"unselect_all_in": "Deselecciona todos en {group}",
"unstack": "Desapilar", "unstack": "Desapilar",
"unstacked_assets_count": "Desapilado(s) {count, plural, one {# elemento} other {# elementos}}", "unstacked_assets_count": "Desapilado(s) {count, plural, one {# elemento} other {# elementos}}",
"up_next": "A continuación", "up_next": "A continuación",

View File

@@ -34,7 +34,6 @@
"added_to_favorites_count": "{count, number} pilti lisatud lemmikutesse", "added_to_favorites_count": "{count, number} pilti lisatud lemmikutesse",
"admin": { "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 failide ignoreerimiseks kasuta \"**/*.tif\". Absouutse tee ignoreerimiseks kasuta \"/path/to/ignore/**\".", "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 failide ignoreerimiseks kasuta \"**/*.tif\". Absouutse tee ignoreerimiseks kasuta \"/path/to/ignore/**\".",
"admin_user": "Administraator",
"asset_offline_description": "Seda välise kogu üksust ei leitud kettalt ning see liigutati prügikasti. Kui faili asukoht kogu siseselt muutus, leiad vastava uue üksuse oma ajajoonelt. Üksuse taastamiseks veendu, et allpool toodud failitee on Immich'ile kättesaadav ning skaneeri kogu uuesti.", "asset_offline_description": "Seda välise kogu üksust ei leitud kettalt ning see liigutati prügikasti. Kui faili asukoht kogu siseselt muutus, 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": "Autentimise seaded",
"authentication_settings_description": "Halda parooli, OAuth ja muid autentimise seadeid", "authentication_settings_description": "Halda parooli, OAuth ja muid autentimise seadeid",
@@ -204,7 +203,7 @@
"oauth_storage_quota_claim": "Talletuskvoodi väide", "oauth_storage_quota_claim": "Talletuskvoodi väide",
"oauth_storage_quota_claim_description": "Sea kasutaja talletuskvoodiks automaatselt selle väite väärtus.", "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": "Vaikimisi talletuskvoot (GiB)",
"oauth_storage_quota_default_description": "Kvoot (GiB), mida kasutada, kui ühtegi väidet pole esitatud.", "oauth_storage_quota_default_description": "Kvoot (GiB), mida kasutada, kui ühtegi väidet pole esitatud (piiramatu kvoodi jaoks sisesta 0).",
"oauth_timeout": "Päringu ajalõpp", "oauth_timeout": "Päringu ajalõpp",
"oauth_timeout_description": "Päringute ajalõpp millisekundites", "oauth_timeout_description": "Päringute ajalõpp millisekundites",
"password_enable_description": "Logi sisse e-posti aadressi ja parooliga", "password_enable_description": "Logi sisse e-posti aadressi ja parooliga",
@@ -244,7 +243,7 @@
"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 <link>{job}</link>.", "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 <link>{job}</link>.",
"storage_template_migration_job": "Talletusmallide migreerimise tööde", "storage_template_migration_job": "Talletusmallide migreerimise tööde",
"storage_template_more_details": "Et selle funktsiooni kohta rohkem teada saada, loe <template-link>talletusmallide</template-link> ja nende <implications-link>tagajärgede</implications-link> kohta", "storage_template_more_details": "Et selle funktsiooni kohta rohkem teada saada, loe <template-link>talletusmallide</template-link> ja nende <implications-link>tagajärgede</implications-link> kohta",
"storage_template_onboarding_description_v2": "Kui lubatud, organiseeritakse failid automaatselt kasutaja määratud malli alusel. Rohkem infot leiad <link>dokumentatsioonist</link>.", "storage_template_onboarding_description": "Kui sisse lülitatud, võimaldab see faile kasutaja määratud malli alusel automaatselt organiseerida. Stabiilsusprobleemide tõttu on see funktsioon vaikimisi välja lülitatud. Rohkem infot leiad <link>dokumentatsioonist</link>.",
"storage_template_path_length": "Tee pikkuse umbkaudne limiit: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "Tee pikkuse umbkaudne limiit: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "Talletusmall", "storage_template_settings": "Talletusmall",
"storage_template_settings_description": "Halda üleslaaditud üksuse kaustastruktuuri ja failinime", "storage_template_settings_description": "Halda üleslaaditud üksuse kaustastruktuuri ja failinime",
@@ -1150,7 +1149,6 @@
"locked_folder": "Lukustatud kaust", "locked_folder": "Lukustatud kaust",
"log_out": "Logi välja", "log_out": "Logi välja",
"log_out_all_devices": "Logi kõigist seadmetest 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_all_devices": "Kõigist seadmetest välja logitud",
"logged_out_device": "Seadmest välja logitud", "logged_out_device": "Seadmest välja logitud",
"login": "Logi sisse", "login": "Logi sisse",
@@ -1608,7 +1606,6 @@
"select_album_cover": "Vali albumi kaanepilt", "select_album_cover": "Vali albumi kaanepilt",
"select_all": "Vali kõik", "select_all": "Vali kõik",
"select_all_duplicates": "Vali kõik duplikaadid", "select_all_duplicates": "Vali kõik duplikaadid",
"select_all_in": "Vali kõik grupis {group}",
"select_avatar_color": "Vali avatari värv", "select_avatar_color": "Vali avatari värv",
"select_face": "Vali nägu", "select_face": "Vali nägu",
"select_featured_photo": "Vali esiletõstetud foto", "select_featured_photo": "Vali esiletõstetud foto",
@@ -1873,7 +1870,6 @@
"unsaved_change": "Salvestamata muudatus", "unsaved_change": "Salvestamata muudatus",
"unselect_all": "Ära vali ühtegi", "unselect_all": "Ära vali ühtegi",
"unselect_all_duplicates": "Ära vali duplikaate", "unselect_all_duplicates": "Ära vali duplikaate",
"unselect_all_in": "Ära vali ühtegi grupis {group}",
"unstack": "Eralda", "unstack": "Eralda",
"unstacked_assets_count": "{count, plural, one {# üksus} other {# üksust}} eraldatud", "unstacked_assets_count": "{count, plural, one {# üksus} other {# üksust}} eraldatud",
"up_next": "Järgmine", "up_next": "Järgmine",

View File

@@ -192,6 +192,7 @@
"storage_template_migration_info": "تغییرات قالب فقط به دارایی‌های جدید اعمال خواهد شد. برای اعمال قالب به دارایی‌های بارگذاری شده قبلی، باید <link>{job}</link> را اجرا کنید.", "storage_template_migration_info": "تغییرات قالب فقط به دارایی‌های جدید اعمال خواهد شد. برای اعمال قالب به دارایی‌های بارگذاری شده قبلی، باید <link>{job}</link> را اجرا کنید.",
"storage_template_migration_job": "وظیفه مهاجرت الگوی ذخیره‌سازی", "storage_template_migration_job": "وظیفه مهاجرت الگوی ذخیره‌سازی",
"storage_template_more_details": "برای جزئیات بیشتر درباره این ویژگی، به <template-link>قالب ذخیره‌سازی</template-link> و <implications-link>مفاهیم</implications-link> آن مراجعه کنید", "storage_template_more_details": "برای جزئیات بیشتر درباره این ویژگی، به <template-link>قالب ذخیره‌سازی</template-link> و <implications-link>مفاهیم</implications-link> آن مراجعه کنید",
"storage_template_onboarding_description": "زمانی که این ویژگی فعال شود، فایل‌ها به‌طور خودکار بر اساس یک قالب تعریف‌شده توسط کاربر سازماندهی می‌شوند. به دلیل مشکلات پایداری، این ویژگی به‌طور پیش‌فرض غیرفعال است. برای اطلاعات بیشتر، لطفاً به <link>مستندات</link> مراجعه کنید.",
"storage_template_path_length": "حداکثر طول مسیر تقریبی: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "حداکثر طول مسیر تقریبی: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "قالب ذخیره‌سازی", "storage_template_settings": "قالب ذخیره‌سازی",
"storage_template_settings_description": "مدیریت ساختار پوشه و نام فایل دارایی بارگذاری شده", "storage_template_settings_description": "مدیریت ساختار پوشه و نام فایل دارایی بارگذاری شده",

View File

@@ -22,7 +22,6 @@
"add_partner": "Lisää kumppani", "add_partner": "Lisää kumppani",
"add_path": "Lisää polku", "add_path": "Lisää polku",
"add_photos": "Lisää kuvia", "add_photos": "Lisää kuvia",
"add_tag": "Lisää tunniste",
"add_to": "Lisää…", "add_to": "Lisää…",
"add_to_album": "Lisää albumiin", "add_to_album": "Lisää albumiin",
"add_to_album_bottom_sheet_added": "Lisätty albumiin {album}", "add_to_album_bottom_sheet_added": "Lisätty albumiin {album}",
@@ -34,7 +33,6 @@
"added_to_favorites_count": "{count, number} lisätty suosikkeihin", "added_to_favorites_count": "{count, number} lisätty suosikkeihin",
"admin": { "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/**\".", "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. Palautaaksesi tämän resurssin, varmista, että alla oleva tiedostopolku on Immichin käytettävissä ja skannaa kirjasto uudelleen.", "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. Palautaaksesi tämän resurssin, varmista, että alla oleva tiedostopolku on Immichin käytettävissä ja skannaa kirjasto uudelleen.",
"authentication_settings": "Autentikointiasetukset", "authentication_settings": "Autentikointiasetukset",
"authentication_settings_description": "Hallitse salasana-, OAuth- ja muut autentikoinnin asetukset", "authentication_settings_description": "Hallitse salasana-, OAuth- ja muut autentikoinnin asetukset",
@@ -171,7 +169,7 @@
"note_apply_storage_label_previous_assets": "Huom: Asettaaksesi nimikkeen aiemmin ladatulle aineistolle, aja", "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!", "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": "Lähettäjän osoite",
"notification_email_from_address_description": "Lähettäjän sähköpostiosoite. Esimerkiksi \"Immich-kuvapalvelin <noreply@example.com>\". Varmista, että käytetystä osoiteesta on lupa lähettää sähköposteja.", "notification_email_from_address_description": "Lähettäjän sähköpostiosoite. Esimerkiksi \"Immich-kuvapalvelin <noreply@example.com>\"",
"notification_email_host_description": "Sähköpostipalvelin (esim. smtp.immich.app)", "notification_email_host_description": "Sähköpostipalvelin (esim. smtp.immich.app)",
"notification_email_ignore_certificate_errors": "Älä huomioi varmennevirheitä", "notification_email_ignore_certificate_errors": "Älä huomioi varmennevirheitä",
"notification_email_ignore_certificate_errors_description": "Älä huomioi TLS-varmenteiden validointivirheitä (ei suositeltu)", "notification_email_ignore_certificate_errors_description": "Älä huomioi TLS-varmenteiden validointivirheitä (ei suositeltu)",
@@ -204,7 +202,7 @@
"oauth_storage_quota_claim": "Tallennustilan kiintiön väittämä (claim)", "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_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": "Tallennustilan oletuskiintiö (Gt)",
"oauth_storage_quota_default_description": "Käytettävä kiintiön määrä gigatavuissa, kun väittämää ei ole annettu.", "oauth_storage_quota_default_description": "Käytettävä kiintiön määrä gigatavuissa, käytetään kun väittämää ei ole annettu (0 rajoittamaton kiintiö).",
"oauth_timeout": "Pyynnön aikakatkaisu", "oauth_timeout": "Pyynnön aikakatkaisu",
"oauth_timeout_description": "Pyyntöjen aikakatkaisu millisekunteina", "oauth_timeout_description": "Pyyntöjen aikakatkaisu millisekunteina",
"password_enable_description": "Kirjaudu käyttäen sähköpostiosoitetta ja salasanaa", "password_enable_description": "Kirjaudu käyttäen sähköpostiosoitetta ja salasanaa",
@@ -244,6 +242,7 @@
"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 <link>{job}</link>.", "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 <link>{job}</link>.",
"storage_template_migration_job": "Tallennustilan mallin muutostyö", "storage_template_migration_job": "Tallennustilan mallin muutostyö",
"storage_template_more_details": "Saadaksesi lisätietoa tästä ominaisuudesta, katso <template-link>Tallennustilan Mallit</template-link> sekä <implications-link>mihin se vaikuttaa</implications-link>", "storage_template_more_details": "Saadaksesi lisätietoa tästä ominaisuudesta, katso <template-link>Tallennustilan Mallit</template-link> sekä <implications-link>mihin se vaikuttaa</implications-link>",
"storage_template_onboarding_description": "Kun tämä ominaisuus on käytössä, se järjestää tiedostot automaattisesti käyttäjän määrittämän mallin perusteella. Vakausongelmien vuoksi ominaisuus on oletuksena poistettu käytöstä. Lisätietoja on <link>dokumentaatiossa</link>.",
"storage_template_path_length": "Arvioitu tiedostopolun pituusrajoitus: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "Arvioitu tiedostopolun pituusrajoitus: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "Tallennustilan malli", "storage_template_settings": "Tallennustilan malli",
"storage_template_settings_description": "Hallitse palvelimelle ladatun aineiston kansiorakennetta ja tiedostonimiä", "storage_template_settings_description": "Hallitse palvelimelle ladatun aineiston kansiorakennetta ja tiedostonimiä",

View File

@@ -34,7 +34,6 @@
"added_to_favorites_count": "{count, number} ajouté(s) aux favoris", "added_to_favorites_count": "{count, number} ajouté(s) aux favoris",
"admin": { "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/** ».", "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.", "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": "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_description": "Gérer le mot de passe, l'authentification OAuth et d'autres paramètres d'authentification",
@@ -168,7 +167,7 @@
"migration_job_description": "Migration des miniatures pour les médias et les visages vers la dernière structure de dossiers", "migration_job_description": "Migration des miniatures pour les médias et les visages vers la dernière structure de dossiers",
"no_paths_added": "Aucun chemin n'a été ajouté", "no_paths_added": "Aucun chemin n'a été ajouté",
"no_pattern_added": "Aucun schéma d'exclusion 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_apply_storage_label_previous_assets": "Remarque : pour appliquer l'étiquette de stockage à des médias précédemment téléversés, exécutez",
"note_cannot_be_changed_later": "REMARQUE: Il n'est pas possible de modifier ce paramètre ultérieurement!", "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": "Depuis l'adresse",
"notification_email_from_address_description": "Adresse courriel de l'expéditeur, par exemple : « Serveur de photos Immich <nepasrepondre@exemple.org> ». Assurez-vous d'utiliser une adresse à partir de laquelle vous pouvez envoyer des courriels.", "notification_email_from_address_description": "Adresse courriel de l'expéditeur, par exemple : « Serveur de photos Immich <nepasrepondre@exemple.org> ». Assurez-vous d'utiliser une adresse à partir de laquelle vous pouvez envoyer des courriels.",
@@ -195,7 +194,7 @@
"oauth_enable_description": "Connexion avec OAuth", "oauth_enable_description": "Connexion avec OAuth",
"oauth_mobile_redirect_uri": "URI de redirection mobile", "oauth_mobile_redirect_uri": "URI de redirection mobile",
"oauth_mobile_redirect_uri_override": "Remplacer l'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_mobile_redirect_uri_override_description": "Activer quand le fournisseur d'OAuth ne permet pas un URI mobile, comme '{callback} '",
"oauth_settings": "OAuth", "oauth_settings": "OAuth",
"oauth_settings_description": "Gérer les paramètres de connexion 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 <link>ce lien</link>.", "oauth_settings_more_details": "Pour plus de détails sur cette fonctionnalité, consultez <link>ce lien</link>.",
@@ -204,7 +203,7 @@
"oauth_storage_quota_claim": "Demande de quota de stockage", "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_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": "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_storage_quota_default_description": "Quota en Go à utiliser lorsqu'aucune valeur n'est précisée (saisir 0 pour un quota illimité).",
"oauth_timeout": "Expiration de la durée de la requête", "oauth_timeout": "Expiration de la durée de la requête",
"oauth_timeout_description": "Délai d'expiration des requêtes en millisecondes", "oauth_timeout_description": "Délai d'expiration des requêtes en millisecondes",
"password_enable_description": "Connexion avec courriel et mot de passe", "password_enable_description": "Connexion avec courriel et mot de passe",
@@ -240,14 +239,14 @@
"storage_template_hash_verification_enabled": "Vérification du hachage activée", "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_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": "Migration du modèle de stockage",
"storage_template_migration_description": "Appliquer le modèle courant <link>{template}</link> aux médias précédemment envoyés", "storage_template_migration_description": "Appliquer le modèle courant <link>{template}</link> aux médias précédemment téléversé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 <link>{job}</link>.", "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 téléversés, exécutez la tâche <link>{job}</link>.",
"storage_template_migration_job": "Tâche de migration du modèle de stockage", "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 <template-link>Modèle de stockage</template-link> et à ses <implications-link>implications</implications-link>", "storage_template_more_details": "Pour plus de détails sur cette fonctionnalité, reportez-vous au <template-link>Modèle de stockage</template-link> et à ses <implications-link>implications</implications-link>",
"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épéter à la <link>documentation</link>.", "storage_template_onboarding_description": "Lorsqu'elle est activée, cette fonctionnalité organise les fichiers basés sur un modèle défini par l'utilisateur. En raison de problèmes de stabilité, la fonction a été désactivée par défaut. Pour plus d'informations, veuillez consulter la <link>documentation</link>.",
"storage_template_path_length": "Limite approximative de la longueur du chemin: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "Limite approximative de la longueur du chemin: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "Modèle de stockage", "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_settings_description": "Gérer la structure des dossiers et le nom des fichiers du média téléversé",
"storage_template_user_label": "<code>{label}</code> est l'étiquette de stockage de l'utilisateur", "storage_template_user_label": "<code>{label}</code> est l'étiquette de stockage de l'utilisateur",
"system_settings": "Paramètres du système", "system_settings": "Paramètres du système",
"tag_cleanup_job": "Nettoyage des étiquettes", "tag_cleanup_job": "Nettoyage des étiquettes",
@@ -414,7 +413,7 @@
"allow_dark_mode": "Autoriser le mode sombre", "allow_dark_mode": "Autoriser le mode sombre",
"allow_edits": "Autoriser les modifications", "allow_edits": "Autoriser les modifications",
"allow_public_user_to_download": "Permettre aux utilisateurs non connectés de télécharger", "allow_public_user_to_download": "Permettre aux utilisateurs non connectés de télécharger",
"allow_public_user_to_upload": "Autoriser l'envoi aux utilisateurs non connectés", "allow_public_user_to_upload": "Permettre le téléversement aux utilisateurs non connectés",
"alt_text_qr_code": "Image du code QR", "alt_text_qr_code": "Image du code QR",
"anti_clockwise": "Sens anti-horaire", "anti_clockwise": "Sens anti-horaire",
"api_key": "Clé API", "api_key": "Clé API",
@@ -457,8 +456,8 @@
"asset_restored_successfully": "Élément restauré avec succès", "asset_restored_successfully": "Élément restauré avec succès",
"asset_skipped": "Sauté", "asset_skipped": "Sauté",
"asset_skipped_in_trash": "À la corbeille", "asset_skipped_in_trash": "À la corbeille",
"asset_uploaded": "Envoyé", "asset_uploaded": "Téléversé",
"asset_uploading": "Envoi…", "asset_uploading": "Téléversement…",
"asset_viewer_settings_subtitle": "Modifier les paramètres du visualiseur photos", "asset_viewer_settings_subtitle": "Modifier les paramètres du visualiseur photos",
"asset_viewer_settings_title": "Visualiseur d'éléments", "asset_viewer_settings_title": "Visualiseur d'éléments",
"assets": "Médias", "assets": "Médias",
@@ -499,11 +498,11 @@
"backup_all": "Tout", "backup_all": "Tout",
"backup_background_service_backup_failed_message": "Échec de la sauvegarde des médias. Nouvelle tentative…", "backup_background_service_backup_failed_message": "Échec de la sauvegarde des médias. Nouvelle tentative…",
"backup_background_service_connection_failed_message": "Impossible de se connecter au serveur. Nouvelle tentative…", "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_current_upload_notification": "Téléversement de {filename}",
"backup_background_service_default_notification": "Recherche de nouveaux médias…", "backup_background_service_default_notification": "Recherche de nouveaux médias…",
"backup_background_service_error_title": "Erreur de sauvegarde", "backup_background_service_error_title": "Erreur de sauvegarde",
"backup_background_service_in_progress_notification": "Sauvegarde de vos médias…", "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_background_service_upload_failure_notification": "Échec lors du téléversement de {filename}",
"backup_controller_page_albums": "Sauvegarder les albums", "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_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_disabled_title": "Rafraîchissement de l'application en arrière-plan désactivé",
@@ -525,7 +524,7 @@
"backup_controller_page_backup_selected": "Sélectionné : ", "backup_controller_page_backup_selected": "Sélectionné : ",
"backup_controller_page_backup_sub": "Photos et vidéos sauvegardées", "backup_controller_page_backup_sub": "Photos et vidéos sauvegardées",
"backup_controller_page_created": "Créé le : {date}", "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_desc_backup": "Activez la sauvegarde au premier plan pour téléverser automatiquement les nouveaux médias sur le serveur lors de l'ouverture de l'application.",
"backup_controller_page_excluded": "Exclus : ", "backup_controller_page_excluded": "Exclus : ",
"backup_controller_page_failed": "Échec de l'opération ({count})", "backup_controller_page_failed": "Échec de l'opération ({count})",
"backup_controller_page_filename": "Nom du fichier : {filename} [{size}]", "backup_controller_page_filename": "Nom du fichier : {filename} [{size}]",
@@ -543,15 +542,15 @@
"backup_controller_page_total_sub": "Toutes les photos et vidéos uniques des albums sélectionnés", "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_off": "Désactiver la sauvegarde",
"backup_controller_page_turn_on": "Activer la sauvegarde", "backup_controller_page_turn_on": "Activer la sauvegarde",
"backup_controller_page_uploading_file_info": "Envoi des informations du fichier", "backup_controller_page_uploading_file_info": "Téléversement des informations du fichier",
"backup_err_only_album": "Impossible de retirer le seul album", "backup_err_only_album": "Impossible de retirer le seul album",
"backup_info_card_assets": "éléments", "backup_info_card_assets": "éléments",
"backup_manual_cancelled": "Annulé", "backup_manual_cancelled": "Annulé",
"backup_manual_in_progress": "Envoi déjà en cours. Réessayez plus tard", "backup_manual_in_progress": "Téléversement déjà en cours. Réessayez plus tard",
"backup_manual_success": "Succès", "backup_manual_success": "Succès",
"backup_manual_title": "Statut de l'envoi", "backup_manual_title": "Statut du téléversement",
"backup_options_page_title": "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_setting_subtitle": "Ajuster les paramètres de téléversement au premier et en arrière-plan",
"backward": "Arrière", "backward": "Arrière",
"biometric_auth_enabled": "Authentification biométrique activée", "biometric_auth_enabled": "Authentification biométrique activée",
"biometric_locked_out": "L'authentification biométrique est verrouillé", "biometric_locked_out": "L'authentification biométrique est verrouillé",
@@ -781,7 +780,7 @@
"downloading": "Téléchargement", "downloading": "Téléchargement",
"downloading_asset_filename": "Téléchargement du média {filename}", "downloading_asset_filename": "Téléchargement du média {filename}",
"downloading_media": "Téléchargement du média", "downloading_media": "Téléchargement du média",
"drop_files_to_upload": "Déposez les fichiers n'importe où pour envoyer", "drop_files_to_upload": "Déposez les fichiers n'importe où pour téléverser",
"duplicates": "Doublons", "duplicates": "Doublons",
"duplicates_description": "Examiner chaque groupe et indiquer s'il y a des doublons", "duplicates_description": "Examiner chaque groupe et indiquer s'il y a des doublons",
"duration": "Durée", "duration": "Durée",
@@ -949,7 +948,7 @@
"unable_to_update_settings": "Impossible de mettre à jour les paramètres", "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_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_user": "Impossible de mettre à jour l'utilisateur",
"unable_to_upload_file": "Impossible d'envoyer le fichier" "unable_to_upload_file": "Impossible de téléverser le fichier"
}, },
"exif": "Exif", "exif": "Exif",
"exif_bottom_sheet_description": "Ajouter une description...", "exif_bottom_sheet_description": "Ajouter une description...",
@@ -1051,12 +1050,12 @@
"home_page_locked_error_local": "Impossible de déplacer l'objet vers le dossier verrouillé, passer", "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_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_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", "home_page_upload_err_limit": "Impossible de téléverser plus de 30 médias en même temps, demande ignorée",
"host": "Hôte", "host": "Hôte",
"hour": "Heure", "hour": "Heure",
"id": "ID", "id": "ID",
"ignore_icloud_photos": "Ignorer les photos iCloud", "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", "ignore_icloud_photos_description": "Les photos stockées sur iCloud ne sont pas téléversées sur le serveur Immich",
"image": "Image", "image": "Image",
"image_alt_text_date": "{isVideo, select, true {Video} other {Image}} prise le {date}", "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_1_person": "{isVideo, select, true {Video} other {Image}} prise avec {person1} le {date}",
@@ -1136,7 +1135,7 @@
"list": "Liste", "list": "Liste",
"loading": "Chargement", "loading": "Chargement",
"loading_search_results_failed": "Chargement des résultats échoué", "loading_search_results_failed": "Chargement des résultats échoué",
"local_asset_cast_failed": "Impossible de caster un média qui n'a pas envoyé vers le serveur", "local_asset_cast_failed": "Impossible de diffuser un appareil qui n'a pas téléversé vers le serveur",
"local_network": "Réseau 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", "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_permission": "Autorisation de localisation", "location_permission": "Autorisation de localisation",
@@ -1150,7 +1149,6 @@
"locked_folder": "Dossier verrouillé", "locked_folder": "Dossier verrouillé",
"log_out": "Se déconnecter", "log_out": "Se déconnecter",
"log_out_all_devices": "Déconnecter tous les appareils", "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_all_devices": "Déconnecté de tous les appareils",
"logged_out_device": "Déconnecté de l'appareil", "logged_out_device": "Déconnecté de l'appareil",
"login": "Connexion", "login": "Connexion",
@@ -1276,12 +1274,12 @@
"no_albums_with_name_yet": "Il semble que vous n'ayez pas encore d'albums avec ce nom.", "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_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_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_message": "CLIQUER ICI POUR TÉLÉVERSER VOTRE PREMIÈRE PHOTO",
"no_assets_to_show": "Aucun élément à afficher", "no_assets_to_show": "Aucun élément à afficher",
"no_cast_devices_found": "Aucun appareil de diffusion trouvé", "no_cast_devices_found": "Aucun appareil de diffusion trouvé",
"no_duplicates_found": "Aucun doublon n'a été trouvé.", "no_duplicates_found": "Aucun doublon n'a été trouvé.",
"no_exif_info_available": "Aucune information exif disponible", "no_exif_info_available": "Aucune information exif disponible",
"no_explore_results_message": "Envoyez plus de photos pour explorer votre bibliothèque.", "no_explore_results_message": "Téléversez 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_favorites_message": "Ajouter des photos et vidéos à vos favoris pour les retrouver plus rapidement",
"no_libraries_message": "Créer une bibliothèque externe pour voir vos photos et vidéos dans un autre espace de stockage", "no_libraries_message": "Créer une bibliothèque externe pour voir vos photos et vidéos dans un autre espace de stockage",
"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_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.",
@@ -1294,7 +1292,7 @@
"no_shared_albums_message": "Créer un album pour partager vos photos et vidéos avec les personnes de votre réseau", "no_shared_albums_message": "Créer un album pour partager vos photos et vidéos avec les personnes de votre réseau",
"not_in_any_album": "Dans aucun album", "not_in_any_album": "Dans aucun album",
"not_selected": "Non sélectionné", "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", "note_apply_storage_label_to_previously_uploaded assets": "Note : Pour appliquer l'étiquette de stockage aux médias déjà téléversés, exécutez",
"notes": "Notes", "notes": "Notes",
"nothing_here_yet": "Rien pour le moment", "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_dialog_content": "Pour activer les notifications, allez dans Paramètres et sélectionnez Autoriser.",
@@ -1514,7 +1512,7 @@
"rename": "Renommer", "rename": "Renommer",
"repair": "Réparer", "repair": "Réparer",
"repair_no_results_message": "Les fichiers non importés ou absents s'afficheront ici", "repair_no_results_message": "Les fichiers non importés ou absents s'afficheront ici",
"replace_with_upload": "Remplacer avec l'envoi", "replace_with_upload": "Remplacer par téléversement",
"repository": "Dépôt", "repository": "Dépôt",
"require_password": "Demander le mot de passe", "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", "require_user_to_change_password_on_first_login": "Demander à l'utilisateur de changer son mot de passe lors de sa première connexion",
@@ -1531,7 +1529,7 @@
"restore_user": "Restaurer l'utilisateur", "restore_user": "Restaurer l'utilisateur",
"restored_asset": "Média restauré", "restored_asset": "Média restauré",
"resume": "Reprendre", "resume": "Reprendre",
"retry_upload": "Réessayer l'envoi", "retry_upload": "Réessayer le téléversement",
"review_duplicates": "Consulter les doublons", "review_duplicates": "Consulter les doublons",
"role": "Rôle", "role": "Rôle",
"role_editor": "Éditeur", "role_editor": "Éditeur",
@@ -1608,7 +1606,6 @@
"select_album_cover": "Sélectionner la couverture d'album", "select_album_cover": "Sélectionner la couverture d'album",
"select_all": "Tout sélectionner", "select_all": "Tout sélectionner",
"select_all_duplicates": "Sélectionner tous les doublons", "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_avatar_color": "Sélectionner la couleur de l'avatar",
"select_face": "Sélectionner le visage", "select_face": "Sélectionner le visage",
"select_featured_photo": "Sélectionner la photo de profil de cette personne", "select_featured_photo": "Sélectionner la photo de profil de cette personne",
@@ -1654,10 +1651,10 @@
"setting_notifications_notify_minutes": "{count} minutes", "setting_notifications_notify_minutes": "{count} minutes",
"setting_notifications_notify_never": "jamais", "setting_notifications_notify_never": "jamais",
"setting_notifications_notify_seconds": "{count} secondes", "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_subtitle": "Informations détaillées sur la progression du téléversement par média",
"setting_notifications_single_progress_title": "Afficher la progression du détail de la sauvegarde en arrière-plan", "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_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_subtitle": "Progression globale du téléversement (effectué/total des médias)",
"setting_notifications_total_progress_title": "Afficher la progression totale de la sauvegarde en arrière-plan", "setting_notifications_total_progress_title": "Afficher la progression totale de la sauvegarde en arrière-plan",
"setting_video_viewer_looping_title": "Boucle", "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_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.",
@@ -1683,7 +1680,7 @@
"shared_by_user": "Partagé par {user}", "shared_by_user": "Partagé par {user}",
"shared_by_you": "Partagé par vous", "shared_by_you": "Partagé par vous",
"shared_from_partner": "Photos de {partner}", "shared_from_partner": "Photos de {partner}",
"shared_intent_upload_button_progress_text": "{current} / {total} Envoyé(s)", "shared_intent_upload_button_progress_text": "{current} / {total} Téléversé(s)",
"shared_link_app_bar_title": "Liens partagés", "shared_link_app_bar_title": "Liens partagés",
"shared_link_clipboard_copied_massage": "Copié dans le presse-papier", "shared_link_clipboard_copied_massage": "Copié dans le presse-papier",
"shared_link_clipboard_text": "Lien : {link}\nMot de passe : {password}", "shared_link_clipboard_text": "Lien : {link}\nMot de passe : {password}",
@@ -1795,8 +1792,8 @@
"swap_merge_direction": "Inverser la direction de fusion", "swap_merge_direction": "Inverser la direction de fusion",
"sync": "Synchroniser", "sync": "Synchroniser",
"sync_albums": "Synchroniser dans des albums", "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_albums_manual_subtitle": "Synchroniser toutes les vidéos et photos téléversées dans les albums sélectionnés",
"sync_upload_album_setting_subtitle": "Créez et envoyez vos photos et vidéos dans les albums sélectionnés sur Immich", "sync_upload_album_setting_subtitle": "Crée et téléverse vos photos et vidéos dans les albums sélectionnés sur Immich",
"tag": "Étiquette", "tag": "Étiquette",
"tag_assets": "Étiqueter les médias", "tag_assets": "Étiqueter les médias",
"tag_created": "Étiquette créée: {tag}", "tag_created": "Étiquette créée: {tag}",
@@ -1873,25 +1870,24 @@
"unsaved_change": "Modification non enregistrée", "unsaved_change": "Modification non enregistrée",
"unselect_all": "Annuler la sélection", "unselect_all": "Annuler la sélection",
"unselect_all_duplicates": "Désélectionner tous les doublons", "unselect_all_duplicates": "Désélectionner tous les doublons",
"unselect_all_in": "Tout désélectionner dans {group}",
"unstack": "Désempiler", "unstack": "Désempiler",
"unstacked_assets_count": "{count, plural, one {# média dépilé} other {# médias dépilés}}", "unstacked_assets_count": "{count, plural, one {# média dépilé} other {# médias dépilés}}",
"up_next": "Suite", "up_next": "Suite",
"updated_at": "Mis à jour à", "updated_at": "Mis à jour à",
"updated_password": "Mot de passe mis à jour", "updated_password": "Mot de passe mis à jour",
"upload": "Envoyer", "upload": "Téléverser",
"upload_concurrency": "Envois simultanés", "upload_concurrency": "Téléversements simultanés",
"upload_dialog_info": "Voulez-vous sauvegarder la sélection vers le serveur?", "upload_dialog_info": "Voulez-vous sauvegarder la sélection vers le serveur?",
"upload_dialog_title": "Envoyer le média", "upload_dialog_title": "Téléverser le média",
"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_errors": "Le téléversement s'est achevé avec {count, plural, one {# erreur} other {# erreurs}}. Rafraîchir la page pour voir les nouveaux médias téléversés.",
"upload_progress": "{remaining, number} restant(s) - {processed, number} traité(s)/{total, number}", "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_skipped_duplicates": "{count, plural, one {# doublon ignoré} other {# doublons ignorés}}",
"upload_status_duplicates": "Doublons", "upload_status_duplicates": "Doublons",
"upload_status_errors": "Erreurs", "upload_status_errors": "Erreurs",
"upload_status_uploaded": "Envoyé", "upload_status_uploaded": "Téléversé",
"upload_success": "Envoi réussi. Rafraîchissez la page pour voir les nouveaux médias envoyés.", "upload_success": "Téléversement réussi. Rafraîchir la page pour voir les nouveaux médias téléversés.",
"upload_to_immich": "Envoyer vers Immich ({count})", "upload_to_immich": "Téléverser vers Immich ({count})",
"uploading": "Envoi", "uploading": "Téléversement en cours",
"url": "URL", "url": "URL",
"usage": "Utilisation", "usage": "Utilisation",
"use_biometric": "Utiliser l'authentification biométrique", "use_biometric": "Utiliser l'authentification biométrique",

View File

@@ -239,6 +239,7 @@
"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 <link>{job}</link>.", "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 <link>{job}</link>.",
"storage_template_migration_job": "Traballo de Migración do Modelo de Almacenamento", "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 <template-link>Modelo de Almacenamento</template-link> e as súas <implications-link>implicacións</implications-link>", "storage_template_more_details": "Para máis detalles sobre esta función, consulte o <template-link>Modelo de Almacenamento</template-link> e as súas <implications-link>implicacións</implications-link>",
"storage_template_onboarding_description": "Cando estea activada, esta función autoorganizará os ficheiros baseándose nun modelo definido polo usuario. Debido a problemas de estabilidade, a función desactivouse por defecto. Para obter máis información, consulte a <link>documentación</link>.",
"storage_template_path_length": "Límite aproximado da lonxitude da ruta: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "Límite aproximado da lonxitude da ruta: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "Modelo de Almacenamento", "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_settings_description": "Xestionar a estrutura de cartafoles e o nome de ficheiro do activo cargado",

View File

@@ -243,6 +243,7 @@
"storage_template_migration_info": "תבנית האחסון תמיר את כל ההרחבות לאותיות קטנות. שינויים בתבנית יחולו רק על תמונות חדשות. כדי להחיל באופן רטרואקטיבי את התבנית על תמונות שהועלו בעבר, הפעל את <link>{job}</link>.", "storage_template_migration_info": "תבנית האחסון תמיר את כל ההרחבות לאותיות קטנות. שינויים בתבנית יחולו רק על תמונות חדשות. כדי להחיל באופן רטרואקטיבי את התבנית על תמונות שהועלו בעבר, הפעל את <link>{job}</link>.",
"storage_template_migration_job": "משימת העברת תבנית אחסון", "storage_template_migration_job": "משימת העברת תבנית אחסון",
"storage_template_more_details": "לפרטים נוספים אודות תכונה זו, עיין ב<template-link>תבנית האחסון</template-link> וב<implications-link>השלכותיה</implications-link>", "storage_template_more_details": "לפרטים נוספים אודות תכונה זו, עיין ב<template-link>תבנית האחסון</template-link> וב<implications-link>השלכותיה</implications-link>",
"storage_template_onboarding_description": "כאשר מופעלת, תכונה זו תארגן אוטומטית קבצים בהתבסס על תבנית שהמשתמש הגדיר. עקב בעיות יציבות התכונה כבויה כברירת מחדל. למידע נוסף, נא לראות את ה<link>תיעוד</link>.",
"storage_template_path_length": "מגבלת אורך נתיב משוערת: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "מגבלת אורך נתיב משוערת: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "תבנית אחסון", "storage_template_settings": "תבנית אחסון",
"storage_template_settings_description": "ניהול מבנה התיקיות ואת שם הקובץ של התמונה שהועלתה", "storage_template_settings_description": "ניהול מבנה התיקיות ואת שם הקובץ של התמונה שהועלתה",

View File

@@ -242,6 +242,7 @@
"storage_template_migration_info": "स्टोरेज टेम्प्लेट सभी एक्सटेंशन को लोअरकेस में बदल देगा। टेम्प्लेट में किए गए बदलाव सिर्फ़ नई संपत्तियों पर लागू होंगे। टेम्प्लेट को पहले अपलोड की गई संपत्तियों पर पूर्वव्यापी रूप से लागू करने के लिए, <link>{job}</link> चलाएँ।", "storage_template_migration_info": "स्टोरेज टेम्प्लेट सभी एक्सटेंशन को लोअरकेस में बदल देगा। टेम्प्लेट में किए गए बदलाव सिर्फ़ नई संपत्तियों पर लागू होंगे। टेम्प्लेट को पहले अपलोड की गई संपत्तियों पर पूर्वव्यापी रूप से लागू करने के लिए, <link>{job}</link> चलाएँ।",
"storage_template_migration_job": "संग्रहण टेम्पलेट माइग्रेशन कार्य", "storage_template_migration_job": "संग्रहण टेम्पलेट माइग्रेशन कार्य",
"storage_template_more_details": "इस सुविधा के बारे में अधिक जानकारी के लिए, देखें <template-link>भंडारण टेम्पलेट</template-link> और इसके <implications-link>आशय</implications-link>", "storage_template_more_details": "इस सुविधा के बारे में अधिक जानकारी के लिए, देखें <template-link>भंडारण टेम्पलेट</template-link> और इसके <implications-link>आशय</implications-link>",
"storage_template_onboarding_description": "सक्षम होने पर, यह सुविधा उपयोगकर्ता द्वारा परिभाषित टेम्पलेट के आधार पर फ़ाइलों को स्वतः व्यवस्थित कर देगी। स्थिरता संबंधी समस्याओं के कारण यह सुविधा डिफ़ॉल्ट रूप से बंद कर दी गई है। अधिक जानकारी के लिए, कृपया <link>दस्तावेज़ीकरण</link> देखें।",
"storage_template_path_length": "अनुमानित पथ लंबाई सीमा: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "अनुमानित पथ लंबाई सीमा: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "भंडारण टेम्पलेट", "storage_template_settings": "भंडारण टेम्पलेट",
"storage_template_settings_description": "अपलोड संपत्ति की फ़ोल्डर संरचना और फ़ाइल नाम प्रबंधित करें", "storage_template_settings_description": "अपलोड संपत्ति की फ़ोल्डर संरचना और फ़ाइल नाम प्रबंधित करें",

View File

@@ -243,6 +243,7 @@
"storage_template_migration_info": "Predložak za pohranu će sve nastavke (ekstenzije) pretvoriti u mala slova. Promjene predloška primjenjivat će se samo na nova sredstva. Za retroaktivnu primjenu predloška na prethodno prenesena sredstva, pokrenite <link>{job}</link>.", "storage_template_migration_info": "Predložak za pohranu će sve nastavke (ekstenzije) pretvoriti u mala slova. Promjene predloška primjenjivat će se samo na nova sredstva. Za retroaktivnu primjenu predloška na prethodno prenesena sredstva, pokrenite <link>{job}</link>.",
"storage_template_migration_job": "Posao Migracije Predloška Pohrane", "storage_template_migration_job": "Posao Migracije Predloška Pohrane",
"storage_template_more_details": "Za više pojedinosti o ovoj značajci pogledajte <template-link>Predložak pohrane</template-link> i njegove <implications-link>implikacije</implications-link>", "storage_template_more_details": "Za više pojedinosti o ovoj značajci pogledajte <template-link>Predložak pohrane</template-link> i njegove <implications-link>implikacije</implications-link>",
"storage_template_onboarding_description": "Kada je omogućena, ova će značajka automatski organizirati datoteke na temelju korisnički definiranog predloška. Zbog problema sa stabilnošću značajka je isključena prema zadanim postavkama. Za više informacija pogledajte <link>dokumentaciju</link>.",
"storage_template_path_length": "Približno ograničenje duljine putanje: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "Približno ograničenje duljine putanje: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "Predložak pohrane", "storage_template_settings": "Predložak pohrane",
"storage_template_settings_description": "Upravljajte strukturom mape i nazivom datoteke učitanog sredstva", "storage_template_settings_description": "Upravljajte strukturom mape i nazivom datoteke učitanog sredstva",

View File

@@ -242,6 +242,7 @@
"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: <link>{job}</link>.", "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: <link>{job}</link>.",
"storage_template_migration_job": "Tárhely Sablon Migrációja", "storage_template_migration_job": "Tárhely Sablon Migrációja",
"storage_template_more_details": "További részletekért erről a funkcióról lásd a <template-link>Tárhely Sablon</template-link> és annak <implications-link>következményeit</implications-link> a dokumentációban", "storage_template_more_details": "További részletekért erről a funkcióról lásd a <template-link>Tárhely Sablon</template-link> és annak <implications-link>következményeit</implications-link> a dokumentációban",
"storage_template_onboarding_description": "Ha ez a funkció engedélyezve van, akkor a fájlokat automatikusan az egyéni sablon alapján rendszerezi el. Stabilitási problémák miatt a funkció alapértelmezés szerint ki van kapcsolva. További információkért lásd a <link>dokumentációt</link>.",
"storage_template_path_length": "Útvonal hozzávetőleges maximális hossza: <b>{length, number}</b>{limit, number}", "storage_template_path_length": "Útvonal hozzávetőleges maximális hossza: <b>{length, number}</b>{limit, number}",
"storage_template_settings": "Tárhely Sablon", "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_settings_description": "A feltöltött elemek mappaszerkezetének és fájl elnevezésének kezelése",

View File

@@ -243,6 +243,7 @@
"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 <link>{job}</link>.", "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 <link>{job}</link>.",
"storage_template_migration_job": "Tugas Migrasi Templat Ruang Penyimpanan", "storage_template_migration_job": "Tugas Migrasi Templat Ruang Penyimpanan",
"storage_template_more_details": "Untuk detail lebih lanjut tentang fitur ini, pergi ke <template-link>Templat Penyimpanan</template-link> dan <implications-link>kekurangannya</implications-link>", "storage_template_more_details": "Untuk detail lebih lanjut tentang fitur ini, pergi ke <template-link>Templat Penyimpanan</template-link> dan <implications-link>kekurangannya</implications-link>",
"storage_template_onboarding_description": "Ketika diaktifkan, fitur ini akan mengelola berkas secara otomatis berdasarkan templat pengguna. Karena masalah stabilitas, fitur ini telah dimatikan secara bawaan. Untuk informasi lebih lanjut, silakan lihat <link>dokumentasi</link>.",
"storage_template_path_length": "Batas panjang jalur: <b>{length, number}</b>{limit, number}", "storage_template_path_length": "Batas panjang jalur: <b>{length, number}</b>{limit, number}",
"storage_template_settings": "Templat Penyimpanan", "storage_template_settings": "Templat Penyimpanan",
"storage_template_settings_description": "Kelola struktur folder dan nama berkas dari aset yang diunggah", "storage_template_settings_description": "Kelola struktur folder dan nama berkas dari aset yang diunggah",

View File

@@ -34,7 +34,6 @@
"added_to_favorites_count": "Aggiunto {count, number} ai preferiti", "added_to_favorites_count": "Aggiunto {count, number} ai preferiti",
"admin": { "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/**\".", "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.", "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": "Impostazioni di Autenticazione",
"authentication_settings_description": "Gestisci password, OAuth e altre impostazioni di autenticazione", "authentication_settings_description": "Gestisci password, OAuth e altre impostazioni di autenticazione",
@@ -171,7 +170,7 @@
"note_apply_storage_label_previous_assets": "Nota: Per assegnare l'etichetta storage ad asset precedentemente caricati, esegui", "note_apply_storage_label_previous_assets": "Nota: Per assegnare l'etichetta storage ad asset precedentemente caricati, esegui",
"note_cannot_be_changed_later": "NOTA: Non potrà essere modificato in futuro!", "note_cannot_be_changed_later": "NOTA: Non potrà essere modificato in futuro!",
"notification_email_from_address": "Indirizzo mittente", "notification_email_from_address": "Indirizzo mittente",
"notification_email_from_address_description": "Indirizzo email del mittente, ad esempio: \"Immich Photo Server <noreply@example.com>\". Assicurati di utilizzare un indirizzo da cui sei autorizzato a inviare email.", "notification_email_from_address_description": "Indirizzo email mittente, ad esempio: \"Server Foto Immich <noreply@example.com>\"",
"notification_email_host_description": "Host del server email (es. smtp.immich.app)", "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": "Ignora errori di certificato",
"notification_email_ignore_certificate_errors_description": "Ignora errori di validazione del certificato TLS (sconsigliato)", "notification_email_ignore_certificate_errors_description": "Ignora errori di validazione del certificato TLS (sconsigliato)",
@@ -204,7 +203,7 @@
"oauth_storage_quota_claim": "Dichiarazione di ambito(claim) limite archiviazione", "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_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": "Limite predefinito di archiviazione (GiB)",
"oauth_storage_quota_default_description": "Limite in GiB da usare quanto nessuna dichiarazione di ambito(claim) è stata fornita.", "oauth_storage_quota_default_description": "Limite in GiB da usare quanto nessuna dichiarazione di ambito(claim) è stata fornita (Inserisci 0 per archiviazione illimitata).",
"oauth_timeout": "Timeout Richiesta", "oauth_timeout": "Timeout Richiesta",
"oauth_timeout_description": "Timeout per le richieste, espresso in millisecondi", "oauth_timeout_description": "Timeout per le richieste, espresso in millisecondi",
"password_enable_description": "Login con email e password", "password_enable_description": "Login con email e password",
@@ -244,7 +243,7 @@
"storage_template_migration_info": "Le modifiche al modello di archiviazione verranno applicate solo agli asset nuovi. Per applicare le modifiche retroattivamente esegui <link>{job}</link>.", "storage_template_migration_info": "Le modifiche al modello di archiviazione verranno applicate solo agli asset nuovi. Per applicare le modifiche retroattivamente esegui <link>{job}</link>.",
"storage_template_migration_job": "Processo Migrazione Modello di Archiviazione", "storage_template_migration_job": "Processo Migrazione Modello di Archiviazione",
"storage_template_more_details": "Per maggiori informazioni riguardo a questa funzionalità, consulta il <template-link>Modello Archiviazione</template-link> e le sue <implications-link>conseguenze</implications-link>", "storage_template_more_details": "Per maggiori informazioni riguardo a questa funzionalità, consulta il <template-link>Modello Archiviazione</template-link> e le sue <implications-link>conseguenze</implications-link>",
"storage_template_onboarding_description_v2": "Se attiva, questa funzionalità organizzerà automaticamente i file utilizzando un modello definito dall'utente. Per maggiori informazioni, consultare la <link>documentazione</link>.", "storage_template_onboarding_description": "Quando attivata, questa funzionalità organizzerà automaticamente i file utilizzando il modello di archiviazione definito dall'utente. Per ragioni di stabilità, questa funzionalità è disabilitata per impostazione predefinita. Per più informazioni, consulta <link>la documentazione</link>.",
"storage_template_path_length": "Limite approssimativo lunghezza percorso: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "Limite approssimativo lunghezza percorso: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "Modello Archiviazione", "storage_template_settings": "Modello Archiviazione",
"storage_template_settings_description": "Gestisci la struttura delle cartelle e il nome degli asset caricati", "storage_template_settings_description": "Gestisci la struttura delle cartelle e il nome degli asset caricati",
@@ -469,8 +468,6 @@
"assets_count": "{count, plural, other {# asset}}", "assets_count": "{count, plural, other {# asset}}",
"assets_deleted_permanently": "{count} elementi cancellati definitivamente", "assets_deleted_permanently": "{count} elementi cancellati definitivamente",
"assets_deleted_permanently_from_server": "{count} elementi cancellati definitivamente dal server Immich", "assets_deleted_permanently_from_server": "{count} elementi cancellati definitivamente dal server Immich",
"assets_downloaded_failed": "{count, plural, one {Scaricato # file - {error} file non riusciti} 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 {# asset spostato} other {# asset spostati}} nel cestino", "assets_moved_to_trash_count": "{count, plural, one {# asset spostato} other {# asset spostati}} nel cestino",
"assets_permanently_deleted_count": "{count, plural, one {# asset cancellato} other {# asset cancellati}} definitivamente", "assets_permanently_deleted_count": "{count, plural, one {# asset cancellato} other {# asset cancellati}} definitivamente",
"assets_removed_count": "{count, plural, one {# asset rimosso} other {# asset rimossi}}", "assets_removed_count": "{count, plural, one {# asset rimosso} other {# asset rimossi}}",
@@ -650,7 +647,6 @@
"confirm_password": "Conferma password", "confirm_password": "Conferma password",
"confirm_tag_face": "Vuoi taggare questo volto come {name}?", "confirm_tag_face": "Vuoi taggare questo volto come {name}?",
"confirm_tag_face_unnamed": "Vuoi taggare questo volto?", "confirm_tag_face_unnamed": "Vuoi taggare questo volto?",
"connected_device": "Dispositivo connesso",
"connected_to": "Connesso", "connected_to": "Connesso",
"contain": "Adatta alla finestra", "contain": "Adatta alla finestra",
"context": "Contesto", "context": "Contesto",
@@ -751,7 +747,6 @@
"disallow_edits": "Blocca modifiche", "disallow_edits": "Blocca modifiche",
"discord": "Discord", "discord": "Discord",
"discover": "Scopri", "discover": "Scopri",
"discovered_devices": "Dispositivi trovati",
"dismiss_all_errors": "Ignora tutti gli errori", "dismiss_all_errors": "Ignora tutti gli errori",
"dismiss_error": "Ignora errore", "dismiss_error": "Ignora errore",
"display_options": "Impostazioni visualizzazione", "display_options": "Impostazioni visualizzazione",
@@ -1136,7 +1131,6 @@
"list": "Lista", "list": "Lista",
"loading": "Caricamento", "loading": "Caricamento",
"loading_search_results_failed": "Impossibile caricare i risultati della ricerca", "loading_search_results_failed": "Impossibile caricare i risultati della ricerca",
"local_asset_cast_failed": "Impossibile trasmettere una risorsa che non è caricata sul server",
"local_network": "Rete locale", "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", "local_network_sheet_info": "L'app si collegherà al server tramite questo URL quando è in uso la rete Wi-Fi specificata",
"location_permission": "Permesso di localizzazione", "location_permission": "Permesso di localizzazione",
@@ -1147,10 +1141,9 @@
"location_picker_longitude_error": "Inserisci una longitudine valida", "location_picker_longitude_error": "Inserisci una longitudine valida",
"location_picker_longitude_hint": "Inserisci la longitudine qui", "location_picker_longitude_hint": "Inserisci la longitudine qui",
"lock": "Rendi privato", "lock": "Rendi privato",
"locked_folder": "Cartella Privata", "locked_folder": "Cartella Bloccata",
"log_out": "Esci", "log_out": "Esci",
"log_out_all_devices": "Disconnetti tutti i dispositivi", "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_all_devices": "Disconnesso da tutti i dispositivi",
"logged_out_device": "Disconnesso dal dispositivo", "logged_out_device": "Disconnesso dal dispositivo",
"login": "Accesso", "login": "Accesso",
@@ -1182,7 +1175,7 @@
"look": "Guarda", "look": "Guarda",
"loop_videos": "Riproduci video in loop", "loop_videos": "Riproduci video in loop",
"loop_videos_description": "Abilita per riprodurre automaticamente un video in loop nella vista dettagli.", "loop_videos_description": "Abilita per riprodurre automaticamente un video in loop nella vista dettagli.",
"main_branch_warning": "Stai utilizzando una versione di sviluppo. Ti consigliamo vivamente di utilizzare una versione di rilascio!", "main_branch_warning": "Stai usando una versione di sviluppo. Consigliamo vivamente di utilizzare una versione di rilascio!",
"main_menu": "Menu Principale", "main_menu": "Menu Principale",
"make": "Produttore", "make": "Produttore",
"manage_shared_links": "Gestisci link condivisi", "manage_shared_links": "Gestisci link condivisi",
@@ -1278,7 +1271,6 @@
"no_archived_assets_message": "Archivia foto e video per nasconderli dalla galleria di foto", "no_archived_assets_message": "Archivia foto e video per nasconderli dalla galleria di foto",
"no_assets_message": "CLICCA PER CARICARE LA TUA PRIMA FOTO", "no_assets_message": "CLICCA PER CARICARE LA TUA PRIMA FOTO",
"no_assets_to_show": "Nessuna risorsa da mostrare", "no_assets_to_show": "Nessuna risorsa da mostrare",
"no_cast_devices_found": "Nessun dispositivo di trasmissione trovato",
"no_duplicates_found": "Nessun duplicato trovato.", "no_duplicates_found": "Nessun duplicato trovato.",
"no_exif_info_available": "Nessuna informazione exif disponibile", "no_exif_info_available": "Nessuna informazione exif disponibile",
"no_explore_results_message": "Carica più foto per esplorare la tua collezione.", "no_explore_results_message": "Carica più foto per esplorare la tua collezione.",
@@ -1448,7 +1440,7 @@
"purchase_lifetime_description": "Acquisto a vita", "purchase_lifetime_description": "Acquisto a vita",
"purchase_option_title": "OPZIONI DI ACQUISTO", "purchase_option_title": "OPZIONI DI ACQUISTO",
"purchase_panel_info_1": "Costruire 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 i software open source e le pratiche aziendali etiche diventino una fonte di reddito sostenibile per gli sviluppatori e creare un ecosistema che rispetti la privacy, offrendo vere alternative ai servizi cloud sfruttatori.", "purchase_panel_info_1": "Costruire 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 i software open source e le pratiche aziendali etiche diventino una fonte di reddito sostenibile per gli sviluppatori e creare 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_info_2": "Poiché siamo impegnati a non aggiungere barriere di pagamento, questo acquisto non ti offrirà funzionalità aggiuntive in Immich. Contiamo su utenti come te per sostenere lo sviluppo continuo di Immich.",
"purchase_panel_title": "Contribuisci al progetto", "purchase_panel_title": "Contribuisci al progetto",
"purchase_per_server": "Per server", "purchase_per_server": "Per server",
"purchase_per_user": "Per utente", "purchase_per_user": "Per utente",
@@ -1501,7 +1493,6 @@
"remove_from_shared_link": "Rimuovi dal link condiviso", "remove_from_shared_link": "Rimuovi dal link condiviso",
"remove_memory": "Rimuovi ricordo", "remove_memory": "Rimuovi ricordo",
"remove_photo_from_memory": "Rimuovi foto da questo ricordo", "remove_photo_from_memory": "Rimuovi foto da questo ricordo",
"remove_tag": "Rimuovi tag",
"remove_url": "Rimuovi URL", "remove_url": "Rimuovi URL",
"remove_user": "Rimuovi utente", "remove_user": "Rimuovi utente",
"removed_api_key": "Rimossa chiave API: {name}", "removed_api_key": "Rimossa chiave API: {name}",
@@ -1608,7 +1599,6 @@
"select_album_cover": "Seleziona copertina album", "select_album_cover": "Seleziona copertina album",
"select_all": "Seleziona tutto", "select_all": "Seleziona tutto",
"select_all_duplicates": "Seleziona tutti i duplicati", "select_all_duplicates": "Seleziona tutti i duplicati",
"select_all_in": "Seleziona tutto in {group}",
"select_avatar_color": "Seleziona colore avatar", "select_avatar_color": "Seleziona colore avatar",
"select_face": "Seleziona volto", "select_face": "Seleziona volto",
"select_featured_photo": "Seleziona foto in evidenza", "select_featured_photo": "Seleziona foto in evidenza",
@@ -1639,7 +1629,6 @@
"set_date_of_birth": "Imposta data di nascita", "set_date_of_birth": "Imposta data di nascita",
"set_profile_picture": "Imposta foto profilo", "set_profile_picture": "Imposta foto profilo",
"set_slideshow_to_fullscreen": "Imposta presentazione a schermo intero", "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_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 per caricare l'immagine originale a risoluzione massima (grande!). Disabilita per ridurre l'utilizzo di banda (sia sul network che nella cache del dispositivo).", "setting_image_viewer_original_subtitle": "Abilita per caricare l'immagine originale a risoluzione massima (grande!). Disabilita per ridurre l'utilizzo di banda (sia sul network che nella cache del dispositivo).",
"setting_image_viewer_original_title": "Carica l'immagine originale", "setting_image_viewer_original_title": "Carica l'immagine originale",
@@ -1777,7 +1766,6 @@
"start_date": "Data di inizio", "start_date": "Data di inizio",
"state": "Provincia", "state": "Provincia",
"status": "Stato", "status": "Stato",
"stop_casting": "Interrompi trasmissione",
"stop_motion_photo": "Ferma Foto in Movimento", "stop_motion_photo": "Ferma Foto in Movimento",
"stop_photo_sharing": "Interrompere la condivisione delle tue foto?", "stop_photo_sharing": "Interrompere la condivisione delle tue foto?",
"stop_photo_sharing_description": "{partner} non potrà più accedere alle tue foto.", "stop_photo_sharing_description": "{partner} non potrà più accedere alle tue foto.",
@@ -1856,7 +1844,6 @@
"unable_to_setup_pin_code": "Impossibile configurare il codice PIN", "unable_to_setup_pin_code": "Impossibile configurare il codice PIN",
"unarchive": "Annulla l'archiviazione", "unarchive": "Annulla l'archiviazione",
"unarchived_count": "{count, plural, other {Non archiviati #}}", "unarchived_count": "{count, plural, other {Non archiviati #}}",
"undo": "Annulla",
"unfavorite": "Rimuovi preferito", "unfavorite": "Rimuovi preferito",
"unhide_person": "Mostra persona", "unhide_person": "Mostra persona",
"unknown": "Sconosciuto", "unknown": "Sconosciuto",
@@ -1873,7 +1860,6 @@
"unsaved_change": "Modifica non salvata", "unsaved_change": "Modifica non salvata",
"unselect_all": "Deseleziona tutto", "unselect_all": "Deseleziona tutto",
"unselect_all_duplicates": "Deseleziona tutti i duplicati", "unselect_all_duplicates": "Deseleziona tutti i duplicati",
"unselect_all_in": "Deseleziona tutto in {group}",
"unstack": "Rimuovi dal gruppo", "unstack": "Rimuovi dal gruppo",
"unstacked_assets_count": "{count, plural, one {Separato # asset} other {Separati # asset}}", "unstacked_assets_count": "{count, plural, one {Separato # asset} other {Separati # asset}}",
"up_next": "Prossimo", "up_next": "Prossimo",

View File

@@ -34,7 +34,6 @@
"added_to_favorites_count": "{count, number} 枚の画像をお気に入りに追加しました", "added_to_favorites_count": "{count, number} 枚の画像をお気に入りに追加しました",
"admin": { "admin": {
"add_exclusion_pattern_description": "除外パターンを追加します。ワイルドカード「*」「**」「?」を使用できます。すべてのディレクトリで「Raw」と名前が付いたファイルを無視するには、「**/Raw/**」を使用します。また、「.tif」で終わるファイルをすべて無視するには、「**/*.tif」を使用します。さらに、絶対パスを無視するには「/path/to/ignore/**」を使用します。", "add_exclusion_pattern_description": "除外パターンを追加します。ワイルドカード「*」「**」「?」を使用できます。すべてのディレクトリで「Raw」と名前が付いたファイルを無視するには、「**/Raw/**」を使用します。また、「.tif」で終わるファイルをすべて無視するには、「**/*.tif」を使用します。さらに、絶対パスを無視するには「/path/to/ignore/**」を使用します。",
"admin_user": "管理ユーザー",
"asset_offline_description": "この外部ライブラリのアセットはディスク上に見つからなくなってゴミ箱に移動されました。ファイルがライブラリの中で移動された場合はタイムラインで新しい対応するアセットを確認してください。このアセットを復元するには以下のファイルパスがImmichからアクセスできるか確認してライブラリをスキャンしてください。", "asset_offline_description": "この外部ライブラリのアセットはディスク上に見つからなくなってゴミ箱に移動されました。ファイルがライブラリの中で移動された場合はタイムラインで新しい対応するアセットを確認してください。このアセットを復元するには以下のファイルパスがImmichからアクセスできるか確認してライブラリをスキャンしてください。",
"authentication_settings": "認証設定", "authentication_settings": "認証設定",
"authentication_settings_description": "認証設定の管理パスワード、OAuth、その他", "authentication_settings_description": "認証設定の管理パスワード、OAuth、その他",
@@ -204,7 +203,7 @@
"oauth_storage_quota_claim": "ストレージクォータ クレーム", "oauth_storage_quota_claim": "ストレージクォータ クレーム",
"oauth_storage_quota_claim_description": "ユーザーのストレージクォータをこのクレームの値に自動的に設定します。", "oauth_storage_quota_claim_description": "ユーザーのストレージクォータをこのクレームの値に自動的に設定します。",
"oauth_storage_quota_default": "デフォルトのストレージ割り当て(GiB)", "oauth_storage_quota_default": "デフォルトのストレージ割り当て(GiB)",
"oauth_storage_quota_default_description": "クレームが提供されていない場合に使用されるクォータをGiB単位で設定します。", "oauth_storage_quota_default_description": "クレームが提供されていない場合に使用されるクォータをGiB単位で設定します無制限にする場合は0を入力してください。",
"oauth_timeout": "リクエストタイムアウト", "oauth_timeout": "リクエストタイムアウト",
"oauth_timeout_description": "リクエストのタイムアウトまでの時間(ms)", "oauth_timeout_description": "リクエストのタイムアウトまでの時間(ms)",
"password_enable_description": "メールアドレスとパスワードでログイン", "password_enable_description": "メールアドレスとパスワードでログイン",
@@ -244,7 +243,7 @@
"storage_template_migration_info": "ストレージテンプレートは全ての拡張子を小文字に変換します。テンプレートの変更は新しいアセットにのみ適用されます。 以前にアップロードしたアセットにテンプレートを遡って適用するには、<link>{job}</link> を実行してください。", "storage_template_migration_info": "ストレージテンプレートは全ての拡張子を小文字に変換します。テンプレートの変更は新しいアセットにのみ適用されます。 以前にアップロードしたアセットにテンプレートを遡って適用するには、<link>{job}</link> を実行してください。",
"storage_template_migration_job": "ストレージテンプレート移行ジョブ", "storage_template_migration_job": "ストレージテンプレート移行ジョブ",
"storage_template_more_details": "この機能の詳細については、<template-link>ストレージテンプレート</template-link>とその<implications-link>影響</implications-link>を参照してください", "storage_template_more_details": "この機能の詳細については、<template-link>ストレージテンプレート</template-link>とその<implications-link>影響</implications-link>を参照してください",
"storage_template_onboarding_description_v2": "この設定をオンにすると、ユーザー定義したテンプレートに従って自動でファイルが整理されます。詳しい情報は<link>ドキュメンテーション</link>で確認してください。", "storage_template_onboarding_description": "この機能を有効にすると、ユーザー定義テンプレートに基づいてファイルが自動で整理されます。 安定性の問題のため、この機能はデフォルトでオフになっています。 詳細については、<link>ドキュメン</link>を参照してください。",
"storage_template_path_length": "おおよそのパス長の制限: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "おおよそのパス長の制限: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "ストレージ テンプレート", "storage_template_settings": "ストレージ テンプレート",
"storage_template_settings_description": "アップロードしたアセットのフォルダ構造とファイル名を管理します", "storage_template_settings_description": "アップロードしたアセットのフォルダ構造とファイル名を管理します",
@@ -1150,7 +1149,6 @@
"locked_folder": "鍵付きフォルダー", "locked_folder": "鍵付きフォルダー",
"log_out": "ログアウト", "log_out": "ログアウト",
"log_out_all_devices": "全てのデバイスからログアウト", "log_out_all_devices": "全てのデバイスからログアウト",
"logged_in_as": "{user}としてログイン中",
"logged_out_all_devices": "全てのデバイスからログアウトしました", "logged_out_all_devices": "全てのデバイスからログアウトしました",
"logged_out_device": "デバイスからログアウトしました", "logged_out_device": "デバイスからログアウトしました",
"login": "ログイン", "login": "ログイン",
@@ -1501,7 +1499,6 @@
"remove_from_shared_link": "共有リンクから削除", "remove_from_shared_link": "共有リンクから削除",
"remove_memory": "メモリーの削除", "remove_memory": "メモリーの削除",
"remove_photo_from_memory": "メモリーから写真を削除", "remove_photo_from_memory": "メモリーから写真を削除",
"remove_tag": "タグを削除",
"remove_url": "URLの削除", "remove_url": "URLの削除",
"remove_user": "ユーザーを削除", "remove_user": "ユーザーを削除",
"removed_api_key": "削除されたAPI キー: {name}", "removed_api_key": "削除されたAPI キー: {name}",
@@ -1608,7 +1605,6 @@
"select_album_cover": "アルバムカバーを選択", "select_album_cover": "アルバムカバーを選択",
"select_all": "全て選択", "select_all": "全て選択",
"select_all_duplicates": "全ての重複を選択", "select_all_duplicates": "全ての重複を選択",
"select_all_in": "{group}のすべてを選択",
"select_avatar_color": "アバターの色を選択", "select_avatar_color": "アバターの色を選択",
"select_face": "顔を選択", "select_face": "顔を選択",
"select_featured_photo": "人物写真を選択", "select_featured_photo": "人物写真を選択",
@@ -1873,7 +1869,6 @@
"unsaved_change": "未保存の変更", "unsaved_change": "未保存の変更",
"unselect_all": "全て選択解除", "unselect_all": "全て選択解除",
"unselect_all_duplicates": "全ての重複の選択を解除", "unselect_all_duplicates": "全ての重複の選択を解除",
"unselect_all_in": "{group}のすべての選択を解除",
"unstack": "スタックを解除", "unstack": "スタックを解除",
"unstacked_assets_count": "{count, plural, one {#個のアセット} other {#個のアセット}}をスタックから解除しました", "unstacked_assets_count": "{count, plural, one {#個のアセット} other {#個のアセット}}をスタックから解除しました",
"up_next": "次へ", "up_next": "次へ",

View File

@@ -242,6 +242,7 @@
"storage_template_migration_info": "스토리지 템플릿은 모든 확장자를 소문자로 변환하며, 변경 사항은 새로 업로드한 항목에만 적용됩니다. 기존에 업로드된 항목에 적용하려면 <link>{job}</link>을 실행하세요.", "storage_template_migration_info": "스토리지 템플릿은 모든 확장자를 소문자로 변환하며, 변경 사항은 새로 업로드한 항목에만 적용됩니다. 기존에 업로드된 항목에 적용하려면 <link>{job}</link>을 실행하세요.",
"storage_template_migration_job": "스토리지 템플릿 마이그레이션 작업", "storage_template_migration_job": "스토리지 템플릿 마이그레이션 작업",
"storage_template_more_details": "이 기능에 대한 자세한 내용은 <template-link>스토리지 템플릿</template-link> 및 <implications-link>설명</implications-link>을 참조하세요.", "storage_template_more_details": "이 기능에 대한 자세한 내용은 <template-link>스토리지 템플릿</template-link> 및 <implications-link>설명</implications-link>을 참조하세요.",
"storage_template_onboarding_description": "이 기능을 활성화하면 사용자 정의 템플릿을 사용하여 파일을 자동으로 정리할 수 있습니다. 안정성 문제로 인해 해당 기능은 기본적으로 비활성화되어 있습니다. 자세한 내용은 <link>문서</link>를 참조하세요.",
"storage_template_path_length": "대략적인 경로 길이 제한: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "대략적인 경로 길이 제한: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "스토리지 템플릿", "storage_template_settings": "스토리지 템플릿",
"storage_template_settings_description": "업로드된 항목의 폴더 구조 및 파일 이름 관리", "storage_template_settings_description": "업로드된 항목의 폴더 구조 및 파일 이름 관리",

Some files were not shown because too many files have changed in this diff Show More