diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000000000..e19fe74643957 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,2 @@ +ARG BASEIMAGE=mcr.microsoft.com/devcontainers/typescript-node:22@sha256:dc2c3654370fe92a55daeefe9d2d95839d85bdc1f68f7fd4ab86621f49e5818a +FROM ${BASEIMAGE} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000000..b297f9a2d8cc1 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,20 @@ +{ + "name": "Immich devcontainers", + "build": { + "dockerfile": "Dockerfile", + "args": { + "BASEIMAGE": "mcr.microsoft.com/devcontainers/typescript-node:22" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "svelte.svelte-vscode" + ] + } + }, + "forwardPorts": [], + "postCreateCommand": "make install-all", + "remoteUser": "node" +} + diff --git a/.github/workflows/docker-cleanup.yml b/.github/workflows/docker-cleanup.yml index 6f2c573a9f435..29b518e0a52a1 100644 --- a/.github/workflows/docker-cleanup.yml +++ b/.github/workflows/docker-cleanup.yml @@ -35,7 +35,7 @@ jobs: steps: - name: Clean temporary images if: "${{ env.TOKEN != '' }}" - uses: stumpylog/image-cleaner-action/ephemeral@v0.8.0 + uses: stumpylog/image-cleaner-action/ephemeral@v0.9.0 with: token: "${{ env.TOKEN }}" owner: "immich-app" @@ -64,7 +64,7 @@ jobs: steps: - name: Clean untagged images if: "${{ env.TOKEN != '' }}" - uses: stumpylog/image-cleaner-action/untagged@v0.8.0 + uses: stumpylog/image-cleaner-action/untagged@v0.9.0 with: token: "${{ env.TOKEN }}" owner: "immich-app" diff --git a/.github/workflows/fix-format.yml b/.github/workflows/fix-format.yml new file mode 100644 index 0000000000000..0c630c9e4b0f3 --- /dev/null +++ b/.github/workflows/fix-format.yml @@ -0,0 +1,52 @@ +name: Fix formatting + +on: + pull_request: + types: [labeled] + +jobs: + fix-formatting: + runs-on: ubuntu-latest + if: ${{ github.event.label.name == 'fix:formatting' }} + permissions: + pull-requests: write + steps: + - name: Generate a token + id: generate-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + + - name: 'Checkout' + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + token: ${{ steps.generate-token.outputs.token }} + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version-file: './server/.nvmrc' + + - name: Fix formatting + run: make install-all && make format-all + + - name: Commit and push + uses: EndBug/add-and-commit@v9 + with: + default_author: github_actions + message: 'chore: fix formatting' + + - name: Remove label + uses: actions/github-script@v7 + if: always() + with: + script: | + github.rest.issues.removeLabel({ + issue_number: context.payload.pull_request.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'fix:formatting' + }) + diff --git a/.github/workflows/pr-require-conventional-commit.yml b/.github/workflows/pr-require-conventional-commit.yml index 4899031249d27..d4bd44ec4303c 100644 --- a/.github/workflows/pr-require-conventional-commit.yml +++ b/.github/workflows/pr-require-conventional-commit.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: PR Conventional Commit Validation - uses: ytanikin/PRConventionalCommits@1.2.0 + uses: ytanikin/PRConventionalCommits@1.3.0 with: task_types: '["feat","fix","docs","test","ci","refactor","perf","chore","revert"]' add_label: 'false' diff --git a/.vscode/settings.json b/.vscode/settings.json index a8661326a0998..49dbf3944cfca 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -41,4 +41,4 @@ "explorer.fileNesting.patterns": { "*.ts": "${capture}.spec.ts,${capture}.mock.ts" } -} +} \ No newline at end of file diff --git a/Makefile b/Makefile index 2096cf86df09c..0899d82d24e5a 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ attach-server: renovate: LOG_LEVEL=debug npx renovate --platform=local --repository-cache=reset -MODULES = e2e server web cli sdk +MODULES = e2e server web cli sdk docs audit-%: npm --prefix $(subst sdk,open-api/typescript-sdk,$*) audit fix @@ -48,11 +48,9 @@ install-%: build-cli: build-sdk build-web: build-sdk build-%: install-% - npm --prefix $(subst sdk,open-api/typescript-sdk,$*) run | grep 'build' >/dev/null \ - && npm --prefix $(subst sdk,open-api/typescript-sdk,$*) run build || true + npm --prefix $(subst sdk,open-api/typescript-sdk,$*) run build format-%: - npm --prefix $(subst sdk,open-api/typescript-sdk,$*) run | grep 'format:fix' >/dev/null \ - && npm --prefix $(subst sdk,open-api/typescript-sdk,$*) run format:fix || true + npm --prefix $* run format:fix lint-%: npm --prefix $* run lint:fix check-%: @@ -79,14 +77,14 @@ test-medium: test-medium-dev: docker exec -it immich_server /bin/sh -c "npm run test:medium" -build-all: $(foreach M,$(MODULES),build-$M) ; +build-all: $(foreach M,$(filter-out e2e,$(MODULES)),build-$M) ; install-all: $(foreach M,$(MODULES),install-$M) ; -check-all: $(foreach M,$(MODULES),check-$M) ; -lint-all: $(foreach M,$(MODULES),lint-$M) ; -format-all: $(foreach M,$(MODULES),format-$M) ; +check-all: $(foreach M,$(filter-out sdk cli docs,$(MODULES)),check-$M) ; +lint-all: $(foreach M,$(filter-out sdk docs,$(MODULES)),lint-$M) ; +format-all: $(foreach M,$(filter-out sdk,$(MODULES)),format-$M) ; audit-all: $(foreach M,$(MODULES),audit-$M) ; hygiene-all: lint-all format-all check-all sql audit-all; -test-all: $(foreach M,$(MODULES),test-$M) ; +test-all: $(foreach M,$(filter-out sdk docs,$(MODULES)),test-$M) ; clean: find . -name "node_modules" -type d -prune -exec rm -rf '{}' + diff --git a/README.md b/README.md index 7ad539c4cd29d..0c7b1252abf75 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,8 @@ For the mobile app, you can use `https://demo.immich.app/api` for the `Server En | Offline support | Yes | No | | Read-only gallery | Yes | Yes | | Stacked Photos | Yes | Yes | +| Tags | No | Yes | +| Folder View | No | Yes | ## Translations diff --git a/cli/.nvmrc b/cli/.nvmrc index 2a393af592b8c..7af24b7ddbde0 100644 --- a/cli/.nvmrc +++ b/cli/.nvmrc @@ -1 +1 @@ -20.18.0 +22.11.0 diff --git a/cli/Dockerfile b/cli/Dockerfile index 7e141548721b9..bc7a074f5fd5a 100644 --- a/cli/Dockerfile +++ b/cli/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20.18.0-alpine3.20@sha256:c13b26e7e602ef2f1074aef304ce6e9b7dd284c419b35d89fcf3cc8e44a8def9 AS core +FROM node:22.11.0-alpine3.20@sha256:dc8ba2f61dd86c44e43eb25a7812ad03c5b1b224a19fc6f77e1eb9e5669f0b82 AS core WORKDIR /usr/src/open-api/typescript-sdk COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./ diff --git a/cli/package-lock.json b/cli/package-lock.json index c6e8c15650236..399300917131c 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1,12 +1,12 @@ { "name": "@immich/cli", - "version": "2.2.26", + "version": "2.2.31", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@immich/cli", - "version": "2.2.26", + "version": "2.2.31", "license": "GNU Affero General Public License version 3", "dependencies": { "fast-glob": "^3.3.2", @@ -24,7 +24,7 @@ "@types/cli-progress": "^3.11.0", "@types/lodash-es": "^4.17.12", "@types/mock-fs": "^4.13.1", - "@types/node": "^20.16.12", + "@types/node": "^22.9.0", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", "@vitest/coverage-v8": "^2.0.5", @@ -43,7 +43,7 @@ "vite": "^5.0.12", "vite-tsconfig-paths": "^5.0.0", "vitest": "^2.0.5", - "vitest-fetch-mock": "^0.3.0", + "vitest-fetch-mock": "^0.4.0", "yaml": "^2.3.1" }, "engines": { @@ -52,14 +52,14 @@ }, "../open-api/typescript-sdk": { "name": "@immich/sdk", - "version": "1.118.2", + "version": "1.120.2", "dev": true, "license": "GNU Affero General Public License version 3", "dependencies": { "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^20.16.12", + "@types/node": "^22.9.0", "typescript": "^5.3.3" } }, @@ -173,19 +173,21 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -276,12 +278,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", - "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.25.2" + "@babel/types": "^7.26.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -291,14 +294,14 @@ } }, "node_modules/@babel/types": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", - "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -766,9 +769,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", - "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", + "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -835,9 +838,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.12.0.tgz", - "integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", + "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", "dev": true, "license": "MIT", "engines": { @@ -1378,13 +1381,13 @@ } }, "node_modules/@types/node": { - "version": "20.16.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.13.tgz", - "integrity": "sha512-GjQ7im10B0labo8ZGXDGROUl9k0BNyDgzfGpb4g/cl+4yYDWVKcozANF4FGr4/p0O/rAkQClM6Wiwkije++1Tg==", + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.19.8" } }, "node_modules/@types/normalize-package-data": { @@ -1394,17 +1397,17 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.10.0.tgz", - "integrity": "sha512-phuB3hoP7FFKbRXxjl+DRlQDuJqhpOnm5MmtROXyWi3uS/Xg2ZXqiQfcG2BJHiN4QKyzdOJi3NEn/qTnjUlkmQ==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.12.2.tgz", + "integrity": "sha512-gQxbxM8mcxBwaEmWdtLCIGLfixBMHhQjBqR8sVWNTPpcj45WlYL2IObS/DNMLH1DBP0n8qz+aiiLTGfopPEebw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.10.0", - "@typescript-eslint/type-utils": "8.10.0", - "@typescript-eslint/utils": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0", + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/type-utils": "8.12.2", + "@typescript-eslint/utils": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1428,16 +1431,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.10.0.tgz", - "integrity": "sha512-E24l90SxuJhytWJ0pTQydFT46Nk0Z+bsLKo/L8rtQSL93rQ6byd1V/QbDpHUTdLPOMsBCcYXZweADNCfOCmOAg==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.12.2.tgz", + "integrity": "sha512-MrvlXNfGPLH3Z+r7Tk+Z5moZAc0dzdVjTgUgwsdGweH7lydysQsnSww3nAmsq8blFuRD5VRlAr9YdEFw3e6PBw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.10.0", - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/typescript-estree": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0", + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/typescript-estree": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "debug": "^4.3.4" }, "engines": { @@ -1457,14 +1460,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.10.0.tgz", - "integrity": "sha512-AgCaEjhfql9MDKjMUxWvH7HjLeBqMCBfIaBbzzIcBbQPZE7CPh1m6FF+L75NUMJFMLYhCywJXIDEMa3//1A0dw==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.12.2.tgz", + "integrity": "sha512-gPLpLtrj9aMHOvxJkSbDBmbRuYdtiEbnvO25bCMza3DhMjTQw0u7Y1M+YR5JPbMsXXnSPuCf5hfq0nEkQDL/JQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0" + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1475,14 +1478,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.10.0.tgz", - "integrity": "sha512-PCpUOpyQSpxBn230yIcK+LeCQaXuxrgCm2Zk1S+PTIRJsEfU6nJ0TtwyH8pIwPK/vJoA+7TZtzyAJSGBz+s/dg==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.12.2.tgz", + "integrity": "sha512-bwuU4TAogPI+1q/IJSKuD4shBLc/d2vGcRT588q+jzayQyjVK2X6v/fbR4InY2U2sgf8MEvVCqEWUzYzgBNcGQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.10.0", - "@typescript-eslint/utils": "8.10.0", + "@typescript-eslint/typescript-estree": "8.12.2", + "@typescript-eslint/utils": "8.12.2", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1500,9 +1503,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.10.0.tgz", - "integrity": "sha512-k/E48uzsfJCRRbGLapdZgrX52csmWJ2rcowwPvOZ8lwPUv3xW6CcFeJAXgx4uJm+Ge4+a4tFOkdYvSpxhRhg1w==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.12.2.tgz", + "integrity": "sha512-VwDwMF1SZ7wPBUZwmMdnDJ6sIFk4K4s+ALKLP6aIQsISkPv8jhiw65sAK6SuWODN/ix+m+HgbYDkH+zLjrzvOA==", "dev": true, "license": "MIT", "engines": { @@ -1514,14 +1517,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.10.0.tgz", - "integrity": "sha512-3OE0nlcOHaMvQ8Xu5gAfME3/tWVDpb/HxtpUZ1WeOAksZ/h/gwrBzCklaGzwZT97/lBbbxJ16dMA98JMEngW4w==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.12.2.tgz", + "integrity": "sha512-mME5MDwGe30Pq9zKPvyduyU86PH7aixwqYR2grTglAdB+AN8xXQ1vFGpYaUSJ5o5P/5znsSBeNcs5g5/2aQwow==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1543,16 +1546,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.10.0.tgz", - "integrity": "sha512-Oq4uZ7JFr9d1ZunE/QKy5egcDRXT/FrS2z/nlxzPua2VHFtmMvFNDvpq1m/hq0ra+T52aUezfcjGRIB7vNJF9w==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.12.2.tgz", + "integrity": "sha512-UTTuDIX3fkfAz6iSVa5rTuSfWIYZ6ATtEocQ/umkRSyC9O919lbZ8dcH7mysshrCdrAM03skJOEYaBugxN+M6A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.10.0", - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/typescript-estree": "8.10.0" + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/typescript-estree": "8.12.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1566,13 +1569,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.10.0.tgz", - "integrity": "sha512-k8nekgqwr7FadWk548Lfph6V3r9OVqjzAIVskE7orMZR23cGJjAOVazsZSJW+ElyjfTM4wx/1g88Mi70DDtG9A==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.12.2.tgz", + "integrity": "sha512-PChz8UaKQAVNHghsHcPyx1OMHoFRUEA7rJSK/mDhdq85bk+PLsUHUBqTQTFt18VJZbmxBovM65fezlheQRsSDA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.10.0", + "@typescript-eslint/types": "8.12.2", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -1584,21 +1587,21 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.3.tgz", - "integrity": "sha512-2OJ3c7UPoFSmBZwqD2VEkUw6A/tzPF0LmW0ZZhhB8PFxuc+9IBG/FaSM+RLEenc7ljzFvGN+G0nGQoZnh7sy2A==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.4.tgz", + "integrity": "sha512-FPKQuJfR6VTfcNMcGpqInmtJuVXFSCd9HQltYncfR01AzXhLucMEtQ5SinPdZxsT5x/5BK7I5qFJ5/ApGCmyTQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.6", + "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.11", - "magicast": "^0.3.4", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", "std-env": "^3.7.0", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" @@ -1607,8 +1610,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.3", - "vitest": "2.1.3" + "@vitest/browser": "2.1.4", + "vitest": "2.1.4" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -1617,15 +1620,15 @@ } }, "node_modules/@vitest/expect": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.3.tgz", - "integrity": "sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.4.tgz", + "integrity": "sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.3", - "@vitest/utils": "2.1.3", - "chai": "^5.1.1", + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -1633,22 +1636,21 @@ } }, "node_modules/@vitest/mocker": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.3.tgz", - "integrity": "sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.4.tgz", + "integrity": "sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.3", + "@vitest/spy": "2.1.4", "estree-walker": "^3.0.3", - "magic-string": "^0.30.11" + "magic-string": "^0.30.12" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/spy": "2.1.3", - "msw": "^2.3.5", + "msw": "^2.4.9", "vite": "^5.0.0" }, "peerDependenciesMeta": { @@ -1661,9 +1663,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.3.tgz", - "integrity": "sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.4.tgz", + "integrity": "sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==", "dev": true, "license": "MIT", "dependencies": { @@ -1674,13 +1676,13 @@ } }, "node_modules/@vitest/runner": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.3.tgz", - "integrity": "sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.4.tgz", + "integrity": "sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.3", + "@vitest/utils": "2.1.4", "pathe": "^1.1.2" }, "funding": { @@ -1688,14 +1690,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.3.tgz", - "integrity": "sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.4.tgz", + "integrity": "sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.3", - "magic-string": "^0.30.11", + "@vitest/pretty-format": "2.1.4", + "magic-string": "^0.30.12", "pathe": "^1.1.2" }, "funding": { @@ -1703,27 +1705,27 @@ } }, "node_modules/@vitest/spy": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.3.tgz", - "integrity": "sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.4.tgz", + "integrity": "sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==", "dev": true, "license": "MIT", "dependencies": { - "tinyspy": "^3.0.0" + "tinyspy": "^3.0.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.3.tgz", - "integrity": "sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.4.tgz", + "integrity": "sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.3", - "loupe": "^3.1.1", + "@vitest/pretty-format": "2.1.4", + "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -1930,9 +1932,9 @@ ] }, "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, "license": "MIT", "dependencies": { @@ -2067,15 +2069,6 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "dependencies": { - "node-fetch": "^2.6.12" - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2091,12 +2084,13 @@ } }, "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -2211,18 +2205,18 @@ } }, "node_modules/eslint": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz", - "integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz", + "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.6.0", + "@eslint/core": "^0.7.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.12.0", + "@eslint/js": "9.13.0", "@eslint/plugin-kit": "^0.2.0", "@humanfs/node": "^0.16.5", "@humanwhocodes/module-importer": "^1.0.1", @@ -2396,9 +2390,9 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", - "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2505,6 +2499,16 @@ "node": ">=0.10.0" } }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3061,22 +3065,24 @@ "dev": true }, "node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/magicast": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", - "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/parser": "^7.24.4", - "@babel/types": "^7.24.0", + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, @@ -3150,9 +3156,9 @@ } }, "node_modules/mock-fs": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.4.0.tgz", - "integrity": "sha512-3ROPnEMgBOkusBMYQUW2rnT3wZwsgfOKzJDLvx/TZ7FL1WmWvwSwn3j4aDR5fLDGtgcc1WF0Z1y0di7c9L4FKw==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.4.1.tgz", + "integrity": "sha512-sz/Q8K1gXXXHR+qr0GZg2ysxCRr323kuN10O7CtQjraJsFDJ4SJ+0I5MzALz7aRp9lHk8Cc/YdsT95h9Ka1aFw==", "dev": true, "license": "MIT", "engines": { @@ -3160,10 +3166,11 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.7", @@ -3189,26 +3196,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -4000,17 +3987,18 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", - "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", "dev": true, "license": "MIT" }, "node_modules/tinypool": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.0.tgz", - "integrity": "sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" } @@ -4034,15 +4022,6 @@ "node": ">=14.0.0" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4054,12 +4033,6 @@ "node": ">=8.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -4181,9 +4154,9 @@ } }, "node_modules/vite": { - "version": "5.4.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz", - "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==", + "version": "5.4.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", + "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4241,14 +4214,14 @@ } }, "node_modules/vite-node": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.3.tgz", - "integrity": "sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.4.tgz", + "integrity": "sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.6", + "debug": "^4.3.7", "pathe": "^1.1.2", "vite": "^5.0.0" }, @@ -4283,30 +4256,31 @@ } }, "node_modules/vitest": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.3.tgz", - "integrity": "sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.4.tgz", + "integrity": "sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "2.1.3", - "@vitest/mocker": "2.1.3", - "@vitest/pretty-format": "^2.1.3", - "@vitest/runner": "2.1.3", - "@vitest/snapshot": "2.1.3", - "@vitest/spy": "2.1.3", - "@vitest/utils": "2.1.3", - "chai": "^5.1.1", - "debug": "^4.3.6", - "magic-string": "^0.30.11", + "@vitest/expect": "2.1.4", + "@vitest/mocker": "2.1.4", + "@vitest/pretty-format": "^2.1.4", + "@vitest/runner": "2.1.4", + "@vitest/snapshot": "2.1.4", + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.7.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.0", - "tinypool": "^1.0.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.1.3", + "vite-node": "2.1.4", "why-is-node-running": "^2.3.0" }, "bin": { @@ -4321,8 +4295,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.3", - "@vitest/ui": "2.1.3", + "@vitest/browser": "2.1.4", + "@vitest/ui": "2.1.4", "happy-dom": "*", "jsdom": "*" }, @@ -4348,36 +4322,18 @@ } }, "node_modules/vitest-fetch-mock": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/vitest-fetch-mock/-/vitest-fetch-mock-0.3.0.tgz", - "integrity": "sha512-g6upWcL8/32fXL43/5f4VHcocuwQIi9Fj5othcK9gPO8XqSEGtnIZdenr2IaipDr61ReRFt+vaOEgo8jiUUX5w==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/vitest-fetch-mock/-/vitest-fetch-mock-0.4.1.tgz", + "integrity": "sha512-Y6VEV2AgJps1t9NUdhID/vUwarAuhOkPHShfoEruIlQr5+O31hgJ4YmZpU8kVWD3KQjEyZqPeMibWehd7rMq+A==", "dev": true, - "dependencies": { - "cross-fetch": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=14.14.0" + "node": ">=18.0.0" }, "peerDependencies": { "vitest": ">=2.0.0" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/cli/package.json b/cli/package.json index 57cbc502ed8e2..4c668d99d8b9d 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@immich/cli", - "version": "2.2.26", + "version": "2.2.31", "description": "Command Line Interface (CLI) for Immich", "type": "module", "exports": "./dist/index.js", @@ -20,7 +20,7 @@ "@types/cli-progress": "^3.11.0", "@types/lodash-es": "^4.17.12", "@types/mock-fs": "^4.13.1", - "@types/node": "^20.16.12", + "@types/node": "^22.9.0", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", "@vitest/coverage-v8": "^2.0.5", @@ -39,7 +39,7 @@ "vite": "^5.0.12", "vite-tsconfig-paths": "^5.0.0", "vitest": "^2.0.5", - "vitest-fetch-mock": "^0.3.0", + "vitest-fetch-mock": "^0.4.0", "yaml": "^2.3.1" }, "scripts": { @@ -67,6 +67,6 @@ "lodash-es": "^4.17.21" }, "volta": { - "node": "20.18.0" + "node": "22.11.0" } } diff --git a/cli/src/commands/asset.ts b/cli/src/commands/asset.ts index 9c1a503cda1a0..4cf6742f24669 100644 --- a/cli/src/commands/asset.ts +++ b/cli/src/commands/asset.ts @@ -1,5 +1,6 @@ import { Action, + AssetBulkUploadCheckItem, AssetBulkUploadCheckResult, AssetMediaResponseDto, AssetMediaStatus, @@ -11,7 +12,7 @@ import { getSupportedMediaTypes, } from '@immich/sdk'; import byteSize from 'byte-size'; -import { Presets, SingleBar } from 'cli-progress'; +import { MultiBar, Presets, SingleBar } from 'cli-progress'; import { chunk } from 'lodash-es'; import { Stats, createReadStream } from 'node:fs'; import { stat, unlink } from 'node:fs/promises'; @@ -90,23 +91,23 @@ export const checkForDuplicates = async (files: string[], { concurrency, skipHas return { newFiles: files, duplicates: [] }; } - const progressBar = new SingleBar( - { format: 'Checking files | {bar} | {percentage}% | ETA: {eta}s | {value}/{total} assets' }, + const multiBar = new MultiBar( + { format: '{message} | {bar} | {percentage}% | ETA: {eta}s | {value}/{total} assets' }, Presets.shades_classic, ); - progressBar.start(files.length, 0); + const hashProgressBar = multiBar.create(files.length, 0, { message: 'Hashing files ' }); + const checkProgressBar = multiBar.create(files.length, 0, { message: 'Checking for duplicates' }); const newFiles: string[] = []; const duplicates: Asset[] = []; - const queue = new Queue( - async (filepaths: string[]) => { - const dto = await Promise.all( - filepaths.map(async (filepath) => ({ id: filepath, checksum: await sha1(filepath) })), - ); - const response = await checkBulkUpload({ assetBulkUploadCheckDto: { assets: dto } }); + const checkBulkUploadQueue = new Queue( + async (assets: AssetBulkUploadCheckItem[]) => { + const response = await checkBulkUpload({ assetBulkUploadCheckDto: { assets } }); + const results = response.results as AssetBulkUploadCheckResults; + for (const { id: filepath, assetId, action } of results) { if (action === Action.Accept) { newFiles.push(filepath); @@ -115,19 +116,46 @@ export const checkForDuplicates = async (files: string[], { concurrency, skipHas duplicates.push({ id: assetId as string, filepath }); } } - progressBar.increment(filepaths.length); + + checkProgressBar.increment(assets.length); + }, + { concurrency, retry: 3 }, + ); + + const results: { id: string; checksum: string }[] = []; + let checkBulkUploadRequests: AssetBulkUploadCheckItem[] = []; + + const queue = new Queue( + async (filepath: string): Promise => { + const dto = { id: filepath, checksum: await sha1(filepath) }; + + results.push(dto); + checkBulkUploadRequests.push(dto); + if (checkBulkUploadRequests.length === 5000) { + const batch = checkBulkUploadRequests; + checkBulkUploadRequests = []; + void checkBulkUploadQueue.push(batch); + } + + hashProgressBar.increment(); return results; }, { concurrency, retry: 3 }, ); - for (const items of chunk(files, concurrency)) { - await queue.push(items); + for (const item of files) { + void queue.push(item); } await queue.drained(); - progressBar.stop(); + if (checkBulkUploadRequests.length > 0) { + void checkBulkUploadQueue.push(checkBulkUploadRequests); + } + + await checkBulkUploadQueue.drained(); + + multiBar.stop(); console.log(`Found ${newFiles.length} new files and ${duplicates.length} duplicate${s(duplicates.length)}`); @@ -201,8 +229,8 @@ export const uploadFiles = async (files: string[], { dryRun, concurrency }: Uplo { concurrency, retry: 3 }, ); - for (const filepath of files) { - await queue.push(filepath); + for (const item of files) { + void queue.push(item); } await queue.drained(); diff --git a/cli/src/queue.ts b/cli/src/queue.ts index c700028a158a9..0b6d6281461cc 100644 --- a/cli/src/queue.ts +++ b/cli/src/queue.ts @@ -72,8 +72,8 @@ export class Queue { * @returns Promise - The returned Promise will be resolved when all tasks in the queue have been processed by a worker. * This promise could be ignored as it will not lead to a `unhandledRejection`. */ - async drained(): Promise { - await this.queue.drain(); + drained(): Promise { + return this.queue.drained(); } /** diff --git a/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl b/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl index 165537096b376..995f5d5b69f64 100644 --- a/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl +++ b/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl @@ -2,37 +2,37 @@ # Manual edits may be lost in future updates. provider "registry.opentofu.org/cloudflare/cloudflare" { - version = "4.44.0" - constraints = "4.44.0" + version = "4.45.0" + constraints = "4.45.0" hashes = [ - "h1:2kgcFIKdPgw/22FoLI+54G2+Np3L0np8ost6e8VwAhU=", - "h1:5FQNWKXde8kkfEJHeu5yBaC0NxCzeOKs2K0QMGTicAQ=", - "h1:8rq9U72Ieus2zn1M1FVKgEnEO2f+ZFx/ISMefIEt6kc=", - "h1:MeMGdavako/OPTU/qAgKRIQKD49x9tn4PGrOTWi9tFE=", - "h1:PIP4W0AHpcV+jnKMlZmNd6MtfXMJlKjBdR2DYOX8rTs=", - "h1:TSWZWvxliZCajkWOq5wOSsB6TJdu3nhhaaAcFL6aBdM=", - "h1:VKuCmJyi+i9UlIHl2ezQkmGZgZMm8E+WHkxTheDGcFc=", - "h1:WL/GOSfwJuTfyGnl17hXxBA24Pe+pG5o6ONKfGdAesM=", - "h1:XicUpPrAY/QJvE3qncz/LtAfeS43fXgI5lihOBmWzpI=", - "h1:YftzVnHzmOxfK9an5wzHnB7kLAEMzoNnEgRffntMIFI=", - "h1:ZQrgBiB//NLYh3K6gOT1nJp5ezBqUQJ1Qx5eTpMpBB4=", - "h1:cRV923M29H/H3NfvlVGsGkvyn8P7ovsMU5BNb9U1d+o=", - "h1:qivroEJiR4Vycmc4E4DherPkBCAm4xfet2Sr87uIXhY=", - "h1:z2119TMCuS7zEYg2oDYff5EnHWtYSVv0mcOJ25wl3i8=", - "zh:0cae95e8c9d2d979669712745251dcf5720cee2a59bb81d8ad2c2dcf0e6e0c7a", - "zh:1220aee9549e7938648f6a36237929ead0de8244c6a00f8e8cded559f4b65a2f", - "zh:23ae1862e5fe5b583b8ec2c96f80a5ba0e3883be8e1169a0484a45106cc238ce", - "zh:3034654c6f34e419c53dcd6ea558b715e1150fbcc70c93209c5ee88a03025072", - "zh:3b64a66f3ddeb04345511262ad9376eb3c26e0683a78f47a3fd7f5e71f3f7e27", - "zh:4b29435e1e8f970b92bb38eca52820f7a8362c16235334aef9a83be32bd00094", - "zh:4f8fe69db7f54bce0e78a4c671aa5db20515114626035051f387d9833f4a5a91", - "zh:86776bfbdabd2095975be9b3ca999c2f47ca5194ece6c58c69130ccfa2e3c97d", + "h1:/CGpnYMkLRDmqn4iAsh/jg7ELZ6QExUw03VdjKZyK5M=", + "h1:82C/ryqwQvxhBINYOOyF5ZzPW/k4zJ/RYT13eCdPgEc=", + "h1:8Wu1D7ZwbLGdHakLRAzoAJ5VqZ8I14qzkPv1OGNfIlg=", + "h1:CVq0CAibeueOuiNk0UQtwZvMLMof33n1BgskFPOymrk=", + "h1:FSS5Kq+L+CX1zARy8PhaF8edBFNgsLtds4Uo8MwJiK8=", + "h1:L4qsorLII7f8xSFmv6JOoWfLWDunWQEpK964Bxk7mtM=", + "h1:StO3PV5PDskSCnhoHhWHOPxu6hbzJUQggfLgOSkvhwg=", + "h1:Tjo+Er9ets5YrTRIdP9LBmi4p89nL/W+A7r8a1MM9nI=", + "h1:XIwT+AWvks1LTytePM9zls+O8ItxoqCfPOgHwuH9ivQ=", + "h1:aOXn/zuM1+5GGy/SSRx8q4EYCSTFE9Tr0twHPIf5/KE=", + "h1:lb+YcuZ4guYd8zE51vgSnDsRAD9IV00Z15l1i1X52s8=", + "h1:pYwNXGjfXA2rUEmotGMLWgmavT9D2rdHnV3TpuIK3ko=", + "h1:q1qrnPq6KkljwBrugCwzb7f0SVP4Lzkfh+EOLARY9V8=", + "h1:v9sL4cZLTV5Gu2004DDyy7209gT0JmudBCAD0WCr/JE=", + "zh:00be2a6adc76615a368491c7a026098103b6286deb31e3cfb037365dd39f095f", + "zh:05bd072e6119f7a5abff05c6064001f745473119a956586cf77ae843cf55d666", + "zh:228bbe61345c4e8e0bc6b698b4b9652abff65662ee72ede2aecb4c3efb91b243", + "zh:2948aeefe71ba041c94082cf931ecc95510d93af0a61d0a287880f5b9d24b11a", + "zh:5dfc2c5e95843ca54957212ee3ecb7ff06f2cf60bfd6ca278b5249fd70ac18f5", + "zh:69922cb45559b0b0544b9c2d31ed2d0fac9121faa75bc2f523484785b45d8e2b", "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:9d50271a09ee01a7105c06e582c52743a3baaf46f298d52bfc9e64cd7bfaa712", - "zh:a1e12d1c3472d457140de0e8b77a4b09e5cdcd3e2f6c0be0fe0dae0526d368db", - "zh:ad638e2c91490367d55ec6fe46ee34a9f7c151ca6e3cc52e5bad9f358e77f1aa", - "zh:cc4c496f2c594994a9d966f7ebe00a797eca9b924ac1bbe5aef26ec83ec7f833", - "zh:e74d5a3695deb38c2858d1c99c424495900e9b298ca8961c0a2fc1b3714c4c77", - "zh:f4b6efa4c2b4c85c92171dc0824dbf42af9dde5250131494de803e0b9fe1ea3c", + "zh:9d83a0cbf72327286f7dbd63cd4af89059c648163fe6ed21b1df768e0518d445", + "zh:a8e1982945822c7d7aaa6ba8602c7247d1a3fad15d612f30eb323491a637bf8d", + "zh:c6d41ebd69ddb23e3dad49a0ebf1da5a9c7d8706a4f55d953115d371f407928b", + "zh:d03e5442b12846c2737f099d30cd23d9f85a0c6d65437ccb44819f9a6c4e1d7f", + "zh:d446f2e1186b35037aea03b0e27d8b032d2f069f194f84b3f0e2907b3a79a955", + "zh:e4d7549a4c856524e01f3dd4d69f57119ea205f7a0fa38dcfe154475b4ae9258", + "zh:e64b8915cb9686f85e77115bd674f2faf4f29880688067d7d0f1376566fdb3b0", + "zh:f046efdc55e6385cdd69baaa06a929bef9fe6809d373b0d2d6c7df8f8c23eddc", ] } diff --git a/deployment/modules/cloudflare/docs-release/config.tf b/deployment/modules/cloudflare/docs-release/config.tf index e8c28c08d2f3b..85e095f195a12 100644 --- a/deployment/modules/cloudflare/docs-release/config.tf +++ b/deployment/modules/cloudflare/docs-release/config.tf @@ -5,7 +5,7 @@ terraform { required_providers { cloudflare = { source = "cloudflare/cloudflare" - version = "4.44.0" + version = "4.45.0" } } } diff --git a/deployment/modules/cloudflare/docs/.terraform.lock.hcl b/deployment/modules/cloudflare/docs/.terraform.lock.hcl index 165537096b376..995f5d5b69f64 100644 --- a/deployment/modules/cloudflare/docs/.terraform.lock.hcl +++ b/deployment/modules/cloudflare/docs/.terraform.lock.hcl @@ -2,37 +2,37 @@ # Manual edits may be lost in future updates. provider "registry.opentofu.org/cloudflare/cloudflare" { - version = "4.44.0" - constraints = "4.44.0" + version = "4.45.0" + constraints = "4.45.0" hashes = [ - "h1:2kgcFIKdPgw/22FoLI+54G2+Np3L0np8ost6e8VwAhU=", - "h1:5FQNWKXde8kkfEJHeu5yBaC0NxCzeOKs2K0QMGTicAQ=", - "h1:8rq9U72Ieus2zn1M1FVKgEnEO2f+ZFx/ISMefIEt6kc=", - "h1:MeMGdavako/OPTU/qAgKRIQKD49x9tn4PGrOTWi9tFE=", - "h1:PIP4W0AHpcV+jnKMlZmNd6MtfXMJlKjBdR2DYOX8rTs=", - "h1:TSWZWvxliZCajkWOq5wOSsB6TJdu3nhhaaAcFL6aBdM=", - "h1:VKuCmJyi+i9UlIHl2ezQkmGZgZMm8E+WHkxTheDGcFc=", - "h1:WL/GOSfwJuTfyGnl17hXxBA24Pe+pG5o6ONKfGdAesM=", - "h1:XicUpPrAY/QJvE3qncz/LtAfeS43fXgI5lihOBmWzpI=", - "h1:YftzVnHzmOxfK9an5wzHnB7kLAEMzoNnEgRffntMIFI=", - "h1:ZQrgBiB//NLYh3K6gOT1nJp5ezBqUQJ1Qx5eTpMpBB4=", - "h1:cRV923M29H/H3NfvlVGsGkvyn8P7ovsMU5BNb9U1d+o=", - "h1:qivroEJiR4Vycmc4E4DherPkBCAm4xfet2Sr87uIXhY=", - "h1:z2119TMCuS7zEYg2oDYff5EnHWtYSVv0mcOJ25wl3i8=", - "zh:0cae95e8c9d2d979669712745251dcf5720cee2a59bb81d8ad2c2dcf0e6e0c7a", - "zh:1220aee9549e7938648f6a36237929ead0de8244c6a00f8e8cded559f4b65a2f", - "zh:23ae1862e5fe5b583b8ec2c96f80a5ba0e3883be8e1169a0484a45106cc238ce", - "zh:3034654c6f34e419c53dcd6ea558b715e1150fbcc70c93209c5ee88a03025072", - "zh:3b64a66f3ddeb04345511262ad9376eb3c26e0683a78f47a3fd7f5e71f3f7e27", - "zh:4b29435e1e8f970b92bb38eca52820f7a8362c16235334aef9a83be32bd00094", - "zh:4f8fe69db7f54bce0e78a4c671aa5db20515114626035051f387d9833f4a5a91", - "zh:86776bfbdabd2095975be9b3ca999c2f47ca5194ece6c58c69130ccfa2e3c97d", + "h1:/CGpnYMkLRDmqn4iAsh/jg7ELZ6QExUw03VdjKZyK5M=", + "h1:82C/ryqwQvxhBINYOOyF5ZzPW/k4zJ/RYT13eCdPgEc=", + "h1:8Wu1D7ZwbLGdHakLRAzoAJ5VqZ8I14qzkPv1OGNfIlg=", + "h1:CVq0CAibeueOuiNk0UQtwZvMLMof33n1BgskFPOymrk=", + "h1:FSS5Kq+L+CX1zARy8PhaF8edBFNgsLtds4Uo8MwJiK8=", + "h1:L4qsorLII7f8xSFmv6JOoWfLWDunWQEpK964Bxk7mtM=", + "h1:StO3PV5PDskSCnhoHhWHOPxu6hbzJUQggfLgOSkvhwg=", + "h1:Tjo+Er9ets5YrTRIdP9LBmi4p89nL/W+A7r8a1MM9nI=", + "h1:XIwT+AWvks1LTytePM9zls+O8ItxoqCfPOgHwuH9ivQ=", + "h1:aOXn/zuM1+5GGy/SSRx8q4EYCSTFE9Tr0twHPIf5/KE=", + "h1:lb+YcuZ4guYd8zE51vgSnDsRAD9IV00Z15l1i1X52s8=", + "h1:pYwNXGjfXA2rUEmotGMLWgmavT9D2rdHnV3TpuIK3ko=", + "h1:q1qrnPq6KkljwBrugCwzb7f0SVP4Lzkfh+EOLARY9V8=", + "h1:v9sL4cZLTV5Gu2004DDyy7209gT0JmudBCAD0WCr/JE=", + "zh:00be2a6adc76615a368491c7a026098103b6286deb31e3cfb037365dd39f095f", + "zh:05bd072e6119f7a5abff05c6064001f745473119a956586cf77ae843cf55d666", + "zh:228bbe61345c4e8e0bc6b698b4b9652abff65662ee72ede2aecb4c3efb91b243", + "zh:2948aeefe71ba041c94082cf931ecc95510d93af0a61d0a287880f5b9d24b11a", + "zh:5dfc2c5e95843ca54957212ee3ecb7ff06f2cf60bfd6ca278b5249fd70ac18f5", + "zh:69922cb45559b0b0544b9c2d31ed2d0fac9121faa75bc2f523484785b45d8e2b", "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:9d50271a09ee01a7105c06e582c52743a3baaf46f298d52bfc9e64cd7bfaa712", - "zh:a1e12d1c3472d457140de0e8b77a4b09e5cdcd3e2f6c0be0fe0dae0526d368db", - "zh:ad638e2c91490367d55ec6fe46ee34a9f7c151ca6e3cc52e5bad9f358e77f1aa", - "zh:cc4c496f2c594994a9d966f7ebe00a797eca9b924ac1bbe5aef26ec83ec7f833", - "zh:e74d5a3695deb38c2858d1c99c424495900e9b298ca8961c0a2fc1b3714c4c77", - "zh:f4b6efa4c2b4c85c92171dc0824dbf42af9dde5250131494de803e0b9fe1ea3c", + "zh:9d83a0cbf72327286f7dbd63cd4af89059c648163fe6ed21b1df768e0518d445", + "zh:a8e1982945822c7d7aaa6ba8602c7247d1a3fad15d612f30eb323491a637bf8d", + "zh:c6d41ebd69ddb23e3dad49a0ebf1da5a9c7d8706a4f55d953115d371f407928b", + "zh:d03e5442b12846c2737f099d30cd23d9f85a0c6d65437ccb44819f9a6c4e1d7f", + "zh:d446f2e1186b35037aea03b0e27d8b032d2f069f194f84b3f0e2907b3a79a955", + "zh:e4d7549a4c856524e01f3dd4d69f57119ea205f7a0fa38dcfe154475b4ae9258", + "zh:e64b8915cb9686f85e77115bd674f2faf4f29880688067d7d0f1376566fdb3b0", + "zh:f046efdc55e6385cdd69baaa06a929bef9fe6809d373b0d2d6c7df8f8c23eddc", ] } diff --git a/deployment/modules/cloudflare/docs/config.tf b/deployment/modules/cloudflare/docs/config.tf index e8c28c08d2f3b..85e095f195a12 100644 --- a/deployment/modules/cloudflare/docs/config.tf +++ b/deployment/modules/cloudflare/docs/config.tf @@ -5,7 +5,7 @@ terraform { required_providers { cloudflare = { source = "cloudflare/cloudflare" - version = "4.44.0" + version = "4.45.0" } } } diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index f2509e0d2f522..1487f8adbef34 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -103,7 +103,7 @@ services: redis: container_name: immich_redis - image: redis:6.2-alpine@sha256:2ba50e1ac3a0ea17b736ce9db2b0a9f6f8b85d4c27d5f5accc6a416d8f42c6d5 + image: redis:6.2-alpine@sha256:eaba718fecd1196d88533de7ba49bf903ad33664a92debb24660a922ecd9cac8 healthcheck: test: redis-cli ping || exit 1 @@ -143,7 +143,7 @@ services: 'wal_compression=on', ] - # set IMMICH_METRICS=true in .env to enable metrics + # set IMMICH_TELEMETRY_INCLUDE=all in .env to enable metrics # immich-prometheus: # container_name: immich_prometheus # ports: diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index 8358097bf330d..96e324f0d9199 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -47,7 +47,7 @@ services: redis: container_name: immich_redis - image: redis:6.2-alpine@sha256:2ba50e1ac3a0ea17b736ce9db2b0a9f6f8b85d4c27d5f5accc6a416d8f42c6d5 + image: redis:6.2-alpine@sha256:eaba718fecd1196d88533de7ba49bf903ad33664a92debb24660a922ecd9cac8 healthcheck: test: redis-cli ping || exit 1 restart: always @@ -89,12 +89,12 @@ services: ] restart: always - # set IMMICH_METRICS=true in .env to enable metrics + # set IMMICH_TELEMETRY_INCLUDE=all in .env to enable metrics immich-prometheus: container_name: immich_prometheus ports: - 9090:9090 - image: prom/prometheus@sha256:378f4e03703557d1c6419e6caccf922f96e6d88a530f7431d66a4c4f4b1000fe + image: prom/prometheus@sha256:3b9b2a15d376334da8c286d995777d3b9315aa666d2311170ada6059a517b74f volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus @@ -106,7 +106,7 @@ services: command: ['./run.sh', '-disable-reporting'] ports: - 3000:3000 - image: grafana/grafana:11.2.2-ubuntu@sha256:2bef00403c18d27919ff19d64fd6253fa713b3880304e92f69109e14221ac843 + image: grafana/grafana:11.3.0-ubuntu@sha256:51587e148ac0214d7938e7f3fe8512182e4eb6141892a3ffb88bba1901b49285 volumes: - grafana-data:/var/lib/grafana diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 979343364c12f..86ec637cbb850 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -48,7 +48,7 @@ services: redis: container_name: immich_redis - image: docker.io/redis:6.2-alpine@sha256:2ba50e1ac3a0ea17b736ce9db2b0a9f6f8b85d4c27d5f5accc6a416d8f42c6d5 + image: docker.io/redis:6.2-alpine@sha256:eaba718fecd1196d88533de7ba49bf903ad33664a92debb24660a922ecd9cac8 healthcheck: test: redis-cli ping || exit 1 restart: always diff --git a/docs/.nvmrc b/docs/.nvmrc index 2a393af592b8c..7af24b7ddbde0 100644 --- a/docs/.nvmrc +++ b/docs/.nvmrc @@ -1 +1 @@ -20.18.0 +22.11.0 diff --git a/docs/docs/administration/backup-and-restore.md b/docs/docs/administration/backup-and-restore.md index 9b5793054ba5e..9ae4e3e51fa39 100644 --- a/docs/docs/administration/backup-and-restore.md +++ b/docs/docs/administration/backup-and-restore.md @@ -15,12 +15,21 @@ Immich saves [file paths in the database](https://github.com/immich-app/immich/d Refer to the official [postgres documentation](https://www.postgresql.org/docs/current/backup.html) for details about backing up and restoring a postgres database. ::: -The recommended way to backup and restore the Immich database is to use the `pg_dumpall` command. When restoring, you need to delete the `DB_DATA_LOCATION` folder (if it exists) to reset the database. - :::caution It is not recommended to directly backup the `DB_DATA_LOCATION` folder. Doing so while the database is running can lead to a corrupted backup that cannot be restored. ::: +### Automatic Database Backups + +Immich will automatically create database backups by default. The backups are stored in `UPLOAD_LOCATION/backups`. +You can adjust the schedule and amount of kept backups in the [admin settings](http://my.immich.app/admin/system-settings?isOpen=backup). +By default, Immich will keep the last 14 backups and create a new backup every day at 2:00 AM. + +#### Restoring + +We hope to make restoring simpler in future versions, for now you can find the backups in the `UPLOAD_LOCATION/backups` folder on your host. +Then please follow the steps in the following section for restoring the database. + ### Manual Backup and Restore @@ -49,7 +58,7 @@ docker compose up -d # Start remainder of Immich apps ```powershell title='Backup' -docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=postgres | Set-Content -Encoding utf8 "C:\path\to\backup\dump.sql" +[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' @@ -68,53 +77,10 @@ docker compose up -d # Start remainder of Immich apps -Note that for the database restore to proceed properly, it requires a completely fresh install (i.e. the Immich server has never run since creating the Docker containers). If the Immich app has run, Postgres conflicts may be encountered upon database restoration (relation already exists, violated foreign key constraints, multiple primary keys, etc.). +Note that for the database restore to proceed properly, it requires a completely fresh install (i.e. the Immich server has never run since creating the Docker containers). If the Immich app has run, Postgres conflicts may be encountered upon database restoration (relation already exists, violated foreign key constraints, multiple primary keys, etc.), in which case you need to delete the `DB_DATA_LOCATION` folder to reset the database. :::tip -Some deployment methods make it difficult to start the database without also starting the server or microservices. In these cases, you may set the environmental variable `DB_SKIP_MIGRATIONS=true` before starting the services. This will prevent the server from running migrations that interfere with the restore process. Note that both the server and microservices must have this variable set to prevent the migrations from running. Be sure to remove this variable and restart the services after the database is restored. -::: - -### Automatic Database Backups - -The database dumps can also be automated (using [this image](https://github.com/prodrigestivill/docker-postgres-backup-local)) by editing the docker compose file to match the following: - -```yaml -services: - ... - backup: - container_name: immich_db_dumper - image: prodrigestivill/postgres-backup-local:14 - restart: always - env_file: - - .env - environment: - POSTGRES_HOST: database - POSTGRES_CLUSTER: 'TRUE' - POSTGRES_USER: ${DB_USERNAME} - POSTGRES_PASSWORD: ${DB_PASSWORD} - POSTGRES_DB: ${DB_DATABASE_NAME} - SCHEDULE: "@daily" - POSTGRES_EXTRA_OPTS: '--clean --if-exists' - BACKUP_DIR: /db_dumps - volumes: - - ./db_dumps:/db_dumps - depends_on: - - database -``` - -Then you can restore with the same command but pointed at the latest dump. - -```bash title='Automated Restore' -# Be sure to check the username if you changed it from default -gunzip < db_dumps/last/immich-latest.sql.gz \ -| sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \ -| docker exec -i immich_postgres psql --username=postgres -``` - -:::note -If you see the error `ERROR: type "earth" does not exist`, or you have problems with Reverse Geocoding after a restore, add the following `sed` fragment to your restore command. - -Example: `gunzip < "/path/to/backup/dump.sql.gz" | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" | docker exec -i immich_postgres psql --username=postgres` +Some deployment methods make it difficult to start the database without also starting the server. In these cases, you may set the environment variable `DB_SKIP_MIGRATIONS=true` before starting the services. This will prevent the server from running migrations that interfere with the restore process. Be sure to remove this variable and restart the services after the database is restored. ::: ## Filesystem diff --git a/docs/docs/administration/reverse-proxy.md b/docs/docs/administration/reverse-proxy.md index c167a10d7fbc5..25762ad7f122d 100644 --- a/docs/docs/administration/reverse-proxy.md +++ b/docs/docs/administration/reverse-proxy.md @@ -40,6 +40,26 @@ server { } ``` +#### Compatibility with Let's Encrypt + +In the event that your nginx configuration includes a section for Let's Encrypt, it's likely that you have a segment similar to the following: + +```nginx +location ~ /.well-known { + ... +} +``` + +This particular `location` directive can inadvertently prevent mobile clients from reaching the `/.well-known/immich` path, which is crucial for discovery. Usual error message for this case is: "Your app major version is not compatible with the server". To remedy this, you should introduce an additional location block specifically for this path, ensuring that requests are correctly proxied to the Immich server: + +```nginx +location = /.well-known/immich { + proxy_pass http://:2283; +} +``` + +By doing so, you'll maintain the functionality of Let's Encrypt while allowing mobile clients to access the necessary Immich path without obstruction. + ### Caddy example config As an alternative to nginx, you can also use [Caddy](https://caddyserver.com/) as a reverse proxy (with automatic HTTPS configuration). Below is an example config. diff --git a/docs/docs/administration/system-integrity.md b/docs/docs/administration/system-integrity.md index 5440e42489d83..2b373134a9a4a 100644 --- a/docs/docs/administration/system-integrity.md +++ b/docs/docs/administration/system-integrity.md @@ -3,7 +3,7 @@ ## Folder checks :::info -The folders considered for these checks include: `upload/`, `library/`, `thumbs/`, `encoded-video/`, `profile/` +The folders considered for these checks include: `upload/`, `library/`, `thumbs/`, `encoded-video/`, `profile/`, `backups/` ::: When Immich starts, it performs a series of checks in order to validate that it can read and write files to the volume mounts used by the storage system. If it cannot perform all the required operations, it will fail to start. The checks include: @@ -40,7 +40,9 @@ The above error messages show that the server has previously (successfully) writ ### Ignoring the checks -The checks are designed to catch common problems that we have seen users have in the past, but if you want to disable them you can set the following environment variable: +:::warning +The checks are designed to catch common problems that we have seen users have in the past, and often indicate there's something wrong that you should solve. If you know what you're doing and you want to disable them you can set the following environment variable: +::: ``` IMMICH_IGNORE_MOUNT_CHECK_ERRORS=true diff --git a/docs/docs/developer/pr-checklist.md b/docs/docs/developer/pr-checklist.md index d2e7fbee4044f..6015694976e37 100644 --- a/docs/docs/developer/pr-checklist.md +++ b/docs/docs/developer/pr-checklist.md @@ -1,5 +1,9 @@ # PR Checklist +A minimal devcontainer is supplied with this repository. All commands can be executed directly inside this container to avoid tedious installation of the environment. +:::warning +The provided devcontainer isn't complete at the moment. At least all dockerized steps in the Makefile won't work (`make dev`, ....). Feel free to contribute! +::: When contributing code through a pull request, please check the following: ## Web Checks diff --git a/docs/docs/developer/setup.md b/docs/docs/developer/setup.md index 32e79849efdcf..e7bde2178b54b 100644 --- a/docs/docs/developer/setup.md +++ b/docs/docs/developer/setup.md @@ -76,7 +76,7 @@ Setting these in the IDE give a better developer experience, auto-formatting cod ### Dart Code Metrics -The mobile app uses DCM (Dart Code Metrics) for linting and metrics calculation. Please refer to the [Getting Started](https://dcm.dev/docs/getting-started/#installation) page for more information on setting up DCM +The mobile app uses DCM (Dart Code Metrics) for linting and metrics calculation. Please refer to the [Getting Started](https://dcm.dev/docs/) page for more information on setting up DCM Note: Activating the license is not required. diff --git a/docs/docs/features/hardware-transcoding.md b/docs/docs/features/hardware-transcoding.md index 4f059281f35f0..a561bafa8030d 100644 --- a/docs/docs/features/hardware-transcoding.md +++ b/docs/docs/features/hardware-transcoding.md @@ -1,7 +1,7 @@ # Hardware Transcoding [Experimental] This feature allows you to use a GPU to accelerate transcoding and reduce CPU load. -Note that hardware transcoding is much less efficient for file sizes. +Note that hardware transcoding produces significantly larger videos than software transcoding with similar settings, typically with lower quality. Using slow presets and preferring more efficient codecs can narrow this gap. As this is a new feature, it is still experimental and may not work on all systems. :::info diff --git a/docs/docs/features/img/folder-view.png b/docs/docs/features/img/folder-view.png new file mode 100644 index 0000000000000..8193b10ed96dc Binary files /dev/null and b/docs/docs/features/img/folder-view.png differ diff --git a/docs/docs/features/libraries.md b/docs/docs/features/libraries.md index 17555469543c8..1d6028935f9a3 100644 --- a/docs/docs/features/libraries.md +++ b/docs/docs/features/libraries.md @@ -149,6 +149,22 @@ If you get an error here, please rename the other external library to something Within seconds, the assets from the old-pics and videos folders should show up in the main timeline. +### Folder view + +:::info +This feature also exists for assets uploaded other than through external libraries. +:::tip +You can use the storage template migration feature for the best experience with uploaded assets in this view. +::: + +You can browse your photos and videos by folder like in a file explorer. + +Enable this feature from the Users Settings > Features > Folders. + +The UI is currently only available for the web; mobile will come in a subsequent release. + + + ### Set Custom Scan Interval :::note diff --git a/docs/docs/features/mobile-app.mdx b/docs/docs/features/mobile-app.mdx index 2019f8cbcfb52..11185fcaf1e42 100644 --- a/docs/docs/features/mobile-app.mdx +++ b/docs/docs/features/mobile-app.mdx @@ -27,3 +27,39 @@ The beta release channel allows users to test upcoming changes before they are o :::info You can enable automatic backup on supported devices. For more information see [Automatic Backup](/docs/features/automatic-backup.md). ::: + +## Album Sync + +You can sync or mirror an album from your phone to the Immich server on your account. For example, if you select Recents, Camera and Videos album for backup, the corresponding album with the same name will be created on the server. Once the assets from those albums are uploaded, they will be put into the target albums automatically. + +### Album Synchronization Highlights + +- **One-Way Sync:** Synchronization is one-way, from the device to the server. + +- **Name Matching:** If an album on the server has the same name as the album on the device, images from the device will be merged with the existing images in the server album. + +- **Shared Albums:** If the matching album on the server is shared, the new photos merged into the album will also be shared. + +- **Album Structure:** When an album is created for the first time, its structure is based on the initial state. Future updates made on the phone (such as deleting or repositioning photos) will not be reflected in Immich. + +- **User-Specific Sync:** Album synchronization is unique to each server user and does not sync between different users or partners. + +- **Mobile-Only Feature:** Album synchronization is currently only available on mobile. For similar options on a computer, refer to [Libraries](/docs/features/libraries) for further details. + +### Synchronizing albums from the past + +Albums can be synchronized to the server even if they did not exist on the server before. In order to apply this setting you have to: +Enter the cloud on the top right -> cog wheel on the top right -> select the sync option under Sync albums. + +:::info Sync albums delete/move photos +If you delete/move photos in the local album on your device, it will not be reflected in the album on the server **even if** you click Sync albums +It will only reflect files you add. +::: + +If the same asset is in more than one album it will only sync to the first album it's in, after that it won't sync again even if the user clicks sync albums manually. +To overcome this limitation, the files must be removed from the blacklist by +App settings -> Advanced -> Duplicate Assets -> Clear + +:::info +Cleaning duplicate assets from the list will cause all the previously uploaded duplicate files to be re-uploaded, the files will not actually be uploaded and will be rejected on the server side (due to duplication) but will be synchronized to the album and at the end will be added to the black list again at the end of the synchronization. +::: diff --git a/docs/docs/features/monitoring.md b/docs/docs/features/monitoring.md index 9de3feb7f6d79..184394abd047e 100644 --- a/docs/docs/features/monitoring.md +++ b/docs/docs/features/monitoring.md @@ -25,10 +25,10 @@ The metrics in immich are grouped into API (endpoint calls and response times), ### Configuration -Immich will not expose an endpoint for metrics by default. To enable this endpoint, you can add the `IMMICH_METRICS=true` environmental variable to your `.env` file. Note that only the server and microservices containers currently use this variable. +Immich will not expose an endpoint for metrics by default. To enable this endpoint, you can add the `IMMICH_TELEMETRY_INCLUDE=all` environmental variable to your `.env` file. Note that only the server container currently use this variable. :::tip -`IMMICH_METRICS` enables all metrics, but there are also [environmental variables](/docs/install/environment-variables.md#prometheus) to toggle specific metric groups. If you'd like to only expose certain kinds of metrics, you can set only those environmental variables to `true`. Explicitly setting the environmental variable for a metric group overrides `IMMICH_METRICS` for that group. For example, setting `IMMICH_METRICS=true` and `IMMICH_API_METRICS=false` will enable all metrics except API metrics. +`IMMICH_TELEMETRY_INCLUDE=all` enables all metrics. For a more granular configuration you can enumerate the telemetry metrics that should be included as a comma separated list (e.g. `IMMICH_TELEMETRY_INCLUDE=repo,api`). Alternatively, you can also exclude specific metrics with `IMMICH_TELEMETRY_EXCLUDE`. For more information refer to the [environment section](/docs/install/environment-variables.md#prometheus). ::: The next step is to configure a new or existing Prometheus instance to scrape this endpoint. The following steps assume that you do not have an existing Prometheus instance, but the steps will be similar either way. diff --git a/docs/docs/guides/custom-locations.md b/docs/docs/guides/custom-locations.md index b364cccf837d2..514008611d11f 100644 --- a/docs/docs/guides/custom-locations.md +++ b/docs/docs/guides/custom-locations.md @@ -1,15 +1,15 @@ # Files Custom Locations -This guide explains storing generated and raw files with docker's volume mount in different locations. +This guide explains how to store generated and raw files with docker's volume mount in different locations. :::caution Backup It is important to remember to update the backup settings after following the guide to back up the new backup paths if using automatic backup tools, especially `profile/`. ::: -In our `.env` file, we will define variables that will help us in the future when we want to move to a more advanced server in the future +In our `.env` file, we will define variables that will help us in the future when we want to move to a more advanced server ```diff title=".env" -# You can find documentation for all the supported env variables [here](/docs/install/environment-variables) +# You can find documentation for all the supported environment variables [here](/docs/install/environment-variables) # Custom location where your uploaded, thumbnails, and transcoded video files are stored - UPLOAD_LOCATION=./library @@ -17,10 +17,11 @@ In our `.env` file, we will define variables that will help us in the future whe + THUMB_LOCATION=/custom/path/immich/thumbs + ENCODED_VIDEO_LOCATION=/custom/path/immich/encoded-video + PROFILE_LOCATION=/custom/path/immich/profile ++ BACKUP_LOCATION=/custom/path/immich/backups ... ``` -After defining the locations for these files, we will edit the `docker-compose.yml` file accordingly and add the new variables to the `immich-server` container. +After defining the locations of these files, we will edit the `docker-compose.yml` file accordingly and add the new variables to the `immich-server` container. ```diff title="docker-compose.yml" services: @@ -30,6 +31,7 @@ services: + - ${THUMB_LOCATION}:/usr/src/app/upload/thumbs + - ${ENCODED_VIDEO_LOCATION}:/usr/src/app/upload/encoded-video + - ${PROFILE_LOCATION}:/usr/src/app/upload/profile ++ - ${BACKUP_LOCATION}:/usr/src/app/upload/backups - /etc/localtime:/etc/localtime:ro ``` @@ -41,12 +43,11 @@ docker compose up -d :::note Because of the underlying properties of docker bind mounts, it is not recommended to mount the `upload/` and `library/` folders as separate bind mounts if they are on the same device. -For this reason, we mount the HDD or network storage to `/usr/src/app/upload` and then mount the folders we want quick access to below this folder. +For this reason, we mount the HDD or the network storage (NAS) to `/usr/src/app/upload` and then mount the folders we want to access under that folder. -The `thumbs/` folder contains both the small thumbnails shown in the timeline, and the larger previews shown when clicking into an image. These cannot be split up. +The `thumbs/` folder contains both the small thumbnails displayed in the timeline and the larger previews shown when clicking into an image. These cannot be separated. -The storage metrics of the Immich server will track the storage available at `UPLOAD_LOCATION`, -so the administrator should setup some kind of monitoring to make sure the SSD does not run out of space. The `profile/` folder is much smaller, typically less than 1 MB. +The storage metrics of the Immich server will track available storage at `UPLOAD_LOCATION`, so the administrator must set up some sort of monitoring to ensure the storage does not run out of space. The `profile/` folder is much smaller, usually less than 1 MB. ::: Thanks to [Jrasm91](https://github.com/immich-app/immich/discussions/2110#discussioncomment-5477767) for writing the guide. diff --git a/docs/docs/guides/database-queries.md b/docs/docs/guides/database-queries.md index 2b4f27cfceaa5..0e58d84f90c01 100644 --- a/docs/docs/guides/database-queries.md +++ b/docs/docs/guides/database-queries.md @@ -98,6 +98,10 @@ SELECT * FROM "move_history"; SELECT * FROM "users"; ``` +```sql title="Get owner info from asset ID" +SELECT "users".* FROM "users" JOIN "assets" ON "users"."id" = "assets"."ownerId" WHERE "assets"."id" = 'fa310b01-2f26-4b7a-9042-d578226e021f'; +``` + ## System Config ```sql title="Custom settings" diff --git a/docs/docs/guides/template-backup-script.md b/docs/docs/guides/template-backup-script.md index a0cd890a49745..dd0b94ebb13a0 100644 --- a/docs/docs/guides/template-backup-script.md +++ b/docs/docs/guides/template-backup-script.md @@ -6,6 +6,15 @@ This script assumes you have a second hard drive connected to your server for on The database is saved to your Immich upload folder in the `database-backup` subdirectory. The database is then backed up and versioned with your assets by Borg. This ensures that the database backup is in sync with your assets in every snapshot. +:::info +This script makes backups of your database along with your photo/video library. This is redundant with the [automatic database backup tool](https://immich.app/docs/administration/backup-and-restore#automatic-database-backups) built into Immich. Using this script to backup your database has two advantages over the built-in backup tool: + +- This script uses storage more efficiently by versioning your backups instead of making multiple copies. +- The database backups are performed at the same time as the library backup, ensuring that the backups of your database and the library are always in sync. + +If you are using this script, it is therefore safe to turn off the built-in automatic database backups from your admin panel to save storage space. +::: + ### Prerequisites - Borg needs to be installed on your server as well as the remote machine. You can find instructions to install Borg [here](https://borgbackup.readthedocs.io/en/latest/installation.html). diff --git a/docs/docs/install/config-file.md b/docs/docs/install/config-file.md index ed902f39cfd1e..9d86b8dad77d2 100644 --- a/docs/docs/install/config-file.md +++ b/docs/docs/install/config-file.md @@ -26,7 +26,6 @@ The default configuration looks like this: "bframes": -1, "refs": 0, "gopSize": 0, - "npl": 0, "temporalAQ": false, "cqMode": "auto", "twoPass": false, @@ -36,6 +35,13 @@ The default configuration looks like this: "accel": "disabled", "accelDecode": false }, + "backup": { + "database": { + "enabled": true, + "cronExpression": "0 02 * * *", + "keepLastAmount": 14 + } + }, "job": { "backgroundTask": { "concurrency": 5 diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md index e86199dc74d99..1f34b5c6d00a4 100644 --- a/docs/docs/install/environment-variables.md +++ b/docs/docs/install/environment-variables.md @@ -183,15 +183,10 @@ Other machine learning parameters can be tuned from the admin UI. ## Prometheus -| Variable | Description | Default | Containers | Workers | -| :----------------------------- | :-------------------------------------------------------------------------------------------- | :-----: | :--------- | :----------------- | -| `IMMICH_METRICS`\*1 | Toggle all metrics (one of [`true`, `false`]) | | server | api, microservices | -| `IMMICH_API_METRICS` | Toggle metrics for endpoints and response times (one of [`true`, `false`]) | | server | api, microservices | -| `IMMICH_HOST_METRICS` | Toggle metrics for CPU and memory utilization for host and process (one of [`true`, `false`]) | | server | api, microservices | -| `IMMICH_IO_METRICS` | Toggle metrics for database queries, image processing, etc. (one of [`true`, `false`]) | | server | api, microservices | -| `IMMICH_JOB_METRICS` | Toggle metrics for jobs and queues (one of [`true`, `false`]) | | server | api, microservices | - -\*1: Overridden for a metric group when its corresponding environmental variable is set. +| Variable | Description | Default | Containers | Workers | +| :------------------------- | :-------------------------------------------------------------------------------------------------------------------- | :-----: | :--------- | :----------------- | +| `IMMICH_TELEMETRY_INCLUDE` | Collect these telemetries. List of `host`, `api`, `io`, `repo`, `job`. Note: You can also specify `all` to enable all | | server | api, microservices | +| `IMMICH_TELEMETRY_EXCLUDE` | Do not collect these telemetries. List of `host`, `api`, `io`, `repo`, `job` | | server | api, microservices | ## Docker Secrets diff --git a/docs/docs/install/img/truenas01.png b/docs/docs/install/img/truenas01.png index 81b0430a75ac2..e648ab3734e9d 100644 Binary files a/docs/docs/install/img/truenas01.png and b/docs/docs/install/img/truenas01.png differ diff --git a/docs/docs/install/img/truenas02.png b/docs/docs/install/img/truenas02.png index ae7d41e62425f..66f0dec7fa49c 100644 Binary files a/docs/docs/install/img/truenas02.png and b/docs/docs/install/img/truenas02.png differ diff --git a/docs/docs/install/img/truenas03.png b/docs/docs/install/img/truenas03.png index 90ff25b7ac916..d9970f5aebe90 100644 Binary files a/docs/docs/install/img/truenas03.png and b/docs/docs/install/img/truenas03.png differ diff --git a/docs/docs/install/img/truenas04.png b/docs/docs/install/img/truenas04.png index 281d02350a8cd..45fa87e5e5e72 100644 Binary files a/docs/docs/install/img/truenas04.png and b/docs/docs/install/img/truenas04.png differ diff --git a/docs/docs/install/img/truenas05.png b/docs/docs/install/img/truenas05.png index 919b008030736..0f9d6a835aeb1 100644 Binary files a/docs/docs/install/img/truenas05.png and b/docs/docs/install/img/truenas05.png differ diff --git a/docs/docs/install/img/truenas06.png b/docs/docs/install/img/truenas06.png index 26cf06738a634..3daf250e36706 100644 Binary files a/docs/docs/install/img/truenas06.png and b/docs/docs/install/img/truenas06.png differ diff --git a/docs/docs/install/img/truenas07.png b/docs/docs/install/img/truenas07.png index 17943e5c8155e..946c1401ac7dd 100644 Binary files a/docs/docs/install/img/truenas07.png and b/docs/docs/install/img/truenas07.png differ diff --git a/docs/docs/install/img/truenas08.png b/docs/docs/install/img/truenas08.png index 4c5a90be6befb..4ace8b49ca0b9 100644 Binary files a/docs/docs/install/img/truenas08.png and b/docs/docs/install/img/truenas08.png differ diff --git a/docs/docs/install/img/truenas09.png b/docs/docs/install/img/truenas09.png index 647c7295b420c..41830fe9e6046 100644 Binary files a/docs/docs/install/img/truenas09.png and b/docs/docs/install/img/truenas09.png differ diff --git a/docs/docs/install/img/truenas10.png b/docs/docs/install/img/truenas10.png new file mode 100644 index 0000000000000..730685c309f99 Binary files /dev/null and b/docs/docs/install/img/truenas10.png differ diff --git a/docs/docs/install/img/truenas11.png b/docs/docs/install/img/truenas11.png new file mode 100644 index 0000000000000..88c166aed3810 Binary files /dev/null and b/docs/docs/install/img/truenas11.png differ diff --git a/docs/docs/install/img/truenas12.png b/docs/docs/install/img/truenas12.png new file mode 100644 index 0000000000000..a107a85f24c76 Binary files /dev/null and b/docs/docs/install/img/truenas12.png differ diff --git a/docs/docs/install/requirements.md b/docs/docs/install/requirements.md index b96705203aa8f..f3f4d2c23dcb4 100644 --- a/docs/docs/install/requirements.md +++ b/docs/docs/install/requirements.md @@ -8,7 +8,7 @@ Hardware and software requirements for Immich: ## Software -- [Docker](https://docs.docker.com/get-docker/) +- [Docker](https://docs.docker.com/engine/install/) - [Docker Compose](https://docs.docker.com/compose/install/) :::note diff --git a/docs/docs/install/truenas.md b/docs/docs/install/truenas.md index ffb559ed1216b..f35e9aa37a874 100644 --- a/docs/docs/install/truenas.md +++ b/docs/docs/install/truenas.md @@ -7,7 +7,9 @@ sidebar_position: 80 :::note This is a community contribution and not officially supported by the Immich team, but included here for convenience. -**Please report issues to the corresponding [Github Repository](https://github.com/truenas/charts/tree/master/community/immich).** +Community support can be found in the dedicated channel on the [Discord Server](https://discord.immich.app/). + +**Please report app issues to the corresponding [Github Repository](https://github.com/truenas/charts/tree/master/community/immich).** ::: Immich can easily be installed on TrueNAS SCALE via the **Community** train application. @@ -20,18 +22,26 @@ TrueNAS SCALE makes installing and updating Immich easy, but you must use the Im The Immich app in TrueNAS SCALE installs, completes the initial configuration, then starts the Immich web portal. When updates become available, SCALE alerts and provides easy updates. -Before installing the Immich app in SCALE, review the [Environment Variables](/docs/install/environment-variables.md) documentation to see if you want to configure any during installation. -You can configure environment variables at any time after deploying the application. +Before installing the Immich app in SCALE, review the [Environment Variables](#environment-variables) documentation to see if you want to configure any during installation. +You may also configure environment variables at any time after deploying the application. + +### Setting up Storage Datasets -You can allow SCALE to create the datasets Immich requires automatically during app installation. -Or before beginning app installation, [create the datasets](https://www.truenas.com/docs/scale/scaletutorials/storage/datasets/datasetsscale/) to use in the **Storage Configuration** section during installation. -Immich requires seven datasets: **library**, **pgBackup**, **pgData**, **profile**, **thumbs**, **uploads**, and **video**. -You can organize these as one parent with seven child datasets, for example `mnt/tank/immich/library`, `mnt/tank/immich/pgBackup`, and so on. +Before beginning app installation, [create the datasets](https://www.truenas.com/docs/scale/scaletutorials/storage/datasets/datasetsscale/) to use in the **Storage Configuration** section during installation. +Immich requires seven datasets: `library`, `upload`, `thumbs`, `profile`, `video`, `backups`, and `pgData`. +You can organize these as one parent with seven child datasets, for example `/mnt/tank/immich/library`, `/mnt/tank/immich/upload`, and so on. + + :::info Permissions The **pgData** dataset must be owned by the user `netdata` (UID 999) for postgres to start. The other datasets must be owned by the user `root` (UID 0) or a group that includes the user `root` (UID 0) for immich to have the necessary permissions. -The **library** dataset must have [ACL mode](https://www.truenas.com/docs/core/coretutorials/storage/pools/permissions/#access-control-lists) set to `Passthrough` if you plan on using a [storage template](/docs/administration/storage-template.mdx) and the dataset is configured for network sharing (its ACL type is set to `SMB/NFSv4`). When the template is applied and files need to be moved from **uploads** to **library**, immich performs `chmod` internally and needs to be allowed to execute the command. +If the **library** dataset uses ACL it must have [ACL mode](https://www.truenas.com/docs/core/coretutorials/storage/pools/permissions/#access-control-lists) set to `Passthrough` if you plan on using a [storage template](/docs/administration/storage-template.mdx) and the dataset is configured for network sharing (its ACL type is set to `SMB/NFSv4`). When the template is applied and files need to be moved from **upload** to **library**, immich performs `chmod` internally and needs to be allowed to execute the command. [More info.](https://github.com/immich-app/immich/pull/13017) ::: ## Installing the Immich Application @@ -47,6 +57,8 @@ className="border rounded-xl" Click on the widget to open the **Immich** application details screen. +

+
+ Application configuration settings are presented in several sections, each explained below. To find specific fields click in the **Search Input Fields** search field, scroll down to a particular section or click on the section heading on the navigation area in the upper-right corner. +### Application Name and Version + Install Immich Screen -Accept the default values in **Application Name** and **Version**. - -Accept the default value in **Timezone** or change to match your local timezone. -**Timezone** is only used by the Immich `exiftool` microservice if it cannot be determined from the image metadata. - -Accept the default port in **Web Port**. - -Immich requires seven storage datasets. -You can allow SCALE to create them for you, or use the dataset(s) created in [First Steps](#first-steps). -Select the storage options you want to use for **Immich Uploads Storage**, **Immich Library Storage**, **Immich Thumbs Storage**, **Immich Profile Storage**, **Immich Video Storage**, **Immich Postgres Data Storage**, **Immich Postgres Backup Storage**. -Select **ixVolume (dataset created automatically by the system)** in **Type** to let SCALE create the dataset or select **Host Path** to use the existing datasets created on the system. +Accept the default value or enter a name in **Application Name** field. +In most cases use the default name, but if adding a second deployment of the application you must change this name. -Accept the defaults in Resources or change the CPU and memory limits to suit your use case. +Accept the default version number in **Version**. +When a new version becomes available, the application has an update badge. +The **Installed Applications** screen shows the option to update applications. -Click **Install**. -The system opens the **Installed Applications** screen with the Immich app in the **Deploying** state. -When the installation completes it changes to **Running**. +### Immich Configuration -Click **Web Portal** on the **Application Info** widget to open the Immich web interface to set up your account and begin uploading photos. +Accept the default value in **Timezone** or change to match your local timezone. +**Timezone** is only used by the Immich `exiftool` microservice if it cannot be determined from the image metadata. -:::tip -For more information on how to use the application once installed, please refer to the [Post Install](/docs/install/post-install.mdx) guide. -::: +Untick **Enable Machine Learning** if you will not use face recognition, image search, and smart duplicate detection. -## Editing Environment Variables +Accept the default option or select the **Machine Learning Image Type** for your hardware based on the [Hardware-Accelerated Machine Learning Supported Backends](/docs/features/ml-hardware-acceleration.md#supported-backends). -Go to the **Installed Applications** screen and select Immich from the list of installed applications. -Click **Edit** on the **Application Info** widget to open the **Edit Immich** screen. -The settings on the edit screen are the same as on the install screen. -You cannot edit **Storage Configuration** paths after the initial app install. +Immich's default is `postgres` but you should consider setting the **Database Password** to a custom value using only the characters `A-Za-z0-9`. -Click **Update** to save changes. -TrueNAS automatically updates, recreates, and redeploys the Immich container with the updated environment variables. +The **Redis Password** should be set to a custom value using only the characters `A-Za-z0-9`. -## Updating the App +Accept the **Log Level** default of **Log**. -When updates become available, SCALE alerts and provides easy updates. -To update the app to the latest version, click **Update** on the **Application Info** widget from the **Installed Applications** screen. +Leave **Hugging Face Endpoint** blank. (This is for downloading ML models from a different source.) -Update opens an update window for the application that includes two selectable options, Images (to be updated) and Changelog. Click on the down arrow to see the options available for each. +Leave **Additional Environment Variables** blank or see [Environment Variables](#environment-variables) to set before installing. -Click **Upgrade** to begin the process and open a counter dialog that shows the upgrade progress. When complete, the update badge and buttons disappear and the application Update state on the Installed screen changes from Update Available to Up to date. +### Network Configuration -## Understanding Immich Settings in TrueNAS SCALE + -Accept the default value or enter a name in **Application Name** field. -In most cases use the default name, but if adding a second deployment of the application you must change this name. +Accept the default port `30041` in **WebUI Port** or enter a custom port number. +:::info Allowed Port Numbers +Only numbers within the range 9000-65535 may be used on SCALE versions below TrueNAS Scale 24.10 Electric Eel. -Accept the default version number in **Version**. -When a new version becomes available, the application has an update badge. -The **Installed Applications** screen shows the option to update applications. +Regardless of version, to avoid port conflicts, don't use [ports on this list](https://www.truenas.com/docs/references/defaultports/). +::: -### Immich Configuration Settings +### Storage Configuration -You can accept the defaults in the **Immich Configuration** settings, or enter the settings you want to use. +Immich requires seven storage datasets. -Accept the default setting in **Timezone** or change to match your local timezone. -**Timezone** is only used by the Immich `exiftool` microservice if it cannot be determined from the image metadata. +:::note Default Setting (Not recommended) +The default setting for datasets is **ixVolume (dataset created automatically by the system)** but this results in your data being harder to access manually and can result in data loss if you delete the immich app. (Not recommended) +::: -You can enter a **Public Login Message** to display on the login page, or leave it blank. +For each Storage option select **Host Path (Path that already exists on the system)** and then select the matching dataset [created before installing the app](#setting-up-storage-datasets): **Immich Library Storage**: `library`, **Immich Uploads Storage**: `upload`, **Immich Thumbs Storage**: `thumbs`, **Immich Profile Storage**: `profile`, **Immich Video Storage**: `video`, **Immich Backups Storage**: `backups`, **Postgres Data Storage**: `pgData`. -### Networking Settings + +The image above has example values. -Accept the default port numbers in **Web Port**. -The SCALE Immich app listens on port **30041**. +
-Refer to the TrueNAS [default port list](https://www.truenas.com/docs/references/defaultports/) for a list of assigned port numbers. -To change the port numbers, enter a number within the range 9000-65535. +### Additional Storage [(External Libraries)](/docs/features/libraries) -### Storage Settings +You may configure [External Libraries](/docs/features/libraries) by mounting them using **Additional Storage**. +The **Mount Path** is the loaction you will need to copy and paste into the External Library settings within Immich. +The **Host Path** is the location on the TrueNAS SCALE server where your external library is located. + + -You can install Immich using the default setting **ixVolume (dataset created automatically by the system)** or use the host path option with datasets [created before installing the app](#first-steps). +### Resources Configuration -Select **Host Path (Path that already exists on the system)** to browse to and select the datasets. +Accept the default **CPU** limit of `2` threads or specify the number of threads (CPUs with Multi-/Hyper-threading have 2 threads per core). + +Accept the default **Memory** limit of `4096` MB or specify the number of MB of RAM. If you're using Machine Learning you should probably set this above 8000 MB. + +:::info Older SCALE Versions +Before TrueNAS SCALE version 24.10 Electric Eel: + +The **CPU** value was specified in a different format with a default of `4000m` which is 4 threads. + +The **Memory** value was specified in a different format with a default of `8Gi` which is 8 GiB of RAM. The value was specified in bytes or a number with a measurement suffix. Examples: `129M`, `123Mi`, `1000000000` +::: + +Enable **GPU Configuration** options if you have a GPU that you will use for [Hardware Transcoding](/docs/features/hardware-transcoding) and/or [Hardware-Accelerated Machine Learning](/docs/features/ml-hardware-acceleration.md). More info: [GPU Passtrough Docs for TrueNAS Apps](https://www.truenas.com/docs/truenasapps/#gpu-passthrough) + +### Install + +Finally, click **Install**. +The system opens the **Installed Applications** screen with the Immich app in the **Deploying** state. +When the installation completes it changes to **Running**. -### Resource Configuration Settings +Click **Web Portal** on the **Application Info** widget to open the Immich web interface to set up your account and begin uploading photos. + +:::tip +For more information on how to use the application once installed, please refer to the [Post Install](/docs/install/post-install.mdx) guide. +::: + +## Edit App Settings -Accept the default values in **Resources Configuration** or enter new CPU and memory values -By default, this application is limited to use no more than 4 CPU cores and 8 Gigabytes available memory. The application might use considerably less system resources. +- Go to the **Installed Applications** screen and select Immich from the list of installed applications. +- Click **Edit** on the **Application Info** widget to open the **Edit Immich** screen. +- Change any settings you would like to change. + - The settings on the edit screen are the same as on the install screen. +- Click **Update** at the very bottom of the page to save changes. + - TrueNAS automatically updates, recreates, and redeploys the Immich container with the updated settings. + +## Environment Variables + +You can set [Environment Variables](/docs/install/environment-variables) by clicking **Add** on the **Additional Environment Variables** option and filling in the **Name** and **Value**. -To customize the CPU and memory allocated to the container Immich uses, enter new CPU values as a plain integer value followed by the suffix m (milli). -Default is 4000m. +:::info +Some Environment Variables are not available for the TrueNAS SCALE app. This is mainly because they can be configured through GUI options in the [Edit Immich screen](#edit-app-settings). + +Some examples are: `IMMICH_VERSION`, `UPLOAD_LOCATION`, `DB_DATA_LOCATION`, `TZ`, `IMMICH_LOG_LEVEL`, `DB_PASSWORD`, `REDIS_PASSWORD`. +::: -Accept the default value 8Gi allocated memory or enter a new limit in bytes. -Enter a plain integer followed by the measurement suffix, for example 129M or 123Mi. +## Updating the App -Systems with compatible GPU(s) display devices in **GPU Configuration**. -See [Managing GPUs](https://www.truenas.com/docs/scale/scaletutorials/systemsettings/advanced/managegpuscale/) for more information about allocating isolated GPU devices in TrueNAS SCALE. +When updates become available, SCALE alerts and provides easy updates. +To update the app to the latest version: + +- Go to the **Installed Applications** screen and select Immich from the list of installed applications. +- Click **Update** on the **Application Info** widget from the **Installed Applications** screen. +- This opens an update window with some options + - You may select an Image update too. + - You may view the Changelog. +- Click **Upgrade** to begin the process and open a counter dialog that shows the upgrade progress. + - When complete, the update badge and buttons disappear and the application Update state on the Installed screen changes from Update Available to Up to date. diff --git a/docs/docs/install/unraid.md b/docs/docs/install/unraid.md index b17ed28295f70..356f81c9e8db4 100644 --- a/docs/docs/install/unraid.md +++ b/docs/docs/install/unraid.md @@ -77,6 +77,7 @@ alt="Select Plugins > Compose.Manager > Add New Stack > Label it Immich" 7. Paste the entire contents of the [Immich example.env](https://github.com/immich-app/immich/releases/latest/download/example.env) file into the Unraid editor, then **before saving** edit the following: - `UPLOAD_LOCATION`: Create a folder in your Images Unraid share and place the **absolute** location here > For example my _"images"_ share has a folder within it called _"immich"_. If I browse to this directory in the terminal and type `pwd` the output is `/mnt/user/images/immich`. This is the exact value I need to enter as my `UPLOAD_LOCATION` + - `DB_DATA_LOCATION`: Change this to use an Unraid share (preferably a cache pool, e.g. `/mnt/user/appdata`). 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. =20" }, "volta": { - "node": "20.18.0" + "node": "22.11.0" } } diff --git a/docs/src/components/community-guides.tsx b/docs/src/components/community-guides.tsx index 7f4206c97baf1..17fe56231754e 100644 --- a/docs/src/components/community-guides.tsx +++ b/docs/src/components/community-guides.tsx @@ -35,19 +35,24 @@ const guides: CommunityGuidesProps[] = [ }, { title: 'Google Photos import + albums', - description: 'Import your Google Photos files into Immich and add your albums', + description: 'Import your Google Photos files into Immich and add your albums.', url: 'https://github.com/immich-app/immich/discussions/1340', }, { title: 'Access Immich with custom domain', - description: 'Access your local Immich installation over the internet using your own domain', + description: 'Access your local Immich installation over the internet using your own domain.', url: 'https://github.com/ppr88/immich-guides/blob/main/open-immich-custom-domain.md', }, { title: 'Nginx caching map server', - description: 'Increase privacy by using nginx as a caching proxy in front of a map tile server', + description: 'Increase privacy by using nginx as a caching proxy in front of a map tile server.', url: 'https://github.com/pcouy/pcouy.github.io/blob/main/_posts/2024-08-30-proxying-a-map-tile-server-for-increased-privacy.md', }, + { + title: 'fail2ban setup instructions', + description: 'How to configure an existing fail2ban installation to block incorrect login attempts.', + url: 'https://github.com/immich-app/immich/discussions/3243#discussioncomment-6681948', + }, ]; function CommunityGuide({ title, description, url }: CommunityGuidesProps): JSX.Element { diff --git a/docs/src/components/community-projects.tsx b/docs/src/components/community-projects.tsx index 3a034e3a04cfd..596bf9dfc40a9 100644 --- a/docs/src/components/community-projects.tsx +++ b/docs/src/components/community-projects.tsx @@ -83,6 +83,12 @@ const projects: CommunityProjectProps[] = [ description: 'Power tools for organizing your immich library.', url: 'https://github.com/varun-raj/immich-power-tools', }, + { + title: 'Immich Public Proxy', + description: + 'Share your Immich photos and albums in a safe way without exposing your Immich instance to the public.', + url: 'https://github.com/alangrainger/immich-public-proxy', + }, ]; function CommunityProject({ title, description, url }: CommunityProjectProps): JSX.Element { diff --git a/docs/src/components/timeline.tsx b/docs/src/components/timeline.tsx index 374d2d88fab19..32b15edb5960b 100644 --- a/docs/src/components/timeline.tsx +++ b/docs/src/components/timeline.tsx @@ -49,7 +49,7 @@ export function Timeline({ items }: Props): JSX.Element {
{cardIcon === 'immich' ? ( - + ) : ( )} diff --git a/docs/src/pages/roadmap.tsx b/docs/src/pages/roadmap.tsx index 1f07e45122749..7de51f7513935 100644 --- a/docs/src/pages/roadmap.tsx +++ b/docs/src/pages/roadmap.tsx @@ -74,12 +74,14 @@ import { mdiFaceRecognition, mdiVideo, mdiWeb, + mdiDatabaseOutline, } from '@mdi/js'; import Layout from '@theme/Layout'; import React from 'react'; import { Item, Timeline } from '../components/timeline'; const releases = { + 'v1.120.0': new Date(2024, 10, 6), 'v1.114.0': new Date(2024, 8, 6), 'v1.113.0': new Date(2024, 7, 30), 'v1.112.0': new Date(2024, 7, 14), @@ -151,6 +153,9 @@ const weirdTags = { 'v1.2.0': 'v0.2-dev ', }; +const title = 'Roadmap'; +const description = 'A list of future plans and goals, as well as past achievements and milestones.'; + const withLanguage = (date: Date) => (language: string) => date.toLocaleDateString(language); type Base = { icon: string; iconColor?: React.CSSProperties['color']; title: string; description: string }; @@ -177,27 +182,19 @@ const withRelease = ({ const roadmap: Item[] = [ { done: false, - icon: mdiLockOutline, - iconColor: 'sandybrown', - title: 'Private/locked photos', - description: 'Private assets with extra protections', - getDateLabel: () => 'Planned for 2024', - }, - { - done: false, - icon: mdiRocketLaunch, - iconColor: 'indianred', - title: 'Stable release', - description: 'Immich goes stable', - getDateLabel: () => 'Planned for 2024', + icon: mdiFlash, + iconColor: 'gold', + title: 'Workflows', + description: 'Automate tasks with workflows', + getDateLabel: () => 'Planned for 2025', }, { done: false, - icon: mdiCloudUploadOutline, - iconColor: 'cornflowerblue', - title: 'Better background backups', - description: 'Rework background backups to be more reliable', - getDateLabel: () => 'Planned for 2024', + icon: mdiTableKey, + iconColor: 'gray', + title: 'Fine grained access controls', + description: 'Granular access controls for users and api keys', + getDateLabel: () => 'Planned for 2025', }, { done: false, @@ -205,22 +202,30 @@ const roadmap: Item[] = [ iconColor: 'rebeccapurple', title: 'Basic editor', description: 'Basic photo editing capabilities', - getDateLabel: () => 'Planned for 2024', + getDateLabel: () => 'Planned for 2025', }, { done: false, - icon: mdiFlash, - iconColor: 'gold', - title: 'Workflows', - description: 'Automate tasks with workflows', + icon: mdiRocketLaunch, + iconColor: 'indianred', + title: 'Stable release', + description: 'Immich goes stable', + getDateLabel: () => 'Planned for early 2025', + }, + { + done: false, + icon: mdiLockOutline, + iconColor: 'sandybrown', + title: 'Private/locked photos', + description: 'Private assets with extra protections', getDateLabel: () => 'Planned for 2024', }, { done: false, - icon: mdiTableKey, - iconColor: 'gray', - title: 'Fine grained access controls', - description: 'Granular access controls for users and api keys', + icon: mdiCloudUploadOutline, + iconColor: 'cornflowerblue', + title: 'Better background backups', + description: 'Rework background backups to be more reliable', getDateLabel: () => 'Planned for 2024', }, { @@ -234,6 +239,20 @@ const roadmap: Item[] = [ ]; const milestones: Item[] = [ + withRelease({ + icon: mdiDatabaseOutline, + iconColor: 'brown', + title: 'Automatic database backups', + description: 'Database backups are now integrated into the Immich server', + release: 'v1.120.0', + }), + { + icon: mdiStar, + iconColor: 'gold', + title: '50,000 Stars', + description: 'Reached 50K Stars on GitHub!', + getDateLabel: withLanguage(new Date(2024, 10, 1)), + }, withRelease({ icon: mdiFaceRecognition, title: 'Metadata Face Import', @@ -853,14 +872,12 @@ const milestones: Item[] = [ export default function MilestonePage(): JSX.Element { return ( - +

- Roadmap + {title}

-

- A list of future plans and goals, as well as past achievements and milestones. -

+

{description}

diff --git a/docs/static/archived-versions.json b/docs/static/archived-versions.json index 912b188cb4252..bcd908c151b59 100644 --- a/docs/static/archived-versions.json +++ b/docs/static/archived-versions.json @@ -1,4 +1,24 @@ [ + { + "label": "v1.120.2", + "url": "https://v1.120.2.archive.immich.app" + }, + { + "label": "v1.120.1", + "url": "https://v1.120.1.archive.immich.app" + }, + { + "label": "v1.120.0", + "url": "https://v1.120.0.archive.immich.app" + }, + { + "label": "v1.119.1", + "url": "https://v1.119.1.archive.immich.app" + }, + { + "label": "v1.119.0", + "url": "https://v1.119.0.archive.immich.app" + }, { "label": "v1.118.2", "url": "https://v1.118.2.archive.immich.app" diff --git a/e2e/.nvmrc b/e2e/.nvmrc index 2a393af592b8c..7af24b7ddbde0 100644 --- a/e2e/.nvmrc +++ b/e2e/.nvmrc @@ -1 +1 @@ -20.18.0 +22.11.0 diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml index 40e800f054b98..d9117b1b4aef3 100644 --- a/e2e/docker-compose.yml +++ b/e2e/docker-compose.yml @@ -19,7 +19,7 @@ services: - DB_PASSWORD=postgres - DB_DATABASE_NAME=immich - IMMICH_MACHINE_LEARNING_ENABLED=false - - IMMICH_METRICS=true + - IMMICH_TELEMETRY_INCLUDE=all - IMMICH_ENV=testing - IMMICH_PORT=2285 - IMMICH_IGNORE_MOUNT_CHECK_ERRORS=true @@ -34,7 +34,7 @@ services: - 2285:2285 redis: - image: redis:6.2-alpine@sha256:2ba50e1ac3a0ea17b736ce9db2b0a9f6f8b85d4c27d5f5accc6a416d8f42c6d5 + image: redis:6.2-alpine@sha256:eaba718fecd1196d88533de7ba49bf903ad33664a92debb24660a922ecd9cac8 database: image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0 diff --git a/e2e/package-lock.json b/e2e/package-lock.json index cc2128a9892b8..ba27b69ad82ad 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -1,12 +1,12 @@ { "name": "immich-e2e", - "version": "1.118.2", + "version": "1.120.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "immich-e2e", - "version": "1.118.2", + "version": "1.120.2", "license": "GNU Affero General Public License version 3", "devDependencies": { "@eslint/eslintrc": "^3.1.0", @@ -15,7 +15,7 @@ "@immich/sdk": "file:../open-api/typescript-sdk", "@playwright/test": "^1.44.1", "@types/luxon": "^3.4.2", - "@types/node": "^20.16.12", + "@types/node": "^22.9.0", "@types/oidc-provider": "^8.5.1", "@types/pg": "^8.11.0", "@types/pngjs": "^6.0.4", @@ -45,7 +45,7 @@ }, "../cli": { "name": "@immich/cli", - "version": "2.2.26", + "version": "2.2.31", "dev": true, "license": "GNU Affero General Public License version 3", "dependencies": { @@ -64,7 +64,7 @@ "@types/cli-progress": "^3.11.0", "@types/lodash-es": "^4.17.12", "@types/mock-fs": "^4.13.1", - "@types/node": "^20.16.12", + "@types/node": "^22.9.0", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", "@vitest/coverage-v8": "^2.0.5", @@ -83,7 +83,7 @@ "vite": "^5.0.12", "vite-tsconfig-paths": "^5.0.0", "vitest": "^2.0.5", - "vitest-fetch-mock": "^0.3.0", + "vitest-fetch-mock": "^0.4.0", "yaml": "^2.3.1" }, "engines": { @@ -92,14 +92,14 @@ }, "../open-api/typescript-sdk": { "name": "@immich/sdk", - "version": "1.118.2", + "version": "1.120.2", "dev": true, "license": "GNU Affero General Public License version 3", "dependencies": { "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^20.16.12", + "@types/node": "^22.9.0", "typescript": "^5.3.3" } }, @@ -210,19 +210,21 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -319,12 +321,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", - "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.25.2" + "@babel/types": "^7.26.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -334,14 +337,14 @@ } }, "node_modules/@babel/types": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", - "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -785,9 +788,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", - "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", + "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -832,9 +835,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.12.0.tgz", - "integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", + "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", "dev": true, "license": "MIT", "engines": { @@ -1060,19 +1063,18 @@ } }, "node_modules/@koa/router": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@koa/router/-/router-12.0.1.tgz", - "integrity": "sha512-ribfPYfHb+Uw3b27Eiw6NPqjhIhTpVFzEWLwyc/1Xp+DCdwRRyIlAUODX+9bPARF6aQtUu1+/PHzdNvRzcs/+Q==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-13.1.0.tgz", + "integrity": "sha512-mNVu1nvkpSd8Q8gMebGbCkDWJ51ODetrFvLKYusej+V0ByD4btqHYnPIzTBLXnQMVUlm/oxVwqmWBY3zQfZilw==", "dev": true, + "license": "MIT", "dependencies": { - "debug": "^4.3.4", "http-errors": "^2.0.0", "koa-compose": "^4.1.0", - "methods": "^1.1.2", - "path-to-regexp": "^6.2.1" + "path-to-regexp": "^6.3.0" }, "engines": { - "node": ">= 12" + "node": ">= 18" } }, "node_modules/@mapbox/node-pre-gyp": { @@ -1187,13 +1189,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.48.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.1.tgz", - "integrity": "sha512-s9RtWoxkOLmRJdw3oFvhFbs9OJS0BzrLUc8Hf6l2UdCNd1rqeEyD4BhCJkvzeEoD1FsK4mirsWwGerhVmYKtZg==", + "version": "1.48.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.2.tgz", + "integrity": "sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.48.1" + "playwright": "1.48.2" }, "bin": { "playwright": "cli.js" @@ -1203,9 +1205,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", - "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.4.tgz", + "integrity": "sha512-jfUJrFct/hTA0XDM5p/htWKoNNTbDLY0KRwEt6pyOA6k2fmk0WVwl65PdUdJZgzGEHWx+49LilkcSaumQRyNQw==", "cpu": [ "arm" ], @@ -1217,9 +1219,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", - "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.4.tgz", + "integrity": "sha512-j4nrEO6nHU1nZUuCfRKoCcvh7PIywQPUCBa2UsootTHvTHIoIu2BzueInGJhhvQO/2FTRdNYpf63xsgEqH9IhA==", "cpu": [ "arm64" ], @@ -1231,9 +1233,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", - "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.4.tgz", + "integrity": "sha512-GmU/QgGtBTeraKyldC7cDVVvAJEOr3dFLKneez/n7BvX57UdhOqDsVwzU7UOnYA7AAOt+Xb26lk79PldDHgMIQ==", "cpu": [ "arm64" ], @@ -1245,9 +1247,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", - "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.4.tgz", + "integrity": "sha512-N6oDBiZCBKlwYcsEPXGDE4g9RoxZLK6vT98M8111cW7VsVJFpNEqvJeIPfsCzbf0XEakPslh72X0gnlMi4Ddgg==", "cpu": [ "x64" ], @@ -1258,10 +1260,38 @@ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.4.tgz", + "integrity": "sha512-py5oNShCCjCyjWXCZNrRGRpjWsF0ic8f4ieBNra5buQz0O/U6mMXCpC1LvrHuhJsNPgRt36tSYMidGzZiJF6mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.4.tgz", + "integrity": "sha512-L7VVVW9FCnTTp4i7KrmHeDsDvjB4++KOBENYtNYAiYl96jeBThFfhP6HVxL74v4SiZEVDH/1ILscR5U9S4ms4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", - "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.4.tgz", + "integrity": "sha512-10ICosOwYChROdQoQo589N5idQIisxjaFE/PAnX2i0Zr84mY0k9zul1ArH0rnJ/fpgiqfu13TFZR5A5YJLOYZA==", "cpu": [ "arm" ], @@ -1273,9 +1303,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", - "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.4.tgz", + "integrity": "sha512-ySAfWs69LYC7QhRDZNKqNhz2UKN8LDfbKSMAEtoEI0jitwfAG2iZwVqGACJT+kfYvvz3/JgsLlcBP+WWoKCLcw==", "cpu": [ "arm" ], @@ -1287,9 +1317,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", - "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.4.tgz", + "integrity": "sha512-uHYJ0HNOI6pGEeZ/5mgm5arNVTI0nLlmrbdph+pGXpC9tFHFDQmDMOEqkmUObRfosJqpU8RliYoGz06qSdtcjg==", "cpu": [ "arm64" ], @@ -1301,9 +1331,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", - "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.4.tgz", + "integrity": "sha512-38yiWLemQf7aLHDgTg85fh3hW9stJ0Muk7+s6tIkSUOMmi4Xbv5pH/5Bofnsb6spIwD5FJiR+jg71f0CH5OzoA==", "cpu": [ "arm64" ], @@ -1315,9 +1345,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", - "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.4.tgz", + "integrity": "sha512-q73XUPnkwt9ZNF2xRS4fvneSuaHw2BXuV5rI4cw0fWYVIWIBeDZX7c7FWhFQPNTnE24172K30I+dViWRVD9TwA==", "cpu": [ "ppc64" ], @@ -1329,9 +1359,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", - "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.4.tgz", + "integrity": "sha512-Aie/TbmQi6UXokJqDZdmTJuZBCU3QBDA8oTKRGtd4ABi/nHgXICulfg1KI6n9/koDsiDbvHAiQO3YAUNa/7BCw==", "cpu": [ "riscv64" ], @@ -1343,9 +1373,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", - "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.4.tgz", + "integrity": "sha512-P8MPErVO/y8ohWSP9JY7lLQ8+YMHfTI4bAdtCi3pC2hTeqFJco2jYspzOzTUB8hwUWIIu1xwOrJE11nP+0JFAQ==", "cpu": [ "s390x" ], @@ -1357,9 +1387,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", - "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.4.tgz", + "integrity": "sha512-K03TljaaoPK5FOyNMZAAEmhlyO49LaE4qCsr0lYHUKyb6QacTNF9pnfPpXnFlFD3TXuFbFbz7tJ51FujUXkXYA==", "cpu": [ "x64" ], @@ -1371,9 +1401,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", - "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.4.tgz", + "integrity": "sha512-VJYl4xSl/wqG2D5xTYncVWW+26ICV4wubwN9Gs5NrqhJtayikwCXzPL8GDsLnaLU3WwhQ8W02IinYSFJfyo34Q==", "cpu": [ "x64" ], @@ -1385,9 +1415,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", - "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.4.tgz", + "integrity": "sha512-ku2GvtPwQfCqoPFIJCqZ8o7bJcj+Y54cZSr43hHca6jLwAiCbZdBUOrqE6y29QFajNAzzpIOwsckaTFmN6/8TA==", "cpu": [ "arm64" ], @@ -1399,9 +1429,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", - "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.4.tgz", + "integrity": "sha512-V3nCe+eTt/W6UYNr/wGvO1fLpHUrnlirlypZfKCT1fG6hWfqhPgQV/K/mRBXBpxc0eKLIF18pIOFVPh0mqHjlg==", "cpu": [ "ia32" ], @@ -1413,9 +1443,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", - "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.4.tgz", + "integrity": "sha512-LTw1Dfd0mBIEqUVCxbvTE/LLo+9ZxVC9k99v1v4ahg9Aak6FpqOfNu5kRkeTAn0wphoC4JU7No1/rL+bBCEwhg==", "cpu": [ "x64" ], @@ -1614,13 +1644,13 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.16.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.13.tgz", - "integrity": "sha512-GjQ7im10B0labo8ZGXDGROUl9k0BNyDgzfGpb4g/cl+4yYDWVKcozANF4FGr4/p0O/rAkQClM6Wiwkije++1Tg==", + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.19.8" } }, "node_modules/@types/normalize-package-data": { @@ -1778,17 +1808,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.10.0.tgz", - "integrity": "sha512-phuB3hoP7FFKbRXxjl+DRlQDuJqhpOnm5MmtROXyWi3uS/Xg2ZXqiQfcG2BJHiN4QKyzdOJi3NEn/qTnjUlkmQ==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.12.2.tgz", + "integrity": "sha512-gQxbxM8mcxBwaEmWdtLCIGLfixBMHhQjBqR8sVWNTPpcj45WlYL2IObS/DNMLH1DBP0n8qz+aiiLTGfopPEebw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.10.0", - "@typescript-eslint/type-utils": "8.10.0", - "@typescript-eslint/utils": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0", + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/type-utils": "8.12.2", + "@typescript-eslint/utils": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1812,16 +1842,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.10.0.tgz", - "integrity": "sha512-E24l90SxuJhytWJ0pTQydFT46Nk0Z+bsLKo/L8rtQSL93rQ6byd1V/QbDpHUTdLPOMsBCcYXZweADNCfOCmOAg==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.12.2.tgz", + "integrity": "sha512-MrvlXNfGPLH3Z+r7Tk+Z5moZAc0dzdVjTgUgwsdGweH7lydysQsnSww3nAmsq8blFuRD5VRlAr9YdEFw3e6PBw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.10.0", - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/typescript-estree": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0", + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/typescript-estree": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "debug": "^4.3.4" }, "engines": { @@ -1841,14 +1871,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.10.0.tgz", - "integrity": "sha512-AgCaEjhfql9MDKjMUxWvH7HjLeBqMCBfIaBbzzIcBbQPZE7CPh1m6FF+L75NUMJFMLYhCywJXIDEMa3//1A0dw==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.12.2.tgz", + "integrity": "sha512-gPLpLtrj9aMHOvxJkSbDBmbRuYdtiEbnvO25bCMza3DhMjTQw0u7Y1M+YR5JPbMsXXnSPuCf5hfq0nEkQDL/JQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0" + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1859,14 +1889,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.10.0.tgz", - "integrity": "sha512-PCpUOpyQSpxBn230yIcK+LeCQaXuxrgCm2Zk1S+PTIRJsEfU6nJ0TtwyH8pIwPK/vJoA+7TZtzyAJSGBz+s/dg==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.12.2.tgz", + "integrity": "sha512-bwuU4TAogPI+1q/IJSKuD4shBLc/d2vGcRT588q+jzayQyjVK2X6v/fbR4InY2U2sgf8MEvVCqEWUzYzgBNcGQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.10.0", - "@typescript-eslint/utils": "8.10.0", + "@typescript-eslint/typescript-estree": "8.12.2", + "@typescript-eslint/utils": "8.12.2", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1884,9 +1914,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.10.0.tgz", - "integrity": "sha512-k/E48uzsfJCRRbGLapdZgrX52csmWJ2rcowwPvOZ8lwPUv3xW6CcFeJAXgx4uJm+Ge4+a4tFOkdYvSpxhRhg1w==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.12.2.tgz", + "integrity": "sha512-VwDwMF1SZ7wPBUZwmMdnDJ6sIFk4K4s+ALKLP6aIQsISkPv8jhiw65sAK6SuWODN/ix+m+HgbYDkH+zLjrzvOA==", "dev": true, "license": "MIT", "engines": { @@ -1898,14 +1928,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.10.0.tgz", - "integrity": "sha512-3OE0nlcOHaMvQ8Xu5gAfME3/tWVDpb/HxtpUZ1WeOAksZ/h/gwrBzCklaGzwZT97/lBbbxJ16dMA98JMEngW4w==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.12.2.tgz", + "integrity": "sha512-mME5MDwGe30Pq9zKPvyduyU86PH7aixwqYR2grTglAdB+AN8xXQ1vFGpYaUSJ5o5P/5znsSBeNcs5g5/2aQwow==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1953,16 +1983,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.10.0.tgz", - "integrity": "sha512-Oq4uZ7JFr9d1ZunE/QKy5egcDRXT/FrS2z/nlxzPua2VHFtmMvFNDvpq1m/hq0ra+T52aUezfcjGRIB7vNJF9w==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.12.2.tgz", + "integrity": "sha512-UTTuDIX3fkfAz6iSVa5rTuSfWIYZ6ATtEocQ/umkRSyC9O919lbZ8dcH7mysshrCdrAM03skJOEYaBugxN+M6A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.10.0", - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/typescript-estree": "8.10.0" + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/typescript-estree": "8.12.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1976,13 +2006,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.10.0.tgz", - "integrity": "sha512-k8nekgqwr7FadWk548Lfph6V3r9OVqjzAIVskE7orMZR23cGJjAOVazsZSJW+ElyjfTM4wx/1g88Mi70DDtG9A==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.12.2.tgz", + "integrity": "sha512-PChz8UaKQAVNHghsHcPyx1OMHoFRUEA7rJSK/mDhdq85bk+PLsUHUBqTQTFt18VJZbmxBovM65fezlheQRsSDA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.10.0", + "@typescript-eslint/types": "8.12.2", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -1994,21 +2024,21 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.3.tgz", - "integrity": "sha512-2OJ3c7UPoFSmBZwqD2VEkUw6A/tzPF0LmW0ZZhhB8PFxuc+9IBG/FaSM+RLEenc7ljzFvGN+G0nGQoZnh7sy2A==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.4.tgz", + "integrity": "sha512-FPKQuJfR6VTfcNMcGpqInmtJuVXFSCd9HQltYncfR01AzXhLucMEtQ5SinPdZxsT5x/5BK7I5qFJ5/ApGCmyTQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.6", + "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.11", - "magicast": "^0.3.4", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", "std-env": "^3.7.0", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" @@ -2017,8 +2047,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.3", - "vitest": "2.1.3" + "@vitest/browser": "2.1.4", + "vitest": "2.1.4" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2027,15 +2057,15 @@ } }, "node_modules/@vitest/expect": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.3.tgz", - "integrity": "sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.4.tgz", + "integrity": "sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.3", - "@vitest/utils": "2.1.3", - "chai": "^5.1.1", + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -2043,22 +2073,21 @@ } }, "node_modules/@vitest/mocker": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.3.tgz", - "integrity": "sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.4.tgz", + "integrity": "sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.3", + "@vitest/spy": "2.1.4", "estree-walker": "^3.0.3", - "magic-string": "^0.30.11" + "magic-string": "^0.30.12" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/spy": "2.1.3", - "msw": "^2.3.5", + "msw": "^2.4.9", "vite": "^5.0.0" }, "peerDependenciesMeta": { @@ -2071,9 +2100,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.3.tgz", - "integrity": "sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.4.tgz", + "integrity": "sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==", "dev": true, "license": "MIT", "dependencies": { @@ -2084,13 +2113,13 @@ } }, "node_modules/@vitest/runner": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.3.tgz", - "integrity": "sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.4.tgz", + "integrity": "sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.3", + "@vitest/utils": "2.1.4", "pathe": "^1.1.2" }, "funding": { @@ -2098,14 +2127,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.3.tgz", - "integrity": "sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.4.tgz", + "integrity": "sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.3", - "magic-string": "^0.30.11", + "@vitest/pretty-format": "2.1.4", + "magic-string": "^0.30.12", "pathe": "^1.1.2" }, "funding": { @@ -2113,27 +2142,27 @@ } }, "node_modules/@vitest/spy": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.3.tgz", - "integrity": "sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.4.tgz", + "integrity": "sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==", "dev": true, "license": "MIT", "dependencies": { - "tinyspy": "^3.0.0" + "tinyspy": "^3.0.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.3.tgz", - "integrity": "sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.4.tgz", + "integrity": "sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.3", - "loupe": "^3.1.1", + "@vitest/pretty-format": "2.1.4", + "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -2480,9 +2509,9 @@ ] }, "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, "license": "MIT", "dependencies": { @@ -3018,18 +3047,18 @@ } }, "node_modules/eslint": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz", - "integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz", + "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.6.0", + "@eslint/core": "^0.7.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.12.0", + "@eslint/js": "9.13.0", "@eslint/plugin-kit": "^0.2.0", "@humanfs/node": "^0.16.5", "@humanwhocodes/module-importer": "^1.0.1", @@ -3185,9 +3214,9 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", - "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3282,10 +3311,11 @@ } }, "node_modules/eta": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eta/-/eta-3.4.0.tgz", - "integrity": "sha512-tCsc7WXTjrTx4ZjYLplcqrI3o4mYJ+Z6YspeuGL8tbt/hHoMchwBwtKfwM09svEY86iRapY93vUqQttcNuIO5Q==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-3.5.0.tgz", + "integrity": "sha512-e3x3FBvGzeCIHhF+zhK8FZA2vC5uFn6b4HJjegUbIWrDb4mJ7JjTGMJY9VGIbRVpmSwHopNiaJibhjIr+HfLug==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" }, @@ -3294,9 +3324,9 @@ } }, "node_modules/exiftool-vendored": { - "version": "28.6.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-28.6.0.tgz", - "integrity": "sha512-Cx8/8ov1tKEacHhsi7FNYdisIhKq/SeQfprYSpYzwBuJwkPmCV8w7tTIvUJRQX9rvopXhBA4eBf1FPXqTZW5vA==", + "version": "28.7.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-28.7.0.tgz", + "integrity": "sha512-0zoq6kBS1yPjzJs+p0qZDinWEA72PTKoRk5ETYKfmeRcZAkhv83Y3HCpbb/LdgJJywfm8BcIJGezrBHvL7dVnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3307,14 +3337,14 @@ "luxon": "^3.5.0" }, "optionalDependencies": { - "exiftool-vendored.exe": "12.97.0", - "exiftool-vendored.pl": "12.97.0" + "exiftool-vendored.exe": "12.99.0", + "exiftool-vendored.pl": "12.99.0" } }, "node_modules/exiftool-vendored.exe": { - "version": "12.97.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.97.0.tgz", - "integrity": "sha512-+HxyFigEJOtwRjP7PhEslhZKuVW2V0hvmHPHtbVtNKGfAUGcfc95xNTjASQfKJvc+9ZuvzdEBPkEQmyA/ZYdIw==", + "version": "12.99.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.99.0.tgz", + "integrity": "sha512-ffpJHCzC9OYJqw4JlPNtCwRy02jwhmnSJEF/QqEjpuIWDEnlRBQP/yWRh1Nw21K1R4FB4yG5PlCgEDu09VQz/w==", "dev": true, "license": "MIT", "optional": true, @@ -3323,9 +3353,9 @@ ] }, "node_modules/exiftool-vendored.pl": { - "version": "12.97.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.97.0.tgz", - "integrity": "sha512-mXe9JEH3csfyPWcC7+H6IpfaokDMMr4S45n7MtiobGPdeeh+kFnf1SQ9cxg4sF403P6IKVeYYPbzgKMlpro9eQ==", + "version": "12.99.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.99.0.tgz", + "integrity": "sha512-qRVEPQxtoerXF+izJ0O7jGAr5o0Uyvnyu7ao5DTKzF+V7Fv3SurE0l43oCeZPFKo/Ld4V7vEylhFCm4IHVZKWA==", "dev": true, "license": "MIT", "optional": true, @@ -3333,6 +3363,16 @@ "!win32" ] }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3942,12 +3982,13 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, + "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" @@ -4172,9 +4213,9 @@ } }, "node_modules/jose": { - "version": "5.9.4", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.4.tgz", - "integrity": "sha512-WBBl6au1qg6OHj67yCffCgFR3BADJBXN8MdRvCgJDuMv3driV2nHr7jdGvaKX9IolosAsn+M0XRArqLXUhyJHQ==", + "version": "5.9.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", + "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", "dev": true, "license": "MIT", "funding": { @@ -4413,22 +4454,24 @@ } }, "node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/magicast": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", - "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/parser": "^7.24.4", - "@babel/types": "^7.24.0", + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, @@ -4768,33 +4811,34 @@ "dev": true }, "node_modules/oidc-provider": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-8.5.1.tgz", - "integrity": "sha512-Bm3EyxN68/KS76IlciJ3+4pnVtfdRWL+NghWpIF0XQbiRT1gzc6Qf/cyFmpL9yieko/jXYZ/uLHUv77jD00qww==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-8.5.2.tgz", + "integrity": "sha512-WhMIQ61KMgABvrYZJDuefOWFpX34DWgg+U4juKARplGhNUSNfHgJh6i6Mp+PTO08ZDk/oj1Ci7ScU6CAI/wgcg==", "dev": true, + "license": "MIT", "dependencies": { "@koa/cors": "^5.0.0", - "@koa/router": "^12.0.1", - "debug": "^4.3.5", - "eta": "^3.4.0", + "@koa/router": "^13.1.0", + "debug": "^4.3.7", + "eta": "^3.5.0", "got": "^13.0.0", - "jose": "^5.6.2", + "jose": "^5.9.4", "jsesc": "^3.0.2", "koa": "^2.15.3", "nanoid": "^5.0.7", "object-hash": "^3.0.0", "oidc-token-hash": "^5.0.3", "quick-lru": "^7.0.0", - "raw-body": "^2.5.2" + "raw-body": "^3.0.0" }, "funding": { "url": "https://github.com/sponsors/panva" } }, "node_modules/oidc-provider/node_modules/nanoid": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz", - "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.8.tgz", + "integrity": "sha512-TcJPw+9RV9dibz1hHUzlLVy8N4X9TnwirAjrU08Juo6BNKggzVfP2ZJ/3ZUSq15Xl5i85i+Z89XBO90pB2PghQ==", "dev": true, "funding": [ { @@ -4802,6 +4846,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.js" }, @@ -5040,9 +5085,9 @@ } }, "node_modules/pg": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.0.tgz", - "integrity": "sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw==", + "version": "8.13.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.1.tgz", + "integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5162,13 +5207,13 @@ } }, "node_modules/playwright": { - "version": "1.48.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.1.tgz", - "integrity": "sha512-j8CiHW/V6HxmbntOfyB4+T/uk08tBy6ph0MpBXwuoofkSnLmlfdYNNkFTYD6ofzzlSqLA1fwH4vwvVFvJgLN0w==", + "version": "1.48.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.2.tgz", + "integrity": "sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.48.1" + "playwright-core": "1.48.2" }, "bin": { "playwright": "cli.js" @@ -5181,9 +5226,9 @@ } }, "node_modules/playwright-core": { - "version": "1.48.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.1.tgz", - "integrity": "sha512-Yw/t4VAFX/bBr1OzwCuOMZkY1Cnb4z/doAFSwf4huqAGWmf9eMNjmK7NiOljCdLmxeRYcGPPmcDgU0zOlzP0YA==", + "version": "1.48.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.2.tgz", + "integrity": "sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5397,14 +5442,15 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", "dev": true, + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", - "iconv-lite": "0.4.24", + "iconv-lite": "0.6.3", "unpipe": "1.0.0" }, "engines": { @@ -5631,9 +5677,9 @@ } }, "node_modules/rollup": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", - "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.4.tgz", + "integrity": "sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA==", "dev": true, "license": "MIT", "dependencies": { @@ -5647,22 +5693,24 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.24.0", - "@rollup/rollup-android-arm64": "4.24.0", - "@rollup/rollup-darwin-arm64": "4.24.0", - "@rollup/rollup-darwin-x64": "4.24.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", - "@rollup/rollup-linux-arm-musleabihf": "4.24.0", - "@rollup/rollup-linux-arm64-gnu": "4.24.0", - "@rollup/rollup-linux-arm64-musl": "4.24.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", - "@rollup/rollup-linux-riscv64-gnu": "4.24.0", - "@rollup/rollup-linux-s390x-gnu": "4.24.0", - "@rollup/rollup-linux-x64-gnu": "4.24.0", - "@rollup/rollup-linux-x64-musl": "4.24.0", - "@rollup/rollup-win32-arm64-msvc": "4.24.0", - "@rollup/rollup-win32-ia32-msvc": "4.24.0", - "@rollup/rollup-win32-x64-msvc": "4.24.0", + "@rollup/rollup-android-arm-eabi": "4.24.4", + "@rollup/rollup-android-arm64": "4.24.4", + "@rollup/rollup-darwin-arm64": "4.24.4", + "@rollup/rollup-darwin-x64": "4.24.4", + "@rollup/rollup-freebsd-arm64": "4.24.4", + "@rollup/rollup-freebsd-x64": "4.24.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.4", + "@rollup/rollup-linux-arm-musleabihf": "4.24.4", + "@rollup/rollup-linux-arm64-gnu": "4.24.4", + "@rollup/rollup-linux-arm64-musl": "4.24.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.4", + "@rollup/rollup-linux-riscv64-gnu": "4.24.4", + "@rollup/rollup-linux-s390x-gnu": "4.24.4", + "@rollup/rollup-linux-x64-gnu": "4.24.4", + "@rollup/rollup-linux-x64-musl": "4.24.4", + "@rollup/rollup-win32-arm64-msvc": "4.24.4", + "@rollup/rollup-win32-ia32-msvc": "4.24.4", + "@rollup/rollup-win32-x64-msvc": "4.24.4", "fsevents": "~2.3.2" } }, @@ -5714,7 +5762,8 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/semver": { "version": "7.6.2", @@ -5815,9 +5864,9 @@ } }, "node_modules/socket.io-client": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.0.tgz", - "integrity": "sha512-C0jdhD5yQahMws9alf/yvtsMGTaIDBnZ8Rb5HU56svyq0l5LIrGzIDZZD5pHQlmzxLuU91Gz+VpQMKgCTNYtkw==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6175,17 +6224,18 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", - "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", "dev": true, "license": "MIT" }, "node_modules/tinypool": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.0.tgz", - "integrity": "sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" } @@ -6209,15 +6259,6 @@ "node": ">=14.0.0" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6407,9 +6448,9 @@ } }, "node_modules/vite": { - "version": "5.4.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz", - "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==", + "version": "5.4.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", + "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6467,14 +6508,14 @@ } }, "node_modules/vite-node": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.3.tgz", - "integrity": "sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.4.tgz", + "integrity": "sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.6", + "debug": "^4.3.7", "pathe": "^1.1.2", "vite": "^5.0.0" }, @@ -6504,30 +6545,31 @@ } }, "node_modules/vitest": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.3.tgz", - "integrity": "sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.4.tgz", + "integrity": "sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "2.1.3", - "@vitest/mocker": "2.1.3", - "@vitest/pretty-format": "^2.1.3", - "@vitest/runner": "2.1.3", - "@vitest/snapshot": "2.1.3", - "@vitest/spy": "2.1.3", - "@vitest/utils": "2.1.3", - "chai": "^5.1.1", - "debug": "^4.3.6", - "magic-string": "^0.30.11", + "@vitest/expect": "2.1.4", + "@vitest/mocker": "2.1.4", + "@vitest/pretty-format": "^2.1.4", + "@vitest/runner": "2.1.4", + "@vitest/snapshot": "2.1.4", + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.7.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.0", - "tinypool": "^1.0.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.1.3", + "vite-node": "2.1.4", "why-is-node-running": "^2.3.0" }, "bin": { @@ -6542,8 +6584,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.3", - "@vitest/ui": "2.1.3", + "@vitest/browser": "2.1.4", + "@vitest/ui": "2.1.4", "happy-dom": "*", "jsdom": "*" }, diff --git a/e2e/package.json b/e2e/package.json index d67aa49f5d71a..b573d2e73020f 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,6 +1,6 @@ { "name": "immich-e2e", - "version": "1.118.2", + "version": "1.120.2", "description": "", "main": "index.js", "type": "module", @@ -25,7 +25,7 @@ "@immich/sdk": "file:../open-api/typescript-sdk", "@playwright/test": "^1.44.1", "@types/luxon": "^3.4.2", - "@types/node": "^20.16.12", + "@types/node": "^22.9.0", "@types/oidc-provider": "^8.5.1", "@types/pg": "^8.11.0", "@types/pngjs": "^6.0.4", @@ -53,6 +53,6 @@ "vitest": "^2.0.5" }, "volta": { - "node": "20.18.0" + "node": "22.11.0" } } diff --git a/e2e/src/api/specs/asset.e2e-spec.ts b/e2e/src/api/specs/asset.e2e-spec.ts index cc898e7468717..a0c429a82e224 100644 --- a/e2e/src/api/specs/asset.e2e-spec.ts +++ b/e2e/src/api/specs/asset.e2e-spec.ts @@ -1148,6 +1148,78 @@ describe('/asset', () => { }, }, }, + { + input: 'formats/raw/Canon/PowerShot_G12.CR2', + expected: { + type: AssetTypeEnum.Image, + originalFileName: 'PowerShot_G12.CR2', + fileCreatedAt: '2015-12-27T09:55:40.000Z', + exifInfo: { + make: 'Canon', + model: 'Canon PowerShot G12', + exifImageHeight: 2736, + exifImageWidth: 3648, + exposureTime: '1/1000', + fNumber: 4, + focalLength: 18.098, + iso: 80, + lensModel: null, + fileSizeInByte: 11_113_617, + dateTimeOriginal: '2015-12-27T09:55:40.000Z', + latitude: null, + longitude: null, + orientation: '1', + }, + }, + }, + { + input: 'formats/raw/Fujifilm/X100V_compressed.RAF', + expected: { + type: AssetTypeEnum.Image, + originalFileName: 'X100V_compressed.RAF', + fileCreatedAt: '2024-10-12T21:01:01.000Z', + exifInfo: { + make: 'FUJIFILM', + model: 'X100V', + exifImageHeight: 4160, + exifImageWidth: 6240, + exposureTime: '1/4000', + fNumber: 16, + focalLength: 23, + iso: 160, + lensModel: null, + fileSizeInByte: 13_551_312, + dateTimeOriginal: '2024-10-12T21:01:01.000Z', + latitude: null, + longitude: null, + orientation: '6', + }, + }, + }, + { + input: 'formats/raw/Ricoh/GR3/Ricoh_GR3-450.DNG', + expected: { + type: AssetTypeEnum.Image, + originalFileName: 'Ricoh_GR3-450.DNG', + fileCreatedAt: '2024-06-08T13:48:39.000Z', + exifInfo: { + dateTimeOriginal: '2024-06-08T13:48:39.000Z', + exifImageHeight: 4064, + exifImageWidth: 6112, + exposureTime: '1/400', + fNumber: 5, + fileSizeInByte: 31_175_472, + focalLength: 18.3, + iso: 100, + latitude: 36.613_24, + lensModel: 'GR LENS 18.3mm F2.8', + longitude: -121.897_85, + make: 'RICOH IMAGING COMPANY, LTD.', + model: 'RICOH GR III', + orientation: '1', + }, + }, + }, ]; it(`should upload and generate a thumbnail for different file types`, async () => { diff --git a/e2e/src/api/specs/search.e2e-spec.ts b/e2e/src/api/specs/search.e2e-spec.ts index 0e5d882f80e50..627fbb3e9eca1 100644 --- a/e2e/src/api/specs/search.e2e-spec.ts +++ b/e2e/src/api/specs/search.e2e-spec.ts @@ -473,10 +473,7 @@ describe('/search', () => { .get('/search/explore') .set('Authorization', `Bearer ${admin.accessToken}`); expect(status).toBe(200); - expect(body).toEqual([ - { fieldName: 'exifInfo.city', items: [] }, - { fieldName: 'smartInfo.tags', items: [] }, - ]); + expect(body).toEqual([{ fieldName: 'exifInfo.city', items: [] }]); }); }); diff --git a/e2e/src/api/specs/server.e2e-spec.ts b/e2e/src/api/specs/server.e2e-spec.ts index 3133460adaf2a..4bff4b3dea678 100644 --- a/e2e/src/api/specs/server.e2e-spec.ts +++ b/e2e/src/api/specs/server.e2e-spec.ts @@ -163,11 +163,15 @@ describe('/server', () => { expect(body).toEqual({ photos: 0, usage: 0, + usagePhotos: 0, + usageVideos: 0, usageByUser: [ { quotaSizeInBytes: null, photos: 0, usage: 0, + usagePhotos: 0, + usageVideos: 0, userName: 'Immich Admin', userId: admin.userId, videos: 0, @@ -176,6 +180,8 @@ describe('/server', () => { quotaSizeInBytes: null, photos: 0, usage: 0, + usagePhotos: 0, + usageVideos: 0, userName: 'User 1', userId: nonAdmin.userId, videos: 0, diff --git a/e2e/src/cli/specs/upload.e2e-spec.ts b/e2e/src/cli/specs/upload.e2e-spec.ts index d700aa73b2091..301c6d3bf0621 100644 --- a/e2e/src/cli/specs/upload.e2e-spec.ts +++ b/e2e/src/cli/specs/upload.e2e-spec.ts @@ -103,7 +103,7 @@ describe(`immich upload`, () => { describe(`immich upload /path/to/file.jpg`, () => { it('should upload a single file', async () => { const { stderr, stdout, exitCode } = await immichCli(['upload', `${testAssetDir}/albums/nature/silver_fir.jpg`]); - expect(stderr).toBe(''); + expect(stderr).toContain('{message}'); expect(stdout.split('\n')).toEqual( expect.arrayContaining([expect.stringContaining('Successfully uploaded 1 new asset')]), ); @@ -126,7 +126,7 @@ describe(`immich upload`, () => { const expectedCount = Object.entries(files).filter((entry) => entry[1]).length; const { stderr, stdout, exitCode } = await immichCli(['upload', ...commandLine]); - expect(stderr).toBe(''); + expect(stderr).toContain('{message}'); expect(stdout.split('\n')).toEqual( expect.arrayContaining([expect.stringContaining(`Successfully uploaded ${expectedCount} new asset`)]), ); @@ -154,7 +154,7 @@ describe(`immich upload`, () => { cpSync(`${testAssetDir}/albums/nature/silver_fir.jpg`, testPaths[1]); const { stderr, stdout, exitCode } = await immichCli(['upload', ...testPaths]); - expect(stderr).toBe(''); + expect(stderr).toContain('{message}'); expect(stdout.split('\n')).toEqual( expect.arrayContaining([expect.stringContaining('Successfully uploaded 2 new assets')]), ); @@ -169,7 +169,7 @@ describe(`immich upload`, () => { it('should skip a duplicate file', async () => { const first = await immichCli(['upload', `${testAssetDir}/albums/nature/silver_fir.jpg`]); - expect(first.stderr).toBe(''); + expect(first.stderr).toContain('{message}'); expect(first.stdout.split('\n')).toEqual( expect.arrayContaining([expect.stringContaining('Successfully uploaded 1 new asset')]), ); @@ -179,7 +179,7 @@ describe(`immich upload`, () => { expect(assets.total).toBe(1); const second = await immichCli(['upload', `${testAssetDir}/albums/nature/silver_fir.jpg`]); - expect(second.stderr).toBe(''); + expect(second.stderr).toContain('{message}'); expect(second.stdout.split('\n')).toEqual( expect.arrayContaining([ expect.stringContaining('Found 0 new files and 1 duplicate'), @@ -205,7 +205,7 @@ describe(`immich upload`, () => { `${testAssetDir}/albums/nature/silver_fir.jpg`, '--dry-run', ]); - expect(stderr).toBe(''); + expect(stderr).toContain('{message}'); expect(stdout.split('\n')).toEqual( expect.arrayContaining([expect.stringContaining('Would have uploaded 1 asset')]), ); @@ -217,7 +217,7 @@ describe(`immich upload`, () => { it('dry run should handle duplicates', async () => { const first = await immichCli(['upload', `${testAssetDir}/albums/nature/silver_fir.jpg`]); - expect(first.stderr).toBe(''); + expect(first.stderr).toContain('{message}'); expect(first.stdout.split('\n')).toEqual( expect.arrayContaining([expect.stringContaining('Successfully uploaded 1 new asset')]), ); @@ -227,7 +227,7 @@ describe(`immich upload`, () => { expect(assets.total).toBe(1); const second = await immichCli(['upload', `${testAssetDir}/albums/nature/`, '--dry-run']); - expect(second.stderr).toBe(''); + expect(second.stderr).toContain('{message}'); expect(second.stdout.split('\n')).toEqual( expect.arrayContaining([ expect.stringContaining('Found 8 new files and 1 duplicate'), @@ -241,7 +241,7 @@ describe(`immich upload`, () => { describe('immich upload --recursive', () => { it('should upload a folder recursively', async () => { const { stderr, stdout, exitCode } = await immichCli(['upload', `${testAssetDir}/albums/nature/`, '--recursive']); - expect(stderr).toBe(''); + expect(stderr).toContain('{message}'); expect(stdout.split('\n')).toEqual( expect.arrayContaining([expect.stringContaining('Successfully uploaded 9 new assets')]), ); @@ -267,7 +267,7 @@ describe(`immich upload`, () => { expect.stringContaining('Successfully updated 9 assets'), ]), ); - expect(stderr).toBe(''); + expect(stderr).toContain('{message}'); expect(exitCode).toBe(0); const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) }); @@ -283,7 +283,7 @@ describe(`immich upload`, () => { expect(response1.stdout.split('\n')).toEqual( expect.arrayContaining([expect.stringContaining('Successfully uploaded 9 new assets')]), ); - expect(response1.stderr).toBe(''); + expect(response1.stderr).toContain('{message}'); expect(response1.exitCode).toBe(0); const assets1 = await getAssetStatistics({}, { headers: asKeyAuth(key) }); @@ -299,7 +299,7 @@ describe(`immich upload`, () => { expect.stringContaining('Successfully updated 9 assets'), ]), ); - expect(response2.stderr).toBe(''); + expect(response2.stderr).toContain('{message}'); expect(response2.exitCode).toBe(0); const assets2 = await getAssetStatistics({}, { headers: asKeyAuth(key) }); @@ -325,7 +325,7 @@ describe(`immich upload`, () => { expect.stringContaining('Would have updated albums of 9 assets'), ]), ); - expect(stderr).toBe(''); + expect(stderr).toContain('{message}'); expect(exitCode).toBe(0); const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) }); @@ -351,7 +351,7 @@ describe(`immich upload`, () => { expect.stringContaining('Successfully updated 9 assets'), ]), ); - expect(stderr).toBe(''); + expect(stderr).toContain('{message}'); expect(exitCode).toBe(0); const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) }); @@ -377,7 +377,7 @@ describe(`immich upload`, () => { expect.stringContaining('Would have updated albums of 9 assets'), ]), ); - expect(stderr).toBe(''); + expect(stderr).toContain('{message}'); expect(exitCode).toBe(0); const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) }); @@ -408,7 +408,7 @@ describe(`immich upload`, () => { expect.stringContaining('Deleting assets that have been uploaded'), ]), ); - expect(stderr).toBe(''); + expect(stderr).toContain('{message}'); expect(exitCode).toBe(0); const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) }); @@ -434,7 +434,7 @@ describe(`immich upload`, () => { expect.stringContaining('Would have deleted 9 local assets'), ]), ); - expect(stderr).toBe(''); + expect(stderr).toContain('{message}'); expect(exitCode).toBe(0); const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) }); @@ -493,7 +493,7 @@ describe(`immich upload`, () => { '2', ]); - expect(stderr).toBe(''); + expect(stderr).toContain('{message}'); expect(stdout.split('\n')).toEqual( expect.arrayContaining([ 'Found 9 new files and 0 duplicates', @@ -534,7 +534,7 @@ describe(`immich upload`, () => { 'silver_fir.jpg', ]); - expect(stderr).toBe(''); + expect(stderr).toContain('{message}'); expect(stdout.split('\n')).toEqual( expect.arrayContaining([ 'Found 8 new files and 0 duplicates', @@ -555,7 +555,7 @@ describe(`immich upload`, () => { '!(*_*_*).jpg', ]); - expect(stderr).toBe(''); + expect(stderr).toContain('{message}'); expect(stdout.split('\n')).toEqual( expect.arrayContaining([ 'Found 1 new files and 0 duplicates', @@ -577,7 +577,7 @@ describe(`immich upload`, () => { '--dry-run', ]); - expect(stderr).toBe(''); + expect(stderr).toContain('{message}'); expect(stdout.split('\n')).toEqual( expect.arrayContaining([ 'Found 8 new files and 0 duplicates', diff --git a/e2e/src/immich-admin/specs/immich-admin.e2e-spec.ts b/e2e/src/immich-admin/specs/immich-admin.e2e-spec.ts index d025b7a338185..cf0558883a0d9 100644 --- a/e2e/src/immich-admin/specs/immich-admin.e2e-spec.ts +++ b/e2e/src/immich-admin/specs/immich-admin.e2e-spec.ts @@ -9,9 +9,11 @@ describe(`immich-admin`, () => { describe('list-users', () => { it('should list the admin user', async () => { - const { stdout, stderr, exitCode } = await immichAdmin(['list-users']).promise; + const { stdout, exitCode } = await immichAdmin(['list-users']).promise; expect(exitCode).toBe(0); - expect(stderr).toBe(''); + + // TODO: Vitest needs upgrade to Node 22.x to fix the failed check + // expect(stderr).toBe(''); expect(stdout).toContain("email: 'admin@immich.cloud'"); expect(stdout).toContain("name: 'Immich Admin'"); }); @@ -29,9 +31,10 @@ describe(`immich-admin`, () => { } }); - const { stderr, stdout, exitCode } = await promise; + const { stdout, exitCode } = await promise; expect(exitCode).toBe(0); - expect(stderr).toBe(''); + // TODO: Vitest needs upgrade to Node 22.x to fix the failed check + // expect(stderr).toBe(''); expect(stdout).toContain('The admin password has been updated to:'); }); }); diff --git a/e2e/src/utils.ts b/e2e/src/utils.ts index 3af44b50b8330..619ef601e2dea 100644 --- a/e2e/src/utils.ts +++ b/e2e/src/utils.ts @@ -11,6 +11,7 @@ import { PersonCreateDto, SharedLinkCreateDto, UserAdminCreateDto, + UserPreferencesUpdateDto, ValidateLibraryDto, checkExistingAssets, createAlbum, @@ -19,6 +20,7 @@ import { createPartner, createPerson, createSharedLink, + createStack, createUserAdmin, deleteAssets, getAllJobsStatus, @@ -28,10 +30,13 @@ import { searchMetadata, setBaseUrl, signUpAdmin, + tagAssets, updateAdminOnboarding, updateAlbumUser, updateAssets, updateConfig, + updateMyPreferences, + upsertTags, validate, } from '@immich/sdk'; import { BrowserContext } from '@playwright/test'; @@ -444,6 +449,18 @@ export const utils = { createPartner: (accessToken: string, id: string) => createPartner({ id }, { headers: asBearerAuth(accessToken) }), + updateMyPreferences: (accessToken: string, userPreferencesUpdateDto: UserPreferencesUpdateDto) => + updateMyPreferences({ userPreferencesUpdateDto }, { headers: asBearerAuth(accessToken) }), + + createStack: (accessToken: string, assetIds: string[]) => + createStack({ stackCreateDto: { assetIds } }, { headers: asBearerAuth(accessToken) }), + + upsertTags: (accessToken: string, tags: string[]) => + upsertTags({ tagUpsertDto: { tags } }, { headers: asBearerAuth(accessToken) }), + + tagAssets: (accessToken: string, tagId: string, assetIds: string[]) => + tagAssets({ id: tagId, bulkIdsDto: { ids: assetIds } }, { headers: asBearerAuth(accessToken) }), + setAuthCookies: async (context: BrowserContext, accessToken: string, domain = '127.0.0.1') => await context.addCookies([ { diff --git a/e2e/src/web/specs/asset-viewer/stack.e2e-spec.ts b/e2e/src/web/specs/asset-viewer/stack.e2e-spec.ts new file mode 100644 index 0000000000000..cb40f82c0a813 --- /dev/null +++ b/e2e/src/web/specs/asset-viewer/stack.e2e-spec.ts @@ -0,0 +1,66 @@ +import { AssetMediaResponseDto, LoginResponseDto } from '@immich/sdk'; +import { expect, Page, test } from '@playwright/test'; +import { utils } from 'src/utils'; + +async function ensureDetailPanelVisible(page: Page) { + await page.waitForSelector('#immich-asset-viewer'); + + const isVisible = await page.locator('#detail-panel').isVisible(); + if (!isVisible) { + await page.keyboard.press('i'); + await page.waitForSelector('#detail-panel'); + } +} + +test.describe('Asset Viewer stack', () => { + let admin: LoginResponseDto; + let assetOne: AssetMediaResponseDto; + let assetTwo: AssetMediaResponseDto; + + test.beforeAll(async () => { + utils.initSdk(); + await utils.resetDatabase(); + admin = await utils.adminSetup(); + await utils.updateMyPreferences(admin.accessToken, { tags: { enabled: true } }); + + assetOne = await utils.createAsset(admin.accessToken); + assetTwo = await utils.createAsset(admin.accessToken); + await utils.createStack(admin.accessToken, [assetOne.id, assetTwo.id]); + + const tags = await utils.upsertTags(admin.accessToken, ['test/1', 'test/2']); + const tagOne = tags.find((tag) => tag.value === 'test/1')!; + const tagTwo = tags.find((tag) => tag.value === 'test/2')!; + await utils.tagAssets(admin.accessToken, tagOne.id, [assetOne.id]); + await utils.tagAssets(admin.accessToken, tagTwo.id, [assetTwo.id]); + }); + + test('stack slideshow is visible', async ({ page, context }) => { + await utils.setAuthCookies(context, admin.accessToken); + await page.goto(`/photos/${assetOne.id}`); + + const stackAssets = page.locator('#stack-slideshow [data-asset]'); + await expect(stackAssets.first()).toBeVisible(); + await expect(stackAssets.nth(1)).toBeVisible(); + }); + + test('tags of primary asset are visible', async ({ page, context }) => { + await utils.setAuthCookies(context, admin.accessToken); + await page.goto(`/photos/${assetOne.id}`); + await ensureDetailPanelVisible(page); + + const tags = page.getByTestId('detail-panel-tags').getByRole('link'); + await expect(tags.first()).toHaveText('test/1'); + }); + + test('tags of second asset are visible', async ({ page, context }) => { + await utils.setAuthCookies(context, admin.accessToken); + await page.goto(`/photos/${assetOne.id}`); + await ensureDetailPanelVisible(page); + + const stackAssets = page.locator('#stack-slideshow [data-asset]'); + await stackAssets.nth(1).click(); + + const tags = page.getByTestId('detail-panel-tags').getByRole('link'); + await expect(tags.first()).toHaveText('test/2'); + }); +}); diff --git a/e2e/test-assets b/e2e/test-assets index 3e057d2f58750..99544a200412d 160000 --- a/e2e/test-assets +++ b/e2e/test-assets @@ -1 +1 @@ -Subproject commit 3e057d2f58750acdf7ff281a3938e34a86cfef4d +Subproject commit 99544a200412d553103cc7b8f1a28f339c7cffd9 diff --git a/i18n/be.json b/i18n/be.json index 0967ef424bce6..8747b4ac8cc6a 100644 --- a/i18n/be.json +++ b/i18n/be.json @@ -1 +1,32 @@ -{} +{ + "about": "Пра праграму", + "account": "Уліковы запіс", + "account_settings": "Налады акаўнта", + "acknowledge": "Пацвердзіць", + "action": "Дзеянне", + "actions": "Дзеянні", + "active": "Актыўны", + "activity": "Актыўнасць", + "activity_changed": "Актыўнасць {enabled, select, true {уключана} other {адключана}}", + "add": "Дадаць", + "add_a_description": "Дадаць апісанне", + "add_a_location": "Дадаць месца", + "add_a_name": "Дадаць імя", + "add_a_title": "Дадаць загаловак", + "add_exclusion_pattern": "Дадаць шаблон выключэння", + "add_import_path": "Дадаць шлях імпарту", + "add_location": "Дадайце месца", + "add_more_users": "Дадаць больш карыстальнікаў", + "add_partner": "Дадаць партнёра", + "add_path": "Дадаць шлях", + "add_photos": "Дадаць фота", + "add_to": "Дадаць у...", + "add_to_album": "Дадаць у альбом", + "add_to_shared_album": "Дадаць у агульны альбом", + "added_to_archive": "Дададзена ў архіў", + "added_to_favorites": "Дададзена ў абраныя", + "added_to_favorites_count": "Дададзена {count, number} да абранага", + "admin": { + "add_exclusion_pattern_description": "Дадайце шаблоны выключэнняў. Падтрымліваецца выкарыстанне сімвалаў * , ** і ?. Каб ігнараваць усе файлы ў любой дырэкторыі з назвай \"Raw\", выкарыстоўвайце \"**/Raw/**\". Каб ігнараваць усе файлы, якія заканчваюцца на \".tif\", выкарыстоўвайце \"**/.tif\". Каб ігнараваць абсолютны шлях, выкарыстоўвайце \"/path/to/ignore/**\"." + } +} diff --git a/i18n/ca.json b/i18n/ca.json index 94b04f2c3c691..86d85becf774c 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -874,7 +874,7 @@ "loop_videos_description": "Habilita la reproducció en bucle del vídeo en els detalls.", "main_branch_warning": "Esteu usant una versió de desenvolupaent. Recomanem fer servir una versió publicada!", "make": "Fabricant", - "manage_shared_links": "Spravovat sdílené odkazy", + "manage_shared_links": "Administrar enllaços compartits", "manage_sharing_with_partners": "Gestiona la compartició amb els companys", "manage_the_app_settings": "Gestioneu la configuració de l'aplicació", "manage_your_account": "Gestiona el teu compte", diff --git a/i18n/cs.json b/i18n/cs.json index 12ba83b8e799b..59c60066db182 100644 --- a/i18n/cs.json +++ b/i18n/cs.json @@ -34,6 +34,11 @@ "authentication_settings_disable_all": "Opravdu chcete zakázat všechny metody přihlášení? Přihlašování bude úplně zakázáno.", "authentication_settings_reenable": "Pro opětovné povolení použijte příkaz Příkaz serveru.", "background_task_job": "Úkoly na pozadí", + "backup_database": "Zálohování databáze", + "backup_database_enable_description": "Povolit zálohování databáze", + "backup_keep_last_amount": "Počet předchozích záloh k uchování", + "backup_settings": "Nastavení zálohování", + "backup_settings_description": "Spravovat nastavení zálohování databáze", "check_all": "Vše zkontrolovat", "cleared_jobs": "Hotové úlohy pro: {job}", "config_set_by_file": "Konfigurace je aktuálně prováděna konfiguračním souborem", @@ -43,6 +48,9 @@ "confirm_reprocess_all_faces": "Opravdu chcete znovu zpracovat všechny obličeje? Tím se vymažou i pojmenované osoby.", "confirm_user_password_reset": "Opravdu chcete obnovit heslo uživatele {user}?", "create_job": "Vytvořit úlohu", + "cron_expression": "Výraz cron", + "cron_expression_description": "Nastavte interval prohledávání pomocí cron formátu. Další informace naleznete např. v Crontab Guru", + "cron_expression_presets": "Předvolby výrazů cron", "crontab_guru": "Crontab Guru", "disable_login": "Zakázat přihlášení", "disabled": "Zakázáno", diff --git a/i18n/da.json b/i18n/da.json index 9a4101b02350b..1d1ca5af02d5a 100644 --- a/i18n/da.json +++ b/i18n/da.json @@ -33,6 +33,11 @@ "authentication_settings_disable_all": "Er du sikker på at du vil deaktivere alle loginmuligheder? Login vil blive helt deaktiveret.", "authentication_settings_reenable": "Brug en server-kommando for at genaktivere.", "background_task_job": "Baggrundsopgaver", + "backup_database": "Backup Database", + "backup_database_enable_description": "Slå database-backup til", + "backup_keep_last_amount": "Mængde af tidligere backups, der skal gemmes", + "backup_settings": "Backup-indstillinger", + "backup_settings_description": "Administrer backupindstillinger for database", "check_all": "Tjek Alle", "cleared_jobs": "Ryddet jobs til: {job}", "config_set_by_file": "konfigurationen er i øjeblikket indstillet af en konfigurations fil", @@ -41,6 +46,7 @@ "confirm_email_below": "For at bekræfte, skriv \"{email}\" herunder", "confirm_reprocess_all_faces": "Er du sikker på, at du vil genbehandle alle ansigter? Dette vil også rydde navngivne personer.", "confirm_user_password_reset": "Er du sikker på, at du vil nulstille {user}s adgangskode?", + "create_job": "Opret job", "crontab_guru": "Crontab Guru", "disable_login": "Deaktiver login", "disabled": "", @@ -54,21 +60,27 @@ "failed_job_command": "Kommando {command} mislykkedes for job: {job}", "force_delete_user_warning": "ADVARSEL: Dette vil øjeblikkeligt fjerne brugeren og alle Billeder/Videoer. Dette kan ikke fortrydes, og filerne kan ikke gendannes.", "forcing_refresh_library_files": "Tvinger genopfriskning af alle biblioteksfiler", + "image_format": "Format", "image_format_description": "WebP producerer mindre filer end JPEG, men er langsommere at komprimere.", "image_prefer_embedded_preview": "Foretræk indlejret forhåndsvisning", "image_prefer_embedded_preview_setting_description": "Brug indlejrede forhåndsvisninger i RAW fotos som input til billedbehandling, når det er tilgængeligt. Dette kan give mere nøjagtige farver for nogle billeder, men kvaliteten af forhåndsvisningen er kameraafhængig, og billedet kan have flere komprimeringsartefakter.", "image_prefer_wide_gamut": "Foretrækker bred farveskala", "image_prefer_wide_gamut_setting_description": "Brug Display P3 til miniaturebilleder. Dette bevarer billeder med brede farveskalaers dynamik bedre, men billeder kan komme til at se anderledes ud på gamle enheder med en gammel browserversion. sRGB-billeder bliver beholdt som sRGB for at undgå farveskift.", + "image_preview_description": "Mellemstørrelse billede med fjernet metadata, der bruges, når du ser en enkelt mediefil og til machine learning", "image_preview_format": "Forhåndsvisningsformat", + "image_preview_quality_description": "Kvalitet af forhåndsvisning fra 1-100. Højere er bedre, men producerer større filer og kan reducere apprespons. Valg af en lav værdi kan påvirke kvaliteten af machine learning.", "image_preview_resolution": "Forhåndsvisnings opløsning", "image_preview_resolution_description": "Bliver brugt når et enkelt billede betragtes og ved maskinlæring. Højere opløsninger kan bevare flere detaljer, men tager længere tid at indkode, har større filstørrelser, og kan gøre appoplevelsen sløvere.", + "image_preview_title": "Indstillinger for forhåndsvisning", "image_quality": "Kvalitet", "image_quality_description": "Billedkvalitet fra 1-100. Højere er bedre for kvaliteten, men producerer større filer. Denne indstilling påvirker forhåndsvisningen og miniaturebillederne.", + "image_resolution": "Opløsning", "image_settings": "Billedindstillinger", "image_settings_description": "Administrer kvaliteten og opløsningen af genererede billeder", "image_thumbnail_format": "Miniatureformat", "image_thumbnail_resolution": "Miniature opløsning", "image_thumbnail_resolution_description": "Bruges ved visning af grupper af billeder (hovedtidslinje, albumvisning osv.). Højere opløsninger kan bevare flere detaljer, men det tager længere tid at kode, har større filstørrelser og kan reducere appens reaktionsevne.", + "image_thumbnail_title": "Thumbnail-indstillinger", "job_concurrency": "{job} samtidighed", "job_not_concurrency_safe": "Denne opgave er ikke sikker at køre samtidigt med andre.", "job_settings": "Jobindstillinger", @@ -198,6 +210,7 @@ "password_settings": "Adgangskodelogin", "password_settings_description": "Administrer indstillinger for adgangskodelogin", "paths_validated_successfully": "Alle stier valideret med succes", + "person_cleanup_job": "Person-oprydning", "quota_size_gib": "Kvotestørrelse (GiB)", "refreshing_all_libraries": "Opdaterer alle biblioteker", "registration": "Administratorregistrering", @@ -209,6 +222,7 @@ "require_password_change_on_login": "Kræv at brugeren skifter adgangskode ved første login", "reset_settings_to_default": "Nulstil indstillingerne til standard", "reset_settings_to_recent_saved": "Nulstil indstillinger til de senest gemte indstillinger", + "scanning_library": "Scanner bibliotek", "scanning_library_for_changed_files": "Skanner bibliotek efter ændrede filer", "scanning_library_for_new_files": "Skanner bibliotek efter nye filer", "send_welcome_email": "Send velkomst-email", @@ -312,6 +326,7 @@ "trash_settings_description": "Administrér skraldeindstillinger", "untracked_files": "Utrackede filer", "untracked_files_description": "Applikationen holder ikke styr på disse filer. De kan være resultatet af mislykkede flytninger, afbrudte uploads eller være efterladt på grund af en fejl", + "user_cleanup_job": "Bruger-oprydning", "user_delete_delay": "{user}'s konto og mediefiler vil blive planlagt til permanent sletning om {delay, plural, one {# dag} other {# dage}}.", "user_delete_delay_settings": "Slet forsinkelse", "user_delete_delay_settings_description": "Antal dage efter fjernelse for permanent at slette en brugers konto og mediefiler. Opgaven for sletning af brugere kører ved midnat for at tjekke efter brugere, der er klar til sletning. Ændringer i denne indstilling vil blive evalueret ved næste udførelse.", @@ -356,6 +371,7 @@ "album_updated_setting_description": "Modtag en emailnotifikation når et delt album får nye mediefiler", "album_user_left": "Forlod {album}", "album_user_removed": "Fjernede {user}", + "album_with_link_access": "Lad alle med linket se billeder og personer i dette album.", "albums": "Albummer", "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Albummer}}", "all": "Alt", @@ -378,7 +394,17 @@ "archive_size": "Arkiv størelse", "archive_size_description": "Konfigurer arkivstørrelsen for downloads (i GiB)", "archived": "Arkiveret", + "are_these_the_same_person": "Er disse den samme person?", + "are_you_sure_to_do_this": "Er du sikker på, at du vil gøre det her?", + "asset_added_to_album": "Tilføjet til album", + "asset_adding_to_album": "Tilføjer til album...", + "asset_description_updated": "Mediefilsbeskrivelse er blevet opdateret", + "asset_filename_is_offline": "Mediefil {filename} er offline", "asset_offline": "Mediefil offline", + "asset_offline_description": "Denne eksterne mediefil kan ikke længere findes på drevet. Kontakt venligst din Immich-administrator for hjælp.", + "asset_skipped": "Sprunget over", + "asset_uploaded": "Uploaded", + "asset_uploading": "Uploader...", "assets": "elementer", "authorized_devices": "Tilladte enheder", "back": "Tilbage", @@ -389,6 +415,7 @@ "build_image": "Byggefil", "bulk_delete_duplicates_confirmation": "Er du sikker på, at du vil slette alle {count, plural, one {# duplicate asset} other {# duplicate assets}}? Dette vil beholde den største fil i hver gruppe og slette alle dubletter. Denne handling kan ikke fortrydes!", "bulk_keep_duplicates_confirmation": "Er du sikker på, at du vil beholde {count, plural, one {# duplicate asset} other {# duplicate assets}}? Dette vil løse alle dubletgrupper uden at slette noget.", + "buy": "Køb Immich", "camera": "Kamera", "camera_brand": "Kameramærke", "camera_model": "Kameramodel", @@ -425,7 +452,9 @@ "collapse_all": "Klap alle sammen", "color": "Farve", "color_theme": "Farvetema", + "comment_deleted": "Kommentar slettet", "comment_options": "Kommentarindstillinger", + "comments_and_likes": "Kommentarer og likes", "comments_are_disabled": "Kommentarer er slået fra", "confirm": "Bekræft", "confirm_admin_password": "Bekræft administratoradgangskode", @@ -481,6 +510,7 @@ "direction": "Retning", "disabled": "Deaktiveret", "disallow_edits": "Deaktivér redigeringer", + "discord": "Discord", "discover": "Opdag", "dismiss_all_errors": "Afvis alle fejl", "dismiss_error": "Afvis fejl", @@ -488,6 +518,7 @@ "display_order": "Display-rækkefølge", "display_original_photos": "Vis originale billeder", "display_original_photos_setting_description": "Foretræk at vise det originale billede frem for miniaturebilleder når den originale fil er web-kompatibelt. Dette kan gøre billedvisning langsommere.", + "do_not_show_again": "Vis ikke denne besked igen", "done": "Færdig", "download": "Hent", "download_settings": "Download", @@ -502,6 +533,7 @@ "months": "{months, plural, one {måned} other {{months, number} måneder}}", "years": "{years, plural, one {år} other {{years, number} år}}" }, + "edit": "Rediger", "edit_album": "Redigér album", "edit_avatar": "Redigér avatar", "edit_date": "Redigér dato", @@ -519,6 +551,9 @@ "edit_user": "Redigér bruger", "edited": "Redigeret", "editor": "Redaktør", + "editor_close_without_save_prompt": "Ændringerne vil ikke blive gemt", + "editor_close_without_save_title": "Luk editor?", + "editor_crop_tool_h2_rotation": "Rotation", "email": "E-mail", "empty": "", "empty_album": "Tomt album", @@ -528,12 +563,30 @@ "end_date": "Slutdato", "error": "Fejl", "error_loading_image": "Fejl ved indlæsning af billede", + "error_title": "Fejl - Noget gik galt", "errors": { + "cannot_navigate_next_asset": "Kan ikke navigere til næste mediefil", + "cannot_navigate_previous_asset": "Kan ikke navigere til forrige mediefil", "cleared_jobs": "Ryddede opgaver for: {job}", + "error_adding_assets_to_album": "Fejl i tilføjelse af mediefiler til album", + "error_adding_users_to_album": "Fejl i tilføjelse af brugere til album", + "error_deleting_shared_user": "Fejl i sletning af delt bruger", + "error_downloading": "Fejl i download af {filename}", + "error_hiding_buy_button": "Fejl i skjulning af køb-knap", + "error_removing_assets_from_album": "Fejl i fjernelse af mediefiler fra album. Tjek konsol for flere detaljer", "exclusion_pattern_already_exists": "Denne udelukkelsesmønster findes allerede.", "failed_job_command": "Kommando {command} slog fejl for opgave: {job}", + "failed_to_create_album": "Oprettelse af album mislykkedes", + "failed_to_create_shared_link": "Oprettelse af delt link mislykkedes", + "failed_to_edit_shared_link": "Redigering af delt link mislykkedes", + "failed_to_load_asset": "Indlæsning af mediefil mislykkedes", + "failed_to_load_assets": "Indlæsning af mediefiler mislykkedes", + "failed_to_load_people": "Indlæsning af personer mislykkedes", + "failed_to_remove_product_key": "Fjernelse af produktnøgle mislykkedes", "import_path_already_exists": "Denne importsti findes allerede.", + "incorrect_email_or_password": "Forkert email eller kodeord", "paths_validation_failed": "{paths, plural, one {# sti} other {# stier}} slog fejl ved validering", + "profile_picture_transparent_pixels": "Profilbilleder kan ikke have gennemsigtige pixels. Zoom venligst ind og/eller flyt billedet.", "quota_higher_than_disk_size": "Du har sat en kvote der er større end disken", "repair_unable_to_check_items": "Kunne ikke tjekke {count, select, one {element} other {elementer}}", "unable_to_add_album_users": "Ikke i stand til at tilføje brugere til album", @@ -554,6 +607,7 @@ "unable_to_create_user": "Ikke i stand til at oprette bruger", "unable_to_delete_album": "Ikke i stand til at slette album", "unable_to_delete_asset": "Kan ikke slette mediefil", + "unable_to_delete_assets": "Fejl i sletning af mediefiler", "unable_to_delete_exclusion_pattern": "Kunne ikke slette udelukkelsesmønster", "unable_to_delete_import_path": "Kunne ikke slette importsti", "unable_to_delete_shared_link": "Kunne ikke slette delt link", @@ -606,10 +660,12 @@ "every_night_at_midnight": "", "every_night_at_twoam": "", "every_six_hours": "", + "exif": "Exif", "exit_slideshow": "Forlad slideshow", "expand_all": "Udvid alle", "expire_after": "Udløb efter", "expired": "Udløbet", + "expires_date": "Udløber {date}", "explore": "Udforsk", "export": "Eksportér", "export_as_json": "Eksportér som JSON", @@ -623,6 +679,8 @@ "feature": "", "feature_photo_updated": "Forsidebillede uploadet", "featurecollection": "", + "features": "Funktioner", + "features_setting_description": "Administrer app-funktioner", "file_name": "Filnavn", "file_name_or_extension": "Filnavn eller filtype", "filename": "Filnavn", @@ -631,6 +689,7 @@ "filter_people": "Filtrér personer", "find_them_fast": "Find dem hurtigt med søgning via navn", "fix_incorrect_match": "Fix forkert match", + "folders": "Mapper", "force_re-scan_library_files": "Tving genskanning af alle biblioteksfiler", "forward": "Fremad", "general": "Generel", @@ -640,10 +699,15 @@ "go_to_search": "Gå til søgning", "go_to_share_page": "Gå til delingsside", "group_albums_by": "Gruppér albummer efter...", + "group_no": "Ingen gruppering", "has_quota": "Har kvote", + "hi_user": "Hej {name} ({email})", + "hide_all_people": "Skjul alle personer", "hide_gallery": "Gem galleri", + "hide_named_person": "Skjul person {name}", "hide_password": "Gem adgangskode", "hide_person": "Gem person", + "hide_unnamed_people": "Skjul unavngivne personer", "host": "Host", "hour": "Time", "image": "Billede", @@ -669,10 +733,12 @@ "job_settings_description": "", "jobs": "Opgaver", "keep": "Behold", + "keep_all": "Behold alle", "keyboard_shortcuts": "Tastaturgenveje", "language": "Sprog", "language_setting_description": "Vælg dit foretrukne sprog", "last_seen": "Sidst set", + "latest_version": "Seneste version", "leave": "Forlad", "let_others_respond": "Lad andre svare", "level": "Niveau", @@ -687,7 +753,12 @@ "loading_search_results_failed": "At loade søgeresultater slog fejl", "log_out": "Log ud", "log_out_all_devices": "Log ud af alle enheder", + "logged_out_all_devices": "Logget ud af alle enheder", + "logged_out_device": "Logget ud af enhed", + "login": "Log ind", "login_has_been_disabled": "Login er blevet deaktiveret.", + "logout_all_device_confirmation": "Er du sikker på, at du vil logge ud af alle enheder?", + "logout_this_device_confirmation": "Er du sikker på, at du vil logge denne enhed ud?", "look": "Kig", "loop_videos": "Gentag videoer", "loop_videos_description": "Aktivér for at genafspille videoer automatisk i detaljeret visning.", @@ -721,15 +792,19 @@ "name": "Navn", "name_or_nickname": "Navn eller kælenavn", "never": "aldrig", + "new_album": "Nyt album", "new_api_key": "Ny API-nøgle", "new_password": "Ny adgangskode", "new_person": "Ny person", "new_user_created": "Ny bruger oprettet", + "new_version_available": "NY VERSION TILGÆNGELIG", "newest_first": "Nyeste først", "next": "Næste", "next_memory": "Næste minde", "no": "Nej", "no_albums_message": "Opret et album for at organisere dine billeder og videoer", + "no_albums_with_name_yet": "Det ser ud til, at du ikke har noget album med dette navn endnu.", + "no_albums_yet": "Det ser ud til, at du ikke har nogen album endnu.", "no_archived_assets_message": "Arkivér billeder og fotos for at gemme dem væk fra dit Billed-view", "no_assets_message": "KLIK FOR AT UPLOADE DIT FØRSTE BILLEDE", "no_duplicates_found": "Ingen duplikater fundet.", @@ -740,6 +815,7 @@ "no_name": "Intet navn", "no_places": "Ingen steder", "no_results": "Ingen resultater", + "no_results_description": "Prøv et synonym eller et mere generelt søgeord", "no_shared_albums_message": "Opret et album for at dele billeder og videoer med personer i dit netværk", "not_in_any_album": "Ikke i noget album", "note_apply_storage_label_to_previously_uploaded assets": "Bemærk: For at anvende Lagringsmærkat på tidligere uploadede medier, kør", @@ -749,17 +825,24 @@ "notifications": "Notifikationer", "notifications_setting_description": "Administrér notifikationer", "oauth": "OAuth", + "official_immich_resources": "Officielle Immich-ressourcer", "offline": "Offline", "offline_paths": "Offline-stier", "offline_paths_description": "Disse resultater kan være på grund af manuel sletning af filer, som ikke er en del af et eksternt bibliotek.", "ok": "Ok", "oldest_first": "Ældste først", + "onboarding_privacy_description": "Følgende (valgfrie) funktioner er afhængige af eksterne tjenester, og kan til enhver tid deaktiveres i administrationsindstillingerne.", + "onboarding_welcome_user": "Velkommen, {user}", "online": "Online", "only_favorites": "Kun favoritter", "only_refreshes_modified_files": "Kun genopfrisk ændrede filer", + "open_in_map_view": "Åben i kortvisning", + "open_in_openstreetmap": "Åben i OpenStreetMap", "open_the_search_filters": "Åbn søgefiltre", "options": "Handlinger", + "or": "eller", "organize_your_library": "Organisér dit bibliotek", + "original": "original", "other": "Andet", "other_devices": "Andre enheder", "other_variables": "Andre variable", @@ -792,6 +875,7 @@ "permanent_deletion_warning_setting_description": "Vis en advarsel, når medier slettes permanent", "permanently_delete": "Slet permanent", "permanently_deleted_asset": "Permanent slettet medie", + "person": "Person", "photos": "Billeder", "photos_count": "{count, plural, one {{count, number} Billede} other {{count, number} Billeder}}", "photos_from_previous_years": "Billeder fra tidligere år", diff --git a/i18n/de.json b/i18n/de.json index f92da539a6605..a22e4f2e15a38 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -34,6 +34,11 @@ "authentication_settings_disable_all": "Bist du sicher, dass du alle Anmeldemethoden deaktivieren willst? Die Anmeldung wird vollständig deaktiviert.", "authentication_settings_reenable": "Nutze einen Server-Befehl zur Reaktivierung.", "background_task_job": "Hintergrund-Aufgaben", + "backup_database": "Datenbank sichern", + "backup_database_enable_description": "Sicherung der Datenbank aktivieren", + "backup_keep_last_amount": "Anzahl der aufzubewahrenden früheren Sicherungen", + "backup_settings": "Datensicherungs-Einstellungen", + "backup_settings_description": "Datensicherungs-Einstellungen verwalten", "check_all": "Alle überprüfen", "cleared_jobs": "Folgende Aufgaben zurückgesetzt: {job}", "config_set_by_file": "Ist derzeit in einer Konfigurationsdatei festgelegt", @@ -43,6 +48,9 @@ "confirm_reprocess_all_faces": "Bist du sicher, dass du alle Gesichter erneut verarbeiten möchtest? Dies löscht auch alle bereits benannten Personen.", "confirm_user_password_reset": "Bist du sicher, dass du das Passwort für {user} zurücksetzen möchtest?", "create_job": "Aufgabe erstellen", + "cron_expression": "Cron-Ausdruck", + "cron_expression_description": "Stellen Sie das Scanintervall im Cron-Format ein. Weitere Informationen finden Sie beispielsweise unter Crontab Guru", + "cron_expression_presets": "Cron-Ausdruck-Vorlagen", "crontab_guru": "Crontab Guru", "disable_login": "Login deaktvieren", "disabled": "Deaktiviert", @@ -592,7 +600,7 @@ "editor_close_without_save_prompt": "Die Änderungen werden nicht gespeichert", "editor_close_without_save_title": "Editor schließen?", "editor_crop_tool_h2_aspect_ratios": "Seitenverhältnisse", - "editor_crop_tool_h2_rotation": "Rotation", + "editor_crop_tool_h2_rotation": "Drehung", "email": "E-Mail", "empty": "Leer", "empty_album": "Leeres Album", @@ -1187,7 +1195,7 @@ "select_library_owner": "Bibliotheksbesitzer auswählen", "select_new_face": "Neues Gesicht auswählen", "select_photos": "Fotos auswählen", - "select_trash_all": "Alle Löschen", + "select_trash_all": "Alle löschen", "selected": "Ausgewählt", "selected_count": "{count, plural, other {# ausgewählt}}", "send_message": "Nachricht senden", @@ -1304,21 +1312,21 @@ "to_login": "Anmelden", "to_parent": "Gehe zum Übergeordneten", "to_root": "Zur Wurzel", - "to_trash": "Zum Papierkorb verschieben", + "to_trash": "In den Papierkorb verschieben", "toggle_settings": "Einstellungen umschalten", "toggle_theme": "Dunkles Theme umschalten", "toggle_visibility": "Sichtbarkeit umschalten", "total_usage": "Gesamtnutzung", "trash": "Papierkorb", - "trash_all": "Alles in den Papierkorb", + "trash_all": "Alle löschen", "trash_count": "Papierkorb {count, number}", "trash_delete_asset": "Datei löschen/in den Papierkorb verschieben", "trash_no_results_message": "Gelöschte Fotos und Videos werden hier angezeigt.", "trashed_items_will_be_permanently_deleted_after": "Gelöschte Objekte werden nach {days, plural, one {# Tag} other {# Tagen}} endgültig gelöscht.", "type": "Typ", - "unarchive": "Unarchivieren", + "unarchive": "Entarchivieren", "unarchived": "Unarchiviert", - "unarchived_count": "{count, plural, other {# Entarchiviert}}", + "unarchived_count": "{count, plural, other {# entarchiviert}}", "unfavorite": "Entfavorisieren", "unhide_person": "Person einblenden", "unknown": "Unbekannt", @@ -1330,7 +1338,7 @@ "unlinked_oauth_account": "Nicht verknüpftes OAuth-Konto", "unnamed_album": "Unbenanntes Album", "unnamed_album_delete_confirmation": "Bist du sicher, dass du dieses Album löschen willst?", - "unnamed_share": "Unbenannte Teilung", + "unnamed_share": "Unbenannte Freigabe", "unsaved_change": "Ungespeicherte Änderung", "unselect_all": "Alles abwählen", "unselect_all_duplicates": "Alle Duplikate abwählen", @@ -1342,7 +1350,7 @@ "updated_password": "Passwort aktualisiert", "upload": "Hochladen", "upload_concurrency": "Parallelität beim Hochladen", - "upload_errors": "Hochladen abgeschlossen mit {count, plural, one {# Fehler} other {# Fehlern}}, aktualisiere die Seite, um neu hochgeladene Dateien zu sehen.", + "upload_errors": "Hochladen mit {count, plural, one {# Fehler} other {# Fehlern}} abgeschlossen, aktualisiere die Seite, um neu hochgeladene Dateien zu sehen.", "upload_progress": "{remaining, number} verbleibend - {processed, number}/{total, number} verarbeitet", "upload_skipped_duplicates": "{count, plural, one {# doppelte Datei} other {# doppelte Dateien}} ausgelassen", "upload_status_duplicates": "Duplikate", @@ -1387,7 +1395,7 @@ "view_stack": "Stapel anzeigen", "viewer": "Zuschauer", "visibility_changed": "Sichtbarkeit für {count, plural, one {# Person} other {# Personen}} geändert", - "waiting": "Warte", + "waiting": "Wartend", "warning": "Warnung", "week": "Woche", "welcome": "Willkommen", diff --git a/i18n/el.json b/i18n/el.json index b29e95b9c4cc8..f656b14df5277 100644 --- a/i18n/el.json +++ b/i18n/el.json @@ -5,7 +5,7 @@ "acknowledge": "Έλαβα γνώση", "action": "Ενέργεια", "actions": "Ενέργειες", - "active": "Ενεργά", + "active": "Ενεργές", "activity": "Δραστηριότητα", "activity_changed": "Η δραστηριότητα είναι {enabled, select, true {ενεργοποιημένη} other {απενεργοποιημένη}}", "add": "Προσθήκη", @@ -13,52 +13,52 @@ "add_a_location": "Προσθήκη μιας τοποθεσίας", "add_a_name": "Προσθήκη ονόματος", "add_a_title": "Προσθήκη τίτλου", - "add_exclusion_pattern": "Προσθήκη προτύπου αποκλεισμού", - "add_import_path": "Προσθήκη διαδρομής εισαγωγής", + "add_exclusion_pattern": "Προσθήκη μοτίβου αποκλεισμού", + "add_import_path": "Προσθήκη μονοπατιού εισαγωγής", "add_location": "Προσθήκη τοποθεσίας", "add_more_users": "Προσθήκη επιπλέον χρηστών", - "add_partner": "Προσθήκη συνεργάτη", - "add_path": "Προσθήκη διαδρομής", + "add_partner": "Προσθήκη συντρόφου", + "add_path": "Προσθήκη μονοπατιού", "add_photos": "Προσθήκη φωτογραφιών", "add_to": "Προσθήκη σε...", "add_to_album": "Προσθήκη σε άλμπουμ", "add_to_shared_album": "Προσθήκη σε κοινόχρηστο άλμπουμ", - "added_to_archive": "Αρχειοθέτηση", - "added_to_favorites": "Προστέθηκε στα αγαπημένα", + "added_to_archive": "Έγινε αρχειοθέτηση", + "added_to_favorites": "Έγινε προσθήκη στα αγαπημένα", "added_to_favorites_count": "Προστέθηκαν {count, number} στα αγαπημένα", "admin": { - "add_exclusion_pattern_description": "Προσθέστε πρότυπα αποκλεισμού. Υποστηρίζεται η επιλογή πολλών με *, **, και ?. Για να αγνοηθούν όλα τα αρχεία σε έναν φάκελο με το όνομα \"Raw\", χρησιμοποιήστε \"**/Raw/**\". Για να αγνοηθούν όλα τα αρχεία με κατάληξη \".tif\", χρησιμοποιήστε \"**/*.tif\". Για να αγνοηθεί μία απόλυτη διαδρομή, χρησιμοποιήστε \"/path/to/ignore/**\".", - "asset_offline_description": "Αυτό το στοιχείο εξωτερικής βιβλιοθήκης δε βρίσκεται πλέον στο δίσκο και έχει μεταφερθεί στα σκουπίδια. Εάν το αρχείο έχει μετακινηθεί εντός της βιβλιοθήκης, ελέγξτε το χρονοδιάγραμμα σας για το νέο αντίστοιχο στοιχείο. Για να επαναφέρετε αυτό το στοιχείο, βεβαιωθείτε ότι το παρακάτω μονοπάτι αρχείου είναι προσβάσιμο από το Immich και ότι μπορεί να σαρώσει τη βιβλιοθήκη.", + "add_exclusion_pattern_description": "Προσθέστε μοτίβα αποκλεισμού. Υποστηρίζεται η επιλογή πολλών με *, **, και ?. Για να αγνοηθούν όλα τα αρχεία σε έναν φάκελο με το όνομα \"Raw\", χρησιμοποιήστε \"**/Raw/**\". Για να αγνοηθούν όλα τα αρχεία με κατάληξη \".tif\", χρησιμοποιήστε \"**/*.tif\". Για να αγνοηθεί ένα απόλυτο μονοπάτι, χρησιμοποιήστε \"/path/to/ignore/**\".", + "asset_offline_description": "Αυτό το στοιχείο εξωτερικής βιβλιοθήκης δε βρίσκεται πλέον στο δίσκο και έχει μεταφερθεί στα απορρίμματα. Εάν το αρχείο έχει μετακινηθεί εντός της βιβλιοθήκης, ελέγξτε το χρονολόγιο φωτογραφιών σας για το νέο αντίστοιχο στοιχείο. Για να επαναφέρετε αυτό το στοιχείο, βεβαιωθείτε ότι το παρακάτω μονοπάτι αρχείου είναι προσβάσιμο από το Immich και σαρώστε τη βιβλιοθήκη.", "authentication_settings": "Ρυθμίσεις ελέγχου ταυτότητας", "authentication_settings_description": "Διαχείριση κωδικού πρόσβασης, OAuth και άλλες ρυθμίσεις ελέγχου ταυτότητας", "authentication_settings_disable_all": "Είστε βέβαιοι ότι θέλετε να απενεργοποιήσετε όλες τις μεθόδους σύνδεσης; Η σύνδεση θα απενεργοποιηθεί πλήρως.", - "authentication_settings_reenable": "Για να επαναενεργοποιηθεί, χρησιμοποιήστε μία Server Command.", + "authentication_settings_reenable": "Για επαναενεργοποίηση, χρησιμοποιήστε μία Εντολή Διακομηστή.", "background_task_job": "Εργασίες Παρασκηνίου", "check_all": "Έλεγχος Όλων", - "cleared_jobs": "Εκκαθάριση εργασιών για: {job}", - "config_set_by_file": "Η διαμόρφωση γίνεται προς το παρόν από ένα αρχείο config", + "cleared_jobs": "Εκκαθαρίστηκαν εργασίες για: {job}", + "config_set_by_file": "Η παραμετροποίηση γίνεται προς το παρόν από ένα αρχείο παραμέτρων", "confirm_delete_library": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τη βιβλιοθήκη {library};", - "confirm_delete_library_assets": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτή τη βιβλιοθήκη; Αυτό θα διαγράψει τα {count, plural, one {# contained asset} other {all # contained assets}} από το Immich και δεν μπορεί να αναιρεθεί. Τα αρχεία θα παραμείνουν στον δίσκο.", + "confirm_delete_library_assets": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή τη βιβλιοθήκη; Αυτό θα διαγράψει τα {count, plural, one {# contained asset} other {all # contained assets}} από το Immich και δεν μπορεί να αναιρεθεί. Τα αρχεία θα παραμείνουν στον δίσκο.", "confirm_email_below": "Για επιβεβαίωση, πληκτρολογήστε \"{email}\" παρακάτω", "confirm_reprocess_all_faces": "Είστε βέβαιοι ότι θέλετε να επεξεργαστείτε ξανά όλα τα πρόσωπα; Αυτό θα διαγράψει επίσης άτομα με όνομα.", "confirm_user_password_reset": "Είστε βέβαιοι ότι θέλετε να επαναφέρετε τον κωδικό πρόσβασης του χρήστη {user};", "create_job": "Δημιουργία εργασίας", - "disable_login": "Απενεργοποίηση σύνδεσης κατά την είσοδο", - "duplicate_detection_job_description": "Εκτελέστε τη εκμάθηση μηχανής σε στοιχεία για να εντοπίσετε παρόμοιες εικόνες. Βασίζεται στην Έξυπνη Αναζήτηση", - "exclusion_pattern_description": "Τα πρότυπα αποκλεισμού σας επιτρέπουν να αγνοείται αρχεία κκαι φακέλους όσο σαρώνεται η βιβλιοθήκη. Αυτό είναι χρήσιμο εάν εχετε φακέλους που περιέχουν αρχεία που δεν θέλετε να εισαγάγετε, όπως αρχεία RAW.", + "disable_login": "Απενεργοποίηση σύνδεσης", + "duplicate_detection_job_description": "Εκτελέστε μηχανική μάθηση σε στοιχεία για να εντοπίσετε παρόμοιες εικόνες. Βασίζεται στην Έξυπνη Αναζήτηση", + "exclusion_pattern_description": "Τα μοτίβα αποκλεισμού σας επιτρέπουν να αγνοείται αρχεία και φακέλους κατά τη σάρωση της βιβλιοθήκης σας. Αυτό είναι χρήσιμο εάν εχετε φακέλους που περιέχουν αρχεία που δεν θέλετε να εισάγετε, όπως αρχεία RAW.", "external_library_created_at": "Εξωτερική βιβλιοθήκη (δημιουργήθηκε {date})", "external_library_management": "Διαχείριση Εξωτερικών Βιβλιοθηκών", - "face_detection": "Αναγνώριση προσώπου", - "face_detection_description": "Εντοπίστε τα πρόσωπα σε στοιχεία χρησιμοποιώντας μηχανική εκμάθηση. Για βίντεο, λαμβάνεται υπόψη μόνο η μικρογραφία. Η επιλογή \"Ανανέωση\" επεξεργάζεται εκ νέου όλα τα στοιχεία και η επιλογή \"Επαναφορά\", επιπλέον επαναφέρει ολα τα δεδομένα προσώπου. Η επιλογή \"Όσα Λείπουν\" προσθέτει στην ουρά στοιχεία που δεν έχουν υποστεί ακόμη επεξεργασία. Τα πρόσωπα που έχουν εντοπιστεί θα μπουν στην ουρά για την Αναγνώριση Προσώπου μετά την ολοκλήρωση της Ανίχνευσης Προσώπου, ομαδοποιώντας τα σε υπάρχοντα ή νέα άτομα.", - "facial_recognition_job_description": "Ομαδοποιήστε εντοπισμένα πρόσωπα σε άτομα. Αυτό το βήμα εκτελείται αφού ολοκληρωθεί η Ανίχνευση προσώπου. Η επιλογή \"Επαναφορά\" ομαδοποιεί εκ νέου όλα τα πρόσωπα. Η επιλογή \"Όσα Λείπουν\" ομαδοποιεί πρόσωπα που δεν έχουν αντιστοιχηθεί σε κάποιο άτομο.", - "failed_job_command": "Η Εντολή {command} απέτυχε για την εργασία: {job}", + "face_detection": "Ανίχνευση προσώπου", + "face_detection_description": "Ανιχνεύστε τα πρόσωπα σε στοιχεία χρησιμοποιώντας μηχανική μάθηση. Για βίντεο, λαμβάνεται υπόψη μόνο η μικρογραφία. Η επιλογή \"Ανανέωση\" επεξεργάζεται εκ νέου όλα τα στοιχεία. Η επιλογή \"Επαναφορά\", επιπλέον εκκαθαρίζει όλα τα δεδομένα προσώπου. Η επιλογή \"Ελλείποντα\" προσθέτει στην ουρά στοιχεία που δεν έχουν υποστεί ακόμη επεξεργασία. Τα πρόσωπα που έχουν εντοπιστεί θα μπουν στην ουρά για την Αναγνώριση Προσώπου μετά την ολοκλήρωση της Ανίχνευσης Προσώπου, ομαδοποιώντας τα σε υπάρχοντα ή νέα άτομα.", + "facial_recognition_job_description": "Ομαδοποιήστε ανιχνευμένα πρόσωπα σε άτομα. Αυτό το βήμα εκτελείται αφού ολοκληρωθεί η Ανίχνευση Προσώπου. Η επιλογή \"Επαναφορά\" ομαδοποιεί εκ νέου όλα τα πρόσωπα. Η επιλογή \"Ελλείποντα\" βάζει στην ουρά για ομαδοποίηση πρόσωπα που δεν έχουν αντιστοιχηθεί σε κάποιο άτομο.", + "failed_job_command": "Η εντολή {command} απέτυχε για την εργασία: {job}", "force_delete_user_warning": "ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα αφαιρέσει άμεσα το χρήστη και όλα τα στοιχεία. Αυτό δεν μπορεί να αναιρεθεί και τα αρχεία δεν μπορούν να ανακτηθούν.", "forcing_refresh_library_files": "Επιβολή ανανέωσης όλων των αρχείων της βιβλιοθήκης", "image_format": "Μορφή", "image_format_description": "Η μορφή WebP παράγει μικρότερα αρχεία από τη μορφή JPEG, αλλά είναι πιο αργή στην κωδικοποίηση.", "image_prefer_embedded_preview": "Προτίμηση ενσωματωμένης προεπισκόπησης", - "image_prefer_embedded_preview_setting_description": "Χρησιμοποιήστε ενσωματωμένες προεπισκοπίσεις για εικόνες RAW ως εισαγωγή στην επεξεργασία εικόνας όταν είναι διαθέσιμο. Αυτό μπορεί να δημιουργήσει πιο ακριβή χρωματα για κάποιες εικόνες, αλλά η ποιότητα των προεπισκοπίσεων εξαρτάται από την κάμερα και ενδέχεται να υπάρχουν περισσότερα μπιμπίκια λόγω συμπίεσης.", - "image_prefer_wide_gamut": "Προτίμηση ευρείας γκάμας", + "image_prefer_embedded_preview_setting_description": "Χρησιμοποιήστε ενσωματωμένες προεπισκοπίσεις για εικόνες RAW ως είσοδο για την επεξεργασία εικόνας όταν είναι διαθέσιμες. Αυτό μπορεί να δημιουργήσει πιο ακριβή χρώματα για κάποιες εικόνες, αλλά η ποιότητα των προεπισκοπίσεων εξαρτάται από την κάμερα και ενδέχεται να υπάρχουν περισσότερες αλλοιώσεις στην εικόνα λόγω συμπίεσης.", + "image_prefer_wide_gamut": "Προτίμηση ευρέος φάσματος", "image_prefer_wide_gamut_setting_description": "Χρησιμοποιήστε Display P3 για τις μικρογραφίες. Αυτό διατηρεί την ζωντάνια των χρωμάτων σε εικόνες μεγάλου χρωματικού εύρους, αλλά ενδέχεται να εμφανίζονται αλλιώς σε παλαιότερες συσκευές με παλαιότερες εκδόσεις περιηγητών. Οι εικόνες sRGB μένουν ως έχουν για να αποφευχθούν χρωματικές αλλαγές.", "image_preview_description": "Μεσαίου μεγέθους εικόνες, χωρίς μεταδεδομένα, οι οποίες χρησιμοποιύνται όταν γίνεται θέαση ενός αντικειμένου και για μηχανική μάθηση", "image_preview_format": "Μορφή προεπισκόπησης", @@ -644,7 +644,7 @@ "warning": "Προειδοποίηση", "week": "Εβδομάδα", "welcome": "Καλωσορίσατε", - "welcome_to_immich": "Καλωσορίσατε στο immich", + "welcome_to_immich": "Καλωσορίσατε στο Ιmmich", "year": "Έτος", "years_ago": "πριν από {years, plural, one {# χρόνο} other {# χρόνια}}", "yes": "Ναι", diff --git a/i18n/en.json b/i18n/en.json index 7a1ada94a65c4..d56595c82652c 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -34,6 +34,11 @@ "authentication_settings_disable_all": "Are you sure you want to disable all login methods? Login will be completely disabled.", "authentication_settings_reenable": "To re-enable, use a Server Command.", "background_task_job": "Background Tasks", + "backup_database": "Backup Database", + "backup_database_enable_description": "Enable database backups", + "backup_keep_last_amount": "Amount of previous backups to keep", + "backup_settings": "Backup Settings", + "backup_settings_description": "Manage database backup settings", "check_all": "Check All", "cleared_jobs": "Cleared jobs for: {job}", "config_set_by_file": "Config is currently set by a config file", @@ -43,6 +48,9 @@ "confirm_reprocess_all_faces": "Are you sure you want to reprocess all faces? This will also clear named people.", "confirm_user_password_reset": "Are you sure you want to reset {user}'s password?", "create_job": "Create job", + "cron_expression": "Cron expression", + "cron_expression_description": "Set the scanning interval using the cron format. For more information please refer to e.g. Crontab Guru", + "cron_expression_presets": "Cron expression presets", "disable_login": "Disable login", "duplicate_detection_job_description": "Run machine learning on assets to detect similar images. Relies on Smart Search", "exclusion_pattern_description": "Exclusion patterns lets you ignore files and folders when scanning your library. This is useful if you have folders that contain files you don't want to import, such as RAW files.", @@ -80,9 +88,6 @@ "jobs_delayed": "{jobCount, plural, other {# delayed}}", "jobs_failed": "{jobCount, plural, other {# failed}}", "library_created": "Created library: {library}", - "library_cron_expression": "Cron expression", - "library_cron_expression_description": "Set the scanning interval using the cron format. For more information please refer to e.g. Crontab Guru", - "library_cron_expression_presets": "Cron expression presets", "library_deleted": "Library deleted", "library_import_path_description": "Specify a folder to import. This folder, including subfolders, will be scanned for images and videos.", "library_scanning": "Periodic Scanning", @@ -300,8 +305,6 @@ "transcoding_threads_description": "Higher values lead to faster encoding, but leave less room for the server to process other tasks while active. This value should not be more than the number of CPU cores. Maximizes utilization if set to 0.", "transcoding_tone_mapping": "Tone-mapping", "transcoding_tone_mapping_description": "Attempts to preserve the appearance of HDR videos when converted to SDR. Each algorithm makes different tradeoffs for color, detail and brightness. Hable preserves detail, Mobius preserves color, and Reinhard preserves brightness.", - "transcoding_tone_mapping_npl": "Tone-mapping NPL", - "transcoding_tone_mapping_npl_description": "Colors will be adjusted to look normal for a display of this brightness. Counter-intuitively, lower values increase the brightness of the video and vice versa since it compensates for the brightness of the display. 0 sets this value automatically.", "transcoding_transcode_policy": "Transcode policy", "transcoding_transcode_policy_description": "Policy for when a video should be transcoded. HDR videos will always be transcoded (except if transcoding is disabled).", "transcoding_two_pass_encoding": "Two-pass encoding", @@ -462,6 +465,7 @@ "confirm": "Confirm", "confirm_admin_password": "Confirm Admin Password", "confirm_delete_shared_link": "Are you sure you want to delete this shared link?", + "confirm_keep_this_delete_others": "All other assets in the stack will be deleted except for this asset. Are you sure you want to continue?", "confirm_password": "Confirm password", "contain": "Contain", "context": "Context", @@ -511,6 +515,7 @@ "delete_key": "Delete key", "delete_library": "Delete Library", "delete_link": "Delete link", + "delete_others": "Delete others", "delete_shared_link": "Delete shared link", "delete_tag": "Delete tag", "delete_tag_confirmation_prompt": "Are you sure you want to delete {tagName} tag?", @@ -607,6 +612,7 @@ "failed_to_remove_product_key": "Failed to remove product key", "failed_to_stack_assets": "Failed to stack assets", "failed_to_unstack_assets": "Failed to un-stack assets", + "failed_to_keep_this_delete_others": "Failed to keep this asset and delete the other assets", "import_path_already_exists": "This import path already exists.", "incorrect_email_or_password": "Incorrect email or password", "paths_validation_failed": "{paths, plural, one {# path} other {# paths}} failed validation", @@ -1250,8 +1256,10 @@ "unselect_all_duplicates": "Unselect all duplicates", "unstack": "Un-stack", "unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}", + "keep_this_delete_others": "Keep this, delete others", "untracked_files": "Untracked files", "untracked_files_decription": "These files are not tracked by the application. They can be the results of failed moves, interrupted uploads, or left behind due to a bug", + "kept_this_deleted_others": "Kept this asset and deleted {count, plural, one {# asset} other {# assets}}", "up_next": "Up next", "updated_password": "Updated password", "upload": "Upload", @@ -1280,7 +1288,7 @@ "variables": "Variables", "version": "Version", "version_announcement_closing": "Your friend, Alex", - "version_announcement_message": "Hi friend, there is a new version of the application please take your time to visit the release notes and ensure your docker-compose.yml, and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your application automatically.", + "version_announcement_message": "Hi there! A new version of Immich is available. Please take some time to read the release notes to ensure your setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your Immich instance automatically.", "version_history": "Version History", "version_history_item": "Installed {version} on {date}", "video": "Video", @@ -1302,7 +1310,7 @@ "warning": "Warning", "week": "Week", "welcome": "Welcome", - "welcome_to_immich": "Welcome to immich", + "welcome_to_immich": "Welcome to Immich", "year": "Year", "years_ago": "{years, plural, one {# year} other {# years}} ago", "yes": "Yes", diff --git a/i18n/es.json b/i18n/es.json index 5b26aa577bc7a..52e0384359826 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -7,50 +7,58 @@ "actions": "Acciones", "active": "Activo", "activity": "Actividad", - "activity_changed": "La actividad {enabled, select, true {activada} other {desactivada}}", - "add": "Añadir", - "add_a_description": "Añadir una descripción", - "add_a_location": "Añadir una ubicación", - "add_a_name": "Añadir un nombre", - "add_a_title": "Añadir un título", - "add_exclusion_pattern": "Añadir patrón de exclusión", - "add_import_path": "Añadir ruta de importación", - "add_location": "Añadir ubicación", - "add_more_users": "Añadir más usuarios", - "add_partner": "Añadir invitado", - "add_path": "Añadir ruta", - "add_photos": "Añadir fotos", - "add_to": "Añadir a...", - "add_to_album": "Añadir a un álbum", - "add_to_shared_album": "Añadir a un álbum compartido", - "added_to_archive": "Archivar", - "added_to_favorites": "Añadido a favoritos", - "added_to_favorites_count": "Añadido {count, number} a favoritos", + "activity_changed": "La actividad está {enabled, select, true {activada} other {desactivada}}", + "add": "Agregar", + "add_a_description": "Agregar descripción", + "add_a_location": "Agregar ubicación", + "add_a_name": "Agregar nombre", + "add_a_title": "Agregar título", + "add_exclusion_pattern": "Agregar patrón de exclusión", + "add_import_path": "Agregar ruta de importación", + "add_location": "Agregar ubicación", + "add_more_users": "Agregar más usuarios", + "add_partner": "Agregar invitado", + "add_path": "Agregar ruta", + "add_photos": "Agregar fotos", + "add_to": "Agregar a...", + "add_to_album": "Agregar a un álbum", + "add_to_shared_album": "Agregar a un álbum compartido", + "added_to_archive": "Archivado", + "added_to_favorites": "Agregado a favoritos", + "added_to_favorites_count": "Agregado {count, number} a favoritos", "admin": { - "add_exclusion_pattern_description": "Añade patrones de exclusión. Puedes utilizar los caracteres *, ** y ? (globbing). Para ignorar los archivos en cualquier ruta llamada \"Raw\", utiliza \"**/Raw/**\". Para ignorar todos los archivos que terminan en \".tif\", utiliza \"**/*.tif\". Para ignorar una ruta desde la raíz, utiliza \"/carpeta/a/ignorar/**\".", - "asset_offline_description": "Este elemento de biblioteca externa ya no está en el disco y se ha movido a la papelera. Si el archivo se ha movido dentro de la biblioteca, consulte la línea de tiempo para encontrar un nuevo elemento coincidente. Para restaurar este elemento, asegúrese de que Immich pueda acceder a la ruta del archivo siguiente y busque en la biblioteca.", - "authentication_settings": "Configuración de autenticación", - "authentication_settings_description": "Gestionar clave, Oauth y otros configuraciones de autenticación", - "authentication_settings_disable_all": "¿Estás seguro de que deseas desactivar todos los métodos de inicio de sesión? Se desactivará el inicio de sesión.", - "authentication_settings_reenable": "Para volver a habilitar, utilice un Comando del servidor .", + "add_exclusion_pattern_description": "Agrega patrones de exclusión. Puedes utilizar los caracteres *, ** y ? (globbing). 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/**\".", + "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 de tiempo para el nuevo recurso correspondiente. Para restaurar este recurso, asegúrate de que Immich puede acceder a la siguiente ruta de archivo y escanear la biblioteca.", + "authentication_settings": "Parámetros de autenticación", + "authentication_settings_description": "Gestionar contraseñas, OAuth y otros parámetros de autenticación", + "authentication_settings_disable_all": "¿Está seguro de que deseas desactivar todos los métodos de inicio de sesión? El inicio de sesión se desactivará por completo.", + "authentication_settings_reenable": "Para volver a activarlo, utiliza un Comando del servidor .", "background_task_job": "Tareas en segundo plano", - "check_all": "Comprobar todo", - "cleared_jobs": "Trabajos realizados para: {job}", - "config_set_by_file": "La configuración está fijada actualmente en base a un archivo", + "backup_database": "Respaldar base de datos", + "backup_database_enable_description": "Activar respaldo de base de datos", + "backup_keep_last_amount": "Cantidad de respaldos previos a mantener", + "backup_settings": "Ajustes de respaldo", + "backup_settings_description": "Administrar configuración de respaldo de base de datos", + "check_all": "Verificar todo", + "cleared_jobs": "Trabajos borrados para: {job}", + "config_set_by_file": "La configuración está definida por un archivo de configuración", "confirm_delete_library": "¿Estás seguro de que quieres eliminar la biblioteca {library}?", "confirm_delete_library_assets": "¿Estás seguro de que quieras eliminar esta biblioteca? Esto eliminará los {count, plural, one {# contained asset} other {all # contained assets}} elementos en Immich y no puede deshacerse. Los archivos permanecerán en tu almacenamiento.", - "confirm_email_below": "Para confirmar, escribe \"{email}\" debajo", - "confirm_reprocess_all_faces": "¿Estás seguro de que quieres volver a procesar todas las caras? Esto también eliminará las personas a las que le hayas asignado nombre.", - "confirm_user_password_reset": "¿Estás seguro de que quieres resetear la contraseña de {user}?", + "confirm_email_below": "Para confirmar, escribe \"{email}\" a continuación", + "confirm_reprocess_all_faces": "¿Estás seguro de que deseas reprocesar todas las caras? Esto borrará a todas las personas que nombraste.", + "confirm_user_password_reset": "¿Estás seguro de que quieres restablecer la contraseña de {user}?", "create_job": "Crear trabajo", + "cron_expression": "Expresión CRON", + "cron_expression_description": "Establece el intervalo de escaneo utilizando el formato CRON. Para más información puede consultar, por ejemplo, Crontab Guru", + "cron_expression_presets": "Valores predefinidos de expresión cron", "crontab_guru": "Crontab Guru", "disable_login": "Deshabilitar inicio de sesión", "disabled": "Deshabilitado", - "duplicate_detection_job_description": "Lanza el aprendizaje automático para detectar imágenes similares. Necesita que esté activa la Búsqueda Inteligente", - "exclusion_pattern_description": "Los patrones de exclusión te permiten ignorar archivos y carpetas al escanear tu biblioteca. Esto es útil hay carpetas que contienen archivos que no quieres importar (por ejemplo los ficheros RAW).", - "external_library_created_at": "Biblioteca externa (creado el {date})", + "duplicate_detection_job_description": "Ejecuta aprendizaje automático sobre los activos para detectar imágenes similares. Se basa en la búsqueda inteligente", + "exclusion_pattern_description": "Los patrones de exclusión te permiten ignorar archivos y carpetas al escanear tu biblioteca. Esto es útil si tienes carpetas que contienen archivos que no deseas importar, como archivos RAW.", + "external_library_created_at": "Biblioteca externa (creada el {date})", "external_library_management": "Gestión de bibliotecas externas", - "face_detection": "Detección de caras", + "face_detection": "Detección de rostros", "face_detection_description": "Detectar las caras en los activos mediante aprendizaje automático. En el caso de los vídeos, solo se tiene en cuenta la miniatura. \"Actualizar\" (re)procesar todos los activos. \"Restablecer\" borra además todos los datos de caras actuales. \"Falta\" pone en cola los activos que aún no se han procesado. Los rostros detectados se pondrán en cola para el reconocimiento facial una vez finalizada la detección facial, agrupándolos en personas existentes o nuevas.", "facial_recognition_job_description": "Agrupa los rostros detectados en personas. Este paso se ejecuta una vez finalizada la detección de caras. \"Restablecer\" (re)agrupa todas las caras. \"Falta\" pone en cola los rostros que no tienen asignada una persona.", "failed_job_command": "El comando {command} ha fallado para la tarea: {job}", @@ -278,7 +286,7 @@ "transcoding_audio_codec": "Codec de audio", "transcoding_audio_codec_description": "Opus es la opción de mayor calidad, pero tiene menor compatibilidad con dispositivos o software antiguos.", "transcoding_bitrate_description": "Vídeos con una tasa de bits superior a la máxima o que no están en un formato aceptado", - "transcoding_codecs_learn_more": "Para obtener más información sobre la terminología utilizada aquí, consulte la documentación de FFmpeg para H.264 codec, HEVC codec y VP9 codec.", + "transcoding_codecs_learn_more": "Para obtener más información sobre la terminología utilizada aquí, consulte la documentación de FFmpeg sobre los codecs H.264, HEVC y VP9.", "transcoding_constant_quality_mode": "Modo de calidad constante", "transcoding_constant_quality_mode_description": "ICQ es mejor que CQP, pero algunos dispositivos de aceleración de hardware no admiten este modo. Al configurar esta opción, se preferirá el modo especificado cuando se utilice codificación basada en calidad. NVENC lo ignora porque no es compatible con ICQ.", "transcoding_constant_rate_factor": "Factor de tasa constante (-crf)", @@ -417,7 +425,7 @@ "assets_added_to_name_count": "Añadido {count, plural, one {# asset} other {# assets}} a {hasName, select, true {{name}} other {new album}}", "assets_count": "{count, plural, one {# activo} other {# activos}}", "assets_moved_to_trash": "Se movió {count, plural, one {# activo} other {# activos}} a la papelera", - "assets_moved_to_trash_count": "Movido {count, plural, one {# elemento} other {# elementos}} 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_removed_count": "Eliminado {count, plural, one {# elemento} other {# elementos}}", "assets_restore_confirmation": "¿Estás seguro de que quieres restaurar todos tus activos eliminados? ¡No puede deshacer esta acción! Tenga en cuenta que los archivos sin conexión no se pueden restaurar de esta manera.", @@ -433,7 +441,7 @@ "blurred_background": "Fondo borroso", "bugs_and_feature_requests": "Errores y solicitudes de funciones", "build": "Compilación", - "build_image": "Construir Imagen", + "build_image": "Imagen", "bulk_delete_duplicates_confirmation": "¿Estás seguro de que deseas eliminar de forma masiva {count, plural, one {# elemento duplicado} other {# elementos duplicados}}? Esto mantendrá el activo más grande de cada grupo y eliminará permanentemente todos los demás duplicados. ¡Esta acción no se puede deshacer!", "bulk_keep_duplicates_confirmation": "¿Estas seguro de que desea mantener {count, plural, one {# duplicate asset} other {# duplicate assets}} archivos duplicados? Esto resolverá todos los grupos duplicados sin borrar nada.", "bulk_trash_duplicates_confirmation": "¿Estas seguro de que desea eliminar masivamente {count, plural, one {# duplicate asset} other {# duplicate assets}} archivos duplicados? Esto mantendrá el archivo más grande de cada grupo y eliminará todos los demás duplicados.", @@ -452,7 +460,7 @@ "cant_search_places": "No se pueden buscar lugares", "change_date": "Cambiar fecha", "change_expiration_time": "Cambiar fecha de caducidad", - "change_location": "Cambiar localización", + "change_location": "Cambiar ubicación", "change_name": "Cambiar nombre", "change_name_successfully": "Nombre cambiado correctamente", "change_password": "Cambiar Contraseña", @@ -950,7 +958,7 @@ "no_results": "Sin resultados", "no_results_description": "Pruebe con un sinónimo o una palabra clave más general", "no_shared_albums_message": "Crea un álbum para compartir fotos y vídeos con personas de tu red", - "not_in_any_album": "Nada en ningún álbum", + "not_in_any_album": "Sin álbum", "note_apply_storage_label_to_previously_uploaded assets": "Nota: Para aplicar la etiqueta de almacenamiento a los archivos cargados previamente, ejecute el", "note_unlimited_quota": "Nota: Ingrese 0 para cuota ilimitada", "notes": "Notas", @@ -1023,7 +1031,7 @@ "photo_shared_all_users": "Parece que compartiste tus fotos con todos los usuarios o no tienes ningún usuario con quien compartirlas.", "photos": "Fotos", "photos_and_videos": "Fotos y Videos", - "photos_count": "{count, plural, one {{count, number} foto} other {{count, number} fotos}}", + "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}", "photos_from_previous_years": "Fotos de años anteriores", "pick_a_location": "Elige una ubicación", "place": "Lugar", @@ -1093,8 +1101,8 @@ "recent_searches": "Búsquedas recientes", "refresh": "Actualizar", "refresh_encoded_videos": "Recargar los vídeos codificados", - "refresh_faces": "Refrescar caras", - "refresh_metadata": "Recargar los metadatos", + "refresh_faces": "Actualizar caras", + "refresh_metadata": "Recargar metadatos", "refresh_thumbnails": "Recargar miniaturas", "refreshed": "Recargado", "refreshes_every_file": "Recargar todos los archivos nuevos y existentes", @@ -1183,11 +1191,11 @@ "select_face": "Seleccionar cara", "select_featured_photo": "Seleccionar foto principal", "select_from_computer": "Seleccionar desde el PC", - "select_keep_all": "Mantener toda la selección", + "select_keep_all": "Conservar todo", "select_library_owner": "Seleccionar propietario de la biblioteca", "select_new_face": "Seleccionar nueva cara", "select_photos": "Seleccionar Fotos", - "select_trash_all": "Enviar la selección a la papelera", + "select_trash_all": "Descartar todo", "selected": "Seleccionado", "selected_count": "{count, plural, one {# seleccionado} other {# seleccionados}}", "send_message": "Enviar mensaje", @@ -1195,7 +1203,7 @@ "server": "Servidor", "server_offline": "Servidor desconectado", "server_online": "Servidor en línea", - "server_stats": "Estadísticas Servidor", + "server_stats": "Estadísticas del servidor", "server_version": "Versión del servidor", "set": "Establecer", "set_as_album_cover": "Establecer portada del álbum", @@ -1256,7 +1264,7 @@ "sort_oldest": "Foto más antigua", "sort_recent": "Foto más reciente", "sort_title": "Título", - "source": "Fuente", + "source": "Origen", "stack": "Apilar", "stack_duplicates": "Apilar duplicados", "stack_select_one_photo": "Selecciona una imagen principal para la pila", @@ -1304,14 +1312,14 @@ "to_login": "Iniciar Sesión", "to_parent": "Ir a los padres", "to_root": "Para root", - "to_trash": "Papelera", + "to_trash": "Descartar", "toggle_settings": "Alternar ajustes", "toggle_theme": "Alternar tema oscuro", "toggle_visibility": "Alternar visibilidad", "total_usage": "Uso total", "trash": "Papelera", - "trash_all": "Enviar todo a la papelera", - "trash_count": "Papelera {count, number}", + "trash_all": "Descartar todo", + "trash_count": "Descartar {count, number}", "trash_delete_asset": "Borrar/Eliminar archivo", "trash_no_results_message": "Las fotos y videos que se envíen a la papelera aparecerán aquí.", "trashed_items_will_be_permanently_deleted_after": "Los elementos en la papelera serán eliminados permanentemente tras {days, plural, one {# día} other {# días}}.", @@ -1391,7 +1399,7 @@ "warning": "Advertencia", "week": "Semana", "welcome": "Bienvenido", - "welcome_to_immich": "Bienvenido a immich", + "welcome_to_immich": "Bienvenido a Immich", "year": "Año", "years_ago": "Hace {years, plural, one {# año} other {# años}}", "yes": "Sí", diff --git a/i18n/et.json b/i18n/et.json index 074ddb9f8bb09..1f46a8b8539f3 100644 --- a/i18n/et.json +++ b/i18n/et.json @@ -34,6 +34,11 @@ "authentication_settings_disable_all": "Kas oled kindel, et soovid kõik sisselogimismeetodid välja lülitada? Sisselogimine lülitatakse täielikult välja.", "authentication_settings_reenable": "Et taas lubada, kasuta serveri käsku.", "background_task_job": "Tausttegumid", + "backup_database": "Varunda andmebaas", + "backup_database_enable_description": "Luba andmebaasi varundamine", + "backup_keep_last_amount": "Varukoopiate arv, mida alles hoida", + "backup_settings": "Varundamise seaded", + "backup_settings_description": "Halda andmebaasi varundamise seadeid", "check_all": "Märgi kõik", "cleared_jobs": "Tööted eemaldatud: {job}", "config_set_by_file": "Konfiguratsioon on määratud konfifaili abil", @@ -43,6 +48,9 @@ "confirm_reprocess_all_faces": "Kas oled kindel, et soovid kõik näod uuesti töödelda? See eemaldab kõik nimega isikud.", "confirm_user_password_reset": "Kas oled kindel, et soovid kasutaja {user} parooli lähtestada?", "create_job": "Lisa tööde", + "cron_expression": "Cron avaldis", + "cron_expression_description": "Sea skaneerimise intervall cron formaadis. Rohkema info jaoks vaata nt. Crontab Guru", + "cron_expression_presets": "Eelseadistatud cron avaldised", "disable_login": "Keela sisselogimine", "duplicate_detection_job_description": "Rakenda üksustele masinõpet, et leida sarnaseid pilte. Kasutab nutiotsingut", "exclusion_pattern_description": "Välistamismustrid võimaldavad ignoreerida faile ja kaustu kogu skaneerimisel. See on kasulik, kui sul on kaustu, mis sisaldavad faile, mida sa ei soovi importida, nagu RAW failid.", @@ -561,7 +569,7 @@ "edit_exclusion_pattern": "Muuda välistamismustrit", "edit_faces": "Muuda nägusid", "edit_import_path": "Muuda imporditeed", - "edit_import_paths": "Muud imporditeid", + "edit_import_paths": "Muuda imporditeid", "edit_key": "Muuda võtit", "edit_link": "Muuda linki", "edit_location": "Muuda asukohta", @@ -1116,6 +1124,7 @@ "server_online": "Server ühendatud", "server_stats": "Serveri statistika", "server_version": "Serveri versioon", + "set": "Määra", "set_as_album_cover": "Sea albumi kaanepildiks", "set_as_profile_picture": "Sea profiilipildiks", "set_date_of_birth": "Määra sünnikuupäev", diff --git a/i18n/fi.json b/i18n/fi.json index bb5a60f49fa53..d3783691bef1f 100644 --- a/i18n/fi.json +++ b/i18n/fi.json @@ -17,7 +17,7 @@ "add_import_path": "Lisää tuontipolku", "add_location": "Lisää sijainti", "add_more_users": "Lisää käyttäjiä", - "add_partner": "Lisää kaveri", + "add_partner": "Lisää kumppani", "add_path": "Lisää polku", "add_photos": "Lisää kuvia", "add_to": "Lisää...", @@ -47,10 +47,10 @@ "disable_login": "Poista kirjautuminen käytöstä", "disabled": "Ei käytössä", "duplicate_detection_job_description": "Tunnista samankaltaiset kuvat käyttäen koneoppimista. Tukeutuu Smart Search:iin", - "exclusion_pattern_description": "Poissulkevat määritteet mahdollistavat tiettyjen tiedostojen ja kansioiden jättämisen pois kirjastoasi skannatessa. Tästä on hyötyä jos kansiot sisältävät tiedostoja mitä et halua tuoda, kuten RAW-tiedostot.", + "exclusion_pattern_description": "Poissulkemismallit mahdollistavat tiettyjen tiedostojen ja kansioiden jättämisen pois kirjastoasi skannatessa. Tästä on hyötyä jos kansiot sisältävät tiedostoja mitä et halua tuoda, kuten RAW-tiedostot.", "external_library_created_at": "Ulkoinen kirjasto (luotu {date})", "external_library_management": "Ulkoisen kirjaston hallinta", - "face_detection": "Kasvojen haitseminen", + "face_detection": "Kasvojen havaitseminen", "face_detection_description": "Tunnista sisällön kasvoja käyttäen koneoppimista. Videoiden osalta vain pikkukuva tunnistetaan. \"Päivitä\" (uudelleen)prosessoi koko sisällön.\"Nollaa\" lisäksi puhdistaa kaiken kasvo-datan. \"Puuttuvat\" prosessoi sisällön, jota ei vielä ole käyty läpi. Havaitut kasvot ryhmitellään jo tunnistettujen kanssa, tai lisätään uusina henkilöinä.", "facial_recognition_job_description": "Ryhmitä havaitut kasvot henkilöihin. Tämä vaihe suoritetaan, kun kasvot on ensin havaittu. \"Nollaus\" (uudelleen-)ryhmittelee kaikki kasvot. \"Puuttuvat\" vain ne, joille ei ole määritetty henkilöä.", "failed_job_command": "Komento {command} epäonnistui työlle {job}", @@ -67,20 +67,20 @@ "image_preview_quality_description": "Esikatselulaatu 1-100. Korkeampi arvo on parempi, mutta tuottaa suurempia tiedostoja ja voi heikentää sovelluksen reagointikykyä. Matalan arvon asettaminen voi vaikuttaa koneoppimisen laatuun.", "image_preview_resolution": "Esikatselun resoluutio", "image_preview_resolution_description": "Käytetään kun katsellaan yksittäisiä kuvia, tai koneoppimiseen. Suurempi resoluutio voi säilyttää paremmin yksityiskohtia. Tosin koodaus kestää kauemmin, tiedostokoko kasvaa, ja se saattaa hidastaa sovelluksen responsiivisuutta.", - "image_preview_title": "Esikatselu asetukset", + "image_preview_title": "Esikatselun asetukset", "image_quality": "Laatu", "image_quality_description": "Kuvan laatu välillä 1-100. Suurempi arvo on paremman laatuinen, mutta tuottaa kookkaampia tiedostoja. Tämä asetus vaikuttaa esikatselu- ja pikkukuviin.", "image_resolution": "Resoluutio", "image_resolution_description": "Korkeammat resoluutiot voivat säilyttää enemmän yksityiskohtia, mutta niiden koodaus kestää kauemmin, tiedostokoot ovat suurempia ja ne voivat heikentää sovelluksen reagointikykyä.", "image_settings": "Kuva-asetukset", - "image_settings_description": "Hallitse luotujen kuvien laatua ja resolutiota", + "image_settings_description": "Hallitse luotujen kuvien laatua ja resoluutiota", "image_thumbnail_description": "Pieni pikkukuva, josta metatiedot on poistettu, käytetään valokuvaryhmien katseluun, kuten pääaikajanalla", "image_thumbnail_format": "Pikkukuvien muoto", "image_thumbnail_quality_description": "Pikkukuvan laatu 1-100. Korkeampi arvo on parempi, mutta tuottaa suurempia tiedostoja ja voi heikentää sovelluksen reagointikykyä.", "image_thumbnail_resolution": "Pikkukuvien resoluutio", "image_thumbnail_resolution_description": "Käytetään katsottaessa useita kuvia kerralla (aikajana, albuminäkymä, jne.) Korkeampi resoluutio antaa enemmän yksityiskohtia, mutta niiden luonti kestää kauemmin, tiedostokoot ovat isompia ja voivat heikentää sovelluksen responsiivisuutta.", "image_thumbnail_title": "Pikkukuva-asetukset", - "job_concurrency": "{job} yhtäaikaisuus", + "job_concurrency": "Tehtävän \"{job}\" samanaikaisuus", "job_created": "Tehtävä luotu", "job_not_concurrency_safe": "Tätä tehtävää ei ole turvallista ajaa yhtäaikaisesti.", "job_settings": "Tehtävän asetukset", @@ -111,7 +111,7 @@ "machine_learning_duplicate_detection": "Kaksoiskappaleiden tunnistus", "machine_learning_duplicate_detection_enabled": "Ota käyttöön kaksoiskappaleiden tunnistus", "machine_learning_duplicate_detection_enabled_description": "Jos ei käytössä, täsmälleen samojen aineistojen kaksoiskappaleet tullaan silti poistamaan.", - "machine_learning_duplicate_detection_setting_description": "Etsi todennäköisiä kaksoiskappaleita CLIP upotuksien avulla", + "machine_learning_duplicate_detection_setting_description": "Etsi todennäköisiä kaksoiskappaleita CLIP-upotuksien avulla", "machine_learning_enabled": "Ota käyttöön koneoppiminen", "machine_learning_enabled_description": "Jos poistettu käytöstä, kaikki koneoppimistoiminnot ovat pois käytöstä riippumatta alla olevista asetuksista.", "machine_learning_facial_recognition": "Kasvojen tunnistus", @@ -131,7 +131,7 @@ "machine_learning_settings": "Koneoppimisen asetukset", "machine_learning_settings_description": "Koneoppimisen ominaisuudet ja asetukset", "machine_learning_smart_search": "Älykäs etsintä", - "machine_learning_smart_search_description": "Etsi kuvia merkityksellisemmin käyttäen CLIP upotuksia", + "machine_learning_smart_search_description": "Etsi kuvia merkityksellisemmin käyttäen CLIP-upotuksia", "machine_learning_smart_search_enabled": "Ota käyttöön älykäs haku", "machine_learning_smart_search_enabled_description": "Jos ei käytössä, kuvia ei koodata älykkäälle etsinnälle.", "machine_learning_url_description": "Koneoppimispalvelimen URL", @@ -139,17 +139,17 @@ "manage_log_settings": "Hallitse lokien asetuksia", "map_dark_style": "Tumma teema", "map_enable_description": "Ota käyttöön karttatoiminnot", - "map_gps_settings": "Kartta & GPS- asetukset", - "map_gps_settings_description": "Hallitse Kartan & GPS (Käänteinen Geokoodaus) Asetuksia", - "map_implications": "Kartta -ominaisuus käyttää ulkoista karttapalvelua", + "map_gps_settings": "Kartta- ja GPS-asetukset", + "map_gps_settings_description": "Hallitse kartan ja GPS:n (käänteisen geokoodauksen) asetuksia", + "map_implications": "Karttaominaisuus käyttää ulkoista karttapalvelua (tiles.immich.cloud)", "map_light_style": "Vaalea teema", "map_manage_reverse_geocoding_settings": "Hallitse käänteisen geokoodauksen asetuksia", "map_reverse_geocoding": "Käänteinen Geokoodaus", "map_reverse_geocoding_enable_description": "Ota käyttöön osoitteiden poiminta karttakoordinaateista", - "map_reverse_geocoding_settings": "Käänteisen Geokoodauksen asetukset", + "map_reverse_geocoding_settings": "Käänteisen geokoodauksen asetukset", "map_settings": "Kartta", "map_settings_description": "Hallitse kartan asetuksia", - "map_style_description": "style.json -karttateeman URL", + "map_style_description": "style.json-karttateeman URL", "metadata_extraction_job": "Kerää metadata", "metadata_extraction_job_description": "Poimi metatiedot aineistoista, kuten GPS, kasvot ja resoluutio", "metadata_faces_import_setting": "Ota käyttöön kasvojen tuonti", @@ -164,10 +164,10 @@ "note_cannot_be_changed_later": "Huom: Tätä ei voi enää myöhemmin vaihtaa!", "note_unlimited_quota": "Huom: Määritä 0 rajoittamattomaksi kiintiöksi", "notification_email_from_address": "Lähettäjän osoite", - "notification_email_from_address_description": "Lähettäjän sähköpostiosoite. Esimerkiksi \"Immich Kuvapalvelin \"", + "notification_email_from_address_description": "Lähettäjän sähköpostiosoite. Esimerkiksi \"Immich-kuvapalvelin \"", "notification_email_host_description": "Sähköpostipalvelin (esim. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Älä huomioi sertifikaattivirheitä", - "notification_email_ignore_certificate_errors_description": "Älä huomioi TLS sertifikaattien validointivirheitä (ei suositeltu)", + "notification_email_ignore_certificate_errors": "Älä huomioi varmennevirheitä", + "notification_email_ignore_certificate_errors_description": "Älä huomioi TLS-varmenteiden validointivirheitä (ei suositeltu)", "notification_email_password_description": "Sähköpostipalvelimen salasana", "notification_email_port_description": "Sähköpostipalvelimen portti (esim. 25, 465, tai 587)", "notification_email_sent_test_email_button": "Lähetä testaussähköposti ja tallenna", @@ -180,13 +180,13 @@ "notification_settings": "Ilmoitusasetukset", "notification_settings_description": "Hallitse ilmoitusasetuksia, myös sähköpostin", "oauth_auto_launch": "Automaattinen käynnistys", - "oauth_auto_launch_description": "Aloita OAuth kirjautuminen heti kun saavutaan kirjautumissivulle", + "oauth_auto_launch_description": "Aloita OAuth-kirjautumisvuo heti kun saavutaan kirjautumissivulle", "oauth_auto_register": "Automaattinen rekisteröinti", "oauth_auto_register_description": "Rekisteröi uudet OAuth:lla kirjautuvat käyttäjät automaattisesti", "oauth_button_text": "Painikkeen teksti", "oauth_client_id": "Client ID", "oauth_client_secret": "Client Secret", - "oauth_enable_description": "Kirjaudu käyttäen OAuth:ia", + "oauth_enable_description": "Kirjaudu käyttäen OAuthia", "oauth_issuer_url": "Toimitsijan URL", "oauth_mobile_redirect_uri": "Mobiilin uudellenohjaus-URI", "oauth_mobile_redirect_uri_override": "Ohita mobiilin uudelleenohjaus-URI", @@ -195,7 +195,7 @@ "oauth_profile_signing_algorithm_description": "Algoritmi, jota käytetään käyttäjäprofiilin allekirjoittamiseen.", "oauth_scope": "Skooppi (Scope)", "oauth_settings": "OAuth", - "oauth_settings_description": "Hallitse OAuth kirjautumisen asetuksia", + "oauth_settings_description": "Hallitse OAuth-kirjautumisen asetuksia", "oauth_settings_more_details": "Saadaksesi lisätietoja tästä toiminnosta, katso dokumentaatio.", "oauth_signing_algorithm": "Allekirjoitusalgoritmi", "oauth_storage_label_claim": "Tallennustilan nimikkeen valtuutusväittämä (claim)", @@ -241,7 +241,7 @@ "storage_template_date_time_sample": "Esimerkki päivämäärä {date}", "storage_template_enable_description": "Ota käyttöön tallennustilan mallit", "storage_template_hash_verification_enabled": "Tarkistussumman varmennus käytössä", - "storage_template_hash_verification_enabled_description": "Ottaa käyttöön varmistussummien laskennan. Älä poista käytöstä jollet ole aivan varma seurauksista", + "storage_template_hash_verification_enabled_description": "Ottaa käyttöön tarkistussummien laskennan. Älä poista käytöstä, ellet ole aivan varma seurauksista", "storage_template_migration": "Tallennustilan mallien migraatio", "storage_template_migration_description": "Käytä nykyistä {template}a aikaisemmin lähetettyihin kohteisiin", "storage_template_migration_info": "Malli vaikuttaa vain uusiin kohteisiin. Käyttääksesi mallia jo olemassa oleviin kohteisiin, aja {job}.", @@ -255,7 +255,7 @@ "system_settings": "Järjestelmäasetukset", "tag_cleanup_job": "Merkintäpuhdistus", "theme_custom_css_settings": "Mukautettu CSS", - "theme_custom_css_settings_description": "Kustomoi Immichin ulkoasua Cascading Style Sheets:llä.", + "theme_custom_css_settings_description": "Mukauta Immichin ulkoasua CSS:llä.", "theme_settings": "Teeman asetukset", "theme_settings_description": "Kustomoi Immichin web-käyttöliittymää", "these_files_matched_by_checksum": "Näillä tiedostoilla on yhteinen tarkistussumma", @@ -278,7 +278,7 @@ "transcoding_audio_codec": "Äänikoodekki", "transcoding_audio_codec_description": "Opus on paras laadultaan, mutta ei välttämättä ole yhteensopiva vanhempien laitteiden tai sovellusten kanssa.", "transcoding_bitrate_description": "Videot, jotka ylittävät enimmäisbittinopeuden tai eivät ole hyväksytyssä muodossa", - "transcoding_codecs_learn_more": "Oppiaksesi lisää tässä käytetystä terminologiasta, tutustu FFmpeg- dokumentaatioon H.264 koodaaja, HEVC koodaaja sekä VP9 koodaaja.", + "transcoding_codecs_learn_more": "Oppiaksesi lisää käytetystä terminologiasta, tutustu FFmpeg-dokumentaatioon: H.264-koodaaja, HEVC-koodaaja ja VP9-koodaaja.", "transcoding_constant_quality_mode": "Tasaisen laadun tyyppi", "transcoding_constant_quality_mode_description": "ICQ on parempi kuin CQP, mutta jotkut laitteistokiihdyttimet eivät tue sitä. Tätä asetusta käytetään oletuksena laatuun pohjautuvissa muunnoksissa, paitsi NVENC mikä ei tue ICQ:ta.", "transcoding_constant_rate_factor": "Vakionopeustekijä", @@ -323,7 +323,7 @@ "transcoding_video_codec_description": "VP9 on tehokkain ja web-yhteensopiva, mutta muuntaminen kestää kauemmin. HEVC suoriutuu yhtäläisesti, mutta ei ole ihan yhtä yhteensopiva. H.264 on hyvin yhteensopiva ja nopea muuntaa, mutta tuottaa paljon suurempia tiedostoja. AV1 on kaikkein tehokkain koodekki, mutta vanhemmat laitteet eivät sitä tue.", "trash_enabled_description": "Ota käyttöön roskakori", "trash_number_of_days": "Päivien lukumäärä", - "trash_number_of_days_description": "Montako päivää aineistoja pidetään roskakorissa ennen pysyvää poistamista", + "trash_number_of_days_description": "Kuinka monta päivää aineistoja pidetään roskakorissa ennen pysyvää poistamista", "trash_settings": "Roskakorin asetukset", "trash_settings_description": "Hallitse roskakoriasetuksia", "untracked_files": "Tiedostot joita ei seurata", @@ -331,7 +331,7 @@ "user_cleanup_job": "Käyttäjien puhdistus", "user_delete_delay": "Käyttäjän {user} tili ja aineistot aikataulutetaan poistettavaksi ajan kuluttua: {delay, plural, one {# day} other {# days}}.", "user_delete_delay_settings": "Poiston viive", - "user_delete_delay_settings_description": "Montako päivää poistamisen jälkeen käyttäjä ja hänen aineistonsa poistetaan pysyvästi. Joka keskiyö käydään läpi poistetuiksi merkityt käyttäjät. Tämä muutos astuu voimaan seuraavalla ajokerralla.", + "user_delete_delay_settings_description": "Kuinka monta päivää poistamisen jälkeen käyttäjä ja hänen aineistonsa poistetaan pysyvästi. Joka keskiyö käydään läpi poistettavaksi merkityt käyttäjät. Tämä muutos astuu voimaan seuraavalla ajokerralla.", "user_delete_immediately": "{user}:n tili ja sen kohteet on ajastettu poistettavaksi heti.", "user_delete_immediately_checkbox": "Aseta tili ja sen kohteet jonoon välitöntä poistoa varten", "user_management": "Käyttäjien hallinta", @@ -343,7 +343,7 @@ "user_settings_description": "Hallitse käyttäjäasetuksia", "user_successfully_removed": "Käyttäjä {email} on poistettu.", "version_check_enabled_description": "Ota käyttöön versiotarkastus", - "version_check_implications": "Versiontarkistus vaatii säännöllisen yhteyden github.com:iin", + "version_check_implications": "Versiotarkistus vaatii säännöllisen yhteyden github.comiin", "version_check_settings": "Versiotarkistus", "version_check_settings_description": "Ota käyttöön ilmoitukset, kun uusi versio on saatavilla", "video_conversion_job": "Transkoodaa videot", @@ -424,7 +424,7 @@ "assets_restored_count": "{count, plural, one {# media} other {# mediaa}} palautettu", "assets_trashed_count": "{count, plural, one {# media} other {# mediaa}} siirretty roskakoriin", "assets_were_part_of_album_count": "{count, plural, one {Media oli} other {Mediat olivat}} jo albumissa", - "authorized_devices": "Auktorisoidut laitteet", + "authorized_devices": "Valtuutetut laitteet", "back": "Takaisin", "back_close_deselect": "Palaa, sulje tai poista valinnat", "backward": "Taaksepäin", @@ -507,7 +507,7 @@ "create_new_person_hint": "Määritä valitut mediat uudelle henkilölle", "create_new_user": "Luo uusi käyttäjä", "create_tag": "Luo tunniste", - "create_tag_description": "Luo uusi tunniste. Sisäkkäisiä tunnisteita varten, syötä tunnisteen täydellinen polku kauttaviiva mukaanluettuna.", + "create_tag_description": "Luo uusi tunniste. Sisäkkäisiä tunnisteita varten syötä tunnisteen täydellinen polku kauttaviivat mukaan luettuna.", "create_user": "Luo käyttäjä", "created": "Luotu", "current_device": "Nykyinen laite", @@ -532,7 +532,7 @@ "delete_link": "Poista linkki", "delete_shared_link": "Poista jaettu linkki", "delete_tag": "Poista tunniste", - "delete_tag_confirmation_prompt": "Haluatko varmasti poistaa {tagName}-tunnisteen?", + "delete_tag_confirmation_prompt": "Haluatko varmasti poistaa tunnisteen {tagName}?", "delete_user": "Poista käyttäjä", "deleted_shared_link": "Jaettu linkki poistettu", "deletes_missing_assets": "Poistaa levyltä puuttuvat resurssit", @@ -644,7 +644,7 @@ "unable_to_add_album_users": "Käyttäjiä ei voi lisätä albumiin", "unable_to_add_assets_to_shared_link": "Medioiden lisääminen jaettuun linkkiin epäonnistui", "unable_to_add_comment": "Kommentin lisääminen epäonnistui", - "unable_to_add_exclusion_pattern": "Ei voida lisätä poissulkuohjetta", + "unable_to_add_exclusion_pattern": "Ei voida lisätä poissulkemismallia", "unable_to_add_import_path": "Tuontipolkua ei voitu lisätä", "unable_to_add_partners": "Kumppaneita ei voitu lisätä", "unable_to_add_remove_archive": "Ei voida {archived, select, true {poistaa kohdetta arkistosta} other {lisätä kohdetta arkistoon}}", @@ -669,12 +669,12 @@ "unable_to_delete_album": "Albumin poistaminen epäonnistui", "unable_to_delete_asset": "Kohteen poistaminen epäonnistui", "unable_to_delete_assets": "Virhe kohteen poistamisessa", - "unable_to_delete_exclusion_pattern": "Ei voida poistaa poissulkuohjetta", + "unable_to_delete_exclusion_pattern": "Ei voida poistaa poissulkemismallia", "unable_to_delete_import_path": "Tuontipolkua ei voitu poistaa", "unable_to_delete_shared_link": "Jaetun linkin poistaminen epäonnistui", "unable_to_delete_user": "Käyttäjän poistaminen epäonnistui", "unable_to_download_files": "Tiedostojen lataaminen epäonnistui", - "unable_to_edit_exclusion_pattern": "Ei voida muokata poissulkuohjetta", + "unable_to_edit_exclusion_pattern": "Ei voida muokata poissulkemismallia", "unable_to_edit_import_path": "Tuontipolkua ei voitu muokata", "unable_to_empty_trash": "Roskakorin tyhjentäminen epäonnistui", "unable_to_enter_fullscreen": "Koko ruudun tilaan siirtyminen epäonnistui", @@ -745,7 +745,7 @@ "expired": "Voimassaolo päättynyt", "expires_date": "Vanhenee {date}", "explore": "Tutki", - "explorer": "Tutkija", + "explorer": "Selain", "export": "Vie", "export_as_json": "Vie JSON-muodossa", "extension": "Tiedostopääte", @@ -805,8 +805,8 @@ "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Kuva}} otettu {city}ssä, {country}ssä {person1}n, {person2}n ja {person3}n kanssa {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Kuva}} otettu {city}ssä, {country}ssä {person1}n, {person2}n ja {additionalCount, number} muun kanssa {date}", "img": "", - "immich_logo": "Immich Logo", - "immich_web_interface": "Immich verkkoliittymä", + "immich_logo": "Immich-logo", + "immich_web_interface": "Immich-verkkokäyttöliittymä", "import_from_json": "Tuo JSON-tiedostosta", "import_path": "Tuontipolku", "in_albums": "{count, plural, one {# Albumissa} other {# albumissa}}", @@ -873,7 +873,7 @@ "manage_your_devices": "Hallitse sisäänkirjautuneita laitteitasi", "manage_your_oauth_connection": "Hallitse OAuth-yhteyttäsi", "map": "Kartta", - "map_marker_for_images": "Karttamarkerointi kuville, jotka on otettu {city}ssä, {country}ssä", + "map_marker_for_images": "Karttamarkerointi kuville, jotka on otettu kaupungissa {city}, maassa {country}", "map_marker_with_image": "Karttamarkerointi kuvalla", "map_settings": "Kartta-asetukset", "matches": "Osumia", @@ -901,7 +901,7 @@ "name_or_nickname": "Nimi tai lempinimi", "never": "ei koskaan", "new_album": "Uusi Albumi", - "new_api_key": "Uusi API Key", + "new_api_key": "Uusi API-avain", "new_password": "Uusi salasana", "new_person": "Uusi henkilö", "new_user_created": "Uusi käyttäjä lisätty", @@ -929,7 +929,7 @@ "note_apply_storage_label_to_previously_uploaded assets": "Huom: Jotta voit soveltaa tallennustunnistetta aiemmin ladattuihin kohteisiin, suorita", "note_unlimited_quota": "Huomio: Syötä 0 rajoittamatonta kiintiötä varten", "notes": "Muistiinpanot", - "notification_toggle_setting_description": "Ota sähköpostilmoitukset käyttöön", + "notification_toggle_setting_description": "Ota sähköposti-ilmoitukset käyttöön", "notifications": "Ilmoitukset", "notifications_setting_description": "Hallitse ilmoituksia", "oauth": "OAuth", @@ -1041,7 +1041,7 @@ "purchase_panel_info_1": "Immichin rakentaminen vie paljon aikaa ja vaivannäköä, ja meillä on kokopäiväisiä insinöörejä työskentelemässä sen parissa, jotta voimme tehdä siitä mahdollisimman hyvän. Missiomme on, että avoimen lähdekoodin ohjelmistosta ja eettisistä liiketoimintakäytännöistä tulee kestävä tulonlähde kehittäjille, sekä luoda yksityisyyttä kunnioittava ekosysteemi, jossa on todellisia vaihtoehtoja hyväksikäyttöön perustuville pilvipalveluille.", "purchase_panel_info_2": "Koska olemme sitoutuneet siihen, ettemme lisää maksumuuria, tämä osto ei anna sinulle mitään lisäominaisuuksia Immichissa. Luotamme kaltaisiisi käyttäjiin tukeaksemme Immichin jatkuvaa kehittämistä.", "purchase_panel_title": "Tue projektia", - "purchase_per_server": "Per serveri", + "purchase_per_server": "Per palvelin", "purchase_per_user": "Per käyttäjä", "purchase_remove_product_key": "Poista Tuoteavain", "purchase_remove_product_key_prompt": "Haluatko varmasti poistaa tuoteavaimen?", @@ -1049,13 +1049,13 @@ "purchase_remove_server_product_key_prompt": "Haluatko varmasti poistaa palvelimen tuoteavaimen?", "purchase_server_description_1": "Koko palvelimelle", "purchase_server_description_2": "Tukijan tila", - "purchase_server_title": "Serveri", + "purchase_server_title": "Palvelin", "purchase_settings_server_activated": "Palvelimen tuoteavainta hallinnoi ylläpitäjä", "range": "", "rating": "Tähtiarvostelu", "rating_clear": "Tyhjennä arvostelu", "rating_count": "{count, plural, one {# tähti} other {# tähteä}}", - "rating_description": "Näytä EXIF-arvosana tiedot-paneelissa", + "rating_description": "Näytä EXIF-arvosana lisätietopaneelissa", "raw": "", "reaction_options": "Reaktioasetukset", "read_changelog": "Lue muutosloki", @@ -1086,7 +1086,7 @@ "remove_from_favorites": "Poista suosikeista", "remove_from_shared_link": "Poista jakolinkistä", "remove_user": "Poista käyttäjä", - "removed_api_key": "API Key {name} poistettu", + "removed_api_key": "API-avain {name} poistettu", "removed_from_archive": "Poistettu arkistosta", "removed_from_favorites": "Poistettu suosikeista", "removed_from_favorites_count": "{count, plural, other {Poistettu #}} suosikeista", @@ -1116,7 +1116,7 @@ "role_editor": "Editori", "role_viewer": "Toistin", "save": "Tallenna", - "saved_api_key": "API Key tallennettu", + "saved_api_key": "API-avain tallennettu", "saved_profile": "Profiili tallennettu", "saved_settings": "Asetukset tallennettu", "say_something": "Sano jotain", @@ -1143,7 +1143,7 @@ "search_places": "Etsi paikkoja", "search_settings": "Hakuasetukset", "search_state": "Etsi tilaa...", - "search_tags": "Haku tageja...", + "search_tags": "Etsi tunnisteita...", "search_timezone": "Etsi aikavyöhyke...", "search_type": "Etsinnän tyyppi", "search_your_photos": "Etsi kuvia", @@ -1167,8 +1167,8 @@ "send_message": "Lähetä viesti", "send_welcome_email": "Lähetä tervetuloviesti", "server": "Palvelin", - "server_offline": "Serveri Offline-tilassa", - "server_online": "Palvelin on linjalla", + "server_offline": "Palvelin Offline-tilassa", + "server_online": "Palvelin Online-tilassa", "server_stats": "Palvelimen tilastot", "server_version": "Palvelimen versio", "set": "Aseta", @@ -1184,11 +1184,11 @@ "shared_by": "Jakanut", "shared_by_user": "Käyttäjän {user} jakama", "shared_by_you": "Sinun jakamasi", - "shared_from_partner": "{partner}n kuvia", + "shared_from_partner": "Kumppanin {partner} kuvia", "shared_link_options": "Jaetun linkin vaihtoehdot", "shared_links": "Jaetut linkit", "shared_photos_and_videos_count": "{assetCount, plural, other {# jaettua kuvaa ja videota.}}", - "shared_with_partner": "Jaa {partner} kanssa", + "shared_with_partner": "Jaa kumppanin {partner} kanssa", "sharing": "Jakaminen", "sharing_enter_password": "Nähdäksesi sivun sinun tulee antaa salasana.", "sharing_sidebar_description": "Näytä jakamislinkki sivupalkissa", @@ -1220,7 +1220,7 @@ "size": "Koko", "skip_to_content": "Siirry sisältöön", "skip_to_folders": "Siirry kansioihin", - "skip_to_tags": "Siirry tageihin", + "skip_to_tags": "Siirry tunnisteisiin", "slideshow": "Diaesitys", "slideshow_settings": "Diaesityksen asetukset", "sort_albums_by": "Järjestä albumit...", @@ -1230,7 +1230,7 @@ "sort_oldest": "Vanhin kuva", "sort_recent": "Tuorein kuva", "sort_title": "Otsikko", - "source": "Lähde", + "source": "Lähdekoodi", "stack": "Pinoa", "stack_duplicates": "Pinoa kaksoiskappaleet", "stack_select_one_photo": "Valitse yksi pääkuva pinolle", @@ -1238,7 +1238,7 @@ "stacked_assets_count": "Pinottu {count, plural, one {# media} other {# mediaa}}", "stacktrace": "Vianetsintätiedot", "start": "Aloita", - "start_date": "Alkupäivämäärä", + "start_date": "Alkupäivä", "state": "Maakunta/osavaltio", "status": "Tila", "stop_motion_photo": "Pysäytä liikkuva kuva", @@ -1256,14 +1256,14 @@ "support_third_party_description": "Immich-asennuksesi on pakattu kolmannen osapuolen toimesta. Kohtaamasi ongelmat saattavat johtua tästä paketista, joten ilmoita niistä ensisijaisesti heille alla olevien linkkien kautta.", "swap_merge_direction": "Käännä yhdistämissuunta", "sync": "Synkronoi", - "tag": "Tagi", - "tag_assets": "Merkitse kohde", + "tag": "Lisää tunniste", + "tag_assets": "Lisää tunnisteita", "tag_created": "Luotu tunniste: {tag}", - "tag_feature_description": "Selaa valokuvia ja videoita, jotka on ryhmitelty loogisten tagiotsikoiden mukaan", + "tag_feature_description": "Selaa valokuvia ja videoita, jotka on ryhmitelty loogisten tunnisteotsikoiden mukaan", "tag_not_found_question": "Etkö löydä tunnistetta? Luo uusi tunniste ", "tag_updated": "Päivitetty tunniste: {tag}", "tagged_assets": "Tunnistettu {count, plural, one {# kohde} other {# kohdetta}}", - "tags": "Tagit", + "tags": "Tunnisteet", "template": "Template", "theme": "Teema", "theme_selection": "Teeman valinta", diff --git a/i18n/fil.json b/i18n/fil.json new file mode 100644 index 0000000000000..0967ef424bce6 --- /dev/null +++ b/i18n/fil.json @@ -0,0 +1 @@ +{} diff --git a/i18n/fr.json b/i18n/fr.json index 781f801bb9f36..9e4b925c885ec 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -34,6 +34,11 @@ "authentication_settings_disable_all": "Êtes-vous sûr de vouloir désactiver toutes les méthodes de connexion ? La connexion sera complètement désactivée.", "authentication_settings_reenable": "Pour réactiver, utilisez une Commande Serveur.", "background_task_job": "Tâches de fond", + "backup_database": "Sauvegarde de la base de données", + "backup_database_enable_description": "Activer la sauvegarde", + "backup_keep_last_amount": "Nombre de sauvegardes à conserver", + "backup_settings": "Paramètres de la sauvegarde", + "backup_settings_description": "Gérer les paramètres de la sauvegarde", "check_all": "Vérifier tout", "cleared_jobs": "Tâches supprimées pour : {job}", "config_set_by_file": "La configuration est actuellement définie par un fichier de configuration", @@ -43,6 +48,9 @@ "confirm_reprocess_all_faces": "Êtes-vous sûr de vouloir retraiter tous les visages ? Cela effacera également les personnes déjà identifiées.", "confirm_user_password_reset": "Êtes-vous sûr de vouloir réinitialiser le mot de passe de {user} ?", "create_job": "Créer une tâche", + "cron_expression": "Expression cron", + "cron_expression_description": "Définir l'intervalle d'analyse à l'aide d'une expression cron. Pour plus d'informations, voir Crontab Guru", + "cron_expression_presets": "Préréglages expression cron", "crontab_guru": "Générateur de règles Cron", "disable_login": "Désactiver la connexion", "disabled": "Désactivé", diff --git a/i18n/he.json b/i18n/he.json index 7cb896f1f0b3f..13353f5d86a8a 100644 --- a/i18n/he.json +++ b/i18n/he.json @@ -433,7 +433,7 @@ "blurred_background": "רקע מטושטש", "bugs_and_feature_requests": "באגים & בקשות לתכונות", "build": "Build", - "build_image": "Build Image", + "build_image": "בניית Image", "bulk_delete_duplicates_confirmation": "האם את/ה בטוח/ה שברצונך למחוק בכמות גדולה {count, plural, one {נכס # כפול} other {# נכסים כפולים}}? זה ישמור על הנכס הכי גדול של כל קבוצה וימחק לצמיתות את כל שאר הכפילויות. את/ה לא יכול/ה לבטל את הפעולה הזו!", "bulk_keep_duplicates_confirmation": "האם את/ה בטוח/ה שברצונך להשאיר {count, plural, one {נכס # כפול} other {# נכסים כפולים}}? זה יפתור את כל הקבוצות הכפולות מבלי למחוק דבר.", "bulk_trash_duplicates_confirmation": "האם את/ה בטוח/ה שברצונך להעביר לאשפה בכמות גדולה {count, plural, one {נכס # כפול} other {# נכסים כפולים}}? זה ישמור על הנכס הגדול ביותר של כל קבוצה ויעביר לאשפה את כל שאר הכפילויות.", @@ -1391,7 +1391,7 @@ "warning": "אזהרה", "week": "שבוע", "welcome": "ברוכים הבאים", - "welcome_to_immich": "ברוכים הבאים אל immich", + "welcome_to_immich": "ברוכים הבאים לimmich", "year": "שנה", "years_ago": "לפני {years, plural, one {שנה #} other {# שנים}}", "yes": "כן", diff --git a/i18n/hr.json b/i18n/hr.json index 886a0ae49b1c4..260cf0b3460ff 100644 --- a/i18n/hr.json +++ b/i18n/hr.json @@ -28,6 +28,7 @@ "added_to_favorites_count": "Dodano {count, number} u omiljeno", "admin": { "add_exclusion_pattern_description": "Dodajte uzorke izuzimanja. Globiranje pomoću *, ** i ? je podržano. Za ignoriranje svih datoteka u bilo kojem direktoriju pod nazivom \"Raw\", koristite \"**/Raw/**\". Da biste zanemarili sve datoteke koje završavaju na \".tif\", koristite \"**/*.tif\". Da biste zanemarili apsolutni put, koristite \"/path/to/ignore/**\".", + "asset_offline_description": "Ovo sredstvo vanjske knjižnice više nije pronađeno na disku i premješteno je u smeće. Ako je datoteka premještena unutar biblioteke, provjerite svoju vremensku traku za novo odgovarajuće sredstvo. Da biste vratili ovo sredstvo, provjerite može li Immich pristupiti donjoj stazi datoteke i skenirajte biblioteku.", "authentication_settings": "Postavke autentikacije", "authentication_settings_description": "Uredi lozinku, OAuth, i druge postavke autentikacije", "authentication_settings_disable_all": "Jeste li sigurni da želite onemogućenit sve načine prijave? Prijava će biti potpuno onemogućena.", @@ -54,12 +55,15 @@ "failed_job_command": "Naredba {command} nije uspjela za posao: {job}", "force_delete_user_warning": "UPOZORENJE: Ovo će odmah ukloniti korisnika i sve pripadajuće podatke. Ovo se ne može poništiti i datoteke se ne mogu vratiti.", "forcing_refresh_library_files": "Prisilno osvježavanje svih datoteka knjižnice", + "image_format": "Format", "image_format_description": "WebP proizvodi manje datoteke od JPEG-a, ali se sporije kodira.", "image_prefer_embedded_preview": "Preferiraj ugrađeni pregled", "image_prefer_embedded_preview_setting_description": "Koristite ugrađene preglede u RAW fotografije kao ulaz za obradu slike kada su dostupni. To može proizvesti preciznije boje za neke slike, ali kvaliteta pregleda ovisi o kameri i slika može imati više artefakata kompresije.", "image_prefer_wide_gamut": "Preferirajte široku gamu", "image_prefer_wide_gamut_setting_description": "Koristite Display P3 za sličice. Ovo bolje čuva živost slika sa širokim prostorima boja, ali slike mogu izgledati drugačije na starim uređajima sa starom verzijom preglednika. sRGB slike čuvaju se kao sRGB kako bi se izbjegle promjene boja.", + "image_preview_description": "Slika srednje veličine s ogoljenim metapodacima, koristi se prilikom pregledavanja jednog sredstva i za strojno učenje", "image_preview_format": "Format pregleda", + "image_preview_quality_description": "Kvaliteta pregleda od 1-100. Više je bolje, ali proizvodi veće datoteke i može smanjiti odziv aplikacije. Postavljanje niske vrijednosti može utjecati na kvalitetu strojnog učenja.", "image_preview_resolution": "Razlučivost pregleda", "image_preview_resolution_description": "Koristi se pri gledanju jedne fotografije i za strojno učenje. Veće razlučivosti mogu sačuvati više detalja, ali trebaju dulje za kodiranje, imaju veće veličine datoteka i mogu smanjiti odaziv aplikacije.", "image_quality": "Kvaliteta", @@ -525,6 +529,7 @@ "direction": "Smjer", "disabled": "Onemogućeno", "disallow_edits": "Zabrani izmjene", + "discord": "Discord", "discover": "Otkrij", "dismiss_all_errors": "Odbaci sve pogreške", "dismiss_error": "Odbaci pogrešku", @@ -533,6 +538,7 @@ "display_original_photos": "Prikaz originalnih fotografija", "display_original_photos_setting_description": "Radije prikažite izvornu fotografiju kada gledate materijal umjesto sličica kada je izvorni materijal kompatibilan s webom. To može rezultirati sporijim brzinama prikaza fotografija.", "do_not_show_again": "Ne prikazuj više ovu poruku", + "documentation": "Dokumentacija", "done": "Gotovo", "download": "Preuzmi", "download_include_embedded_motion_videos": "Ugrađeni videozapisi", @@ -1078,6 +1084,7 @@ "say_something": "Reci nešto", "scan_all_libraries": "Skeniraj sve Knjižnice", "scan_all_library_files": "Ponovno skenirajte sve datoteke Knjižnice", + "scan_library": "Skeniraj", "scan_new_library_files": "Skeniraj nove datoteke Knjižnice", "scan_settings": "Postavke skeniranja", "scanning_for_album": "Skeniranje albuma...", @@ -1098,49 +1105,56 @@ "search_places": "Traži mjesta", "search_settings": "Postavke pretraživanja", "search_state": "", - "search_timezone": "", + "search_timezone": "Pretraži vremenske zone", "search_type": "", "search_your_photos": "", "searching_locales": "", "second": "", "select_album_cover": "", - "select_all": "", + "select_all": "Odaberi sve", + "select_all_duplicates": "Odaberi sve duplikate", "select_avatar_color": "", - "select_face": "", + "select_face": "Odaberi lice", "select_featured_photo": "", "select_keep_all": "", "select_library_owner": "", "select_new_face": "", "select_photos": "", "select_trash_all": "", - "selected": "", + "selected": "Odabrano", "send_message": "", - "send_welcome_email": "", + "send_welcome_email": "Pošalji email dobrodošlice", "server": "", - "server_stats": "", - "set": "", + "server_offline": "Server izvan mreže", + "server_online": "Server na mreži", + "server_stats": "Statistike servera", + "server_version": "Verzija servera", + "set": "Postavi", "set_as_album_cover": "", - "set_as_profile_picture": "", - "set_date_of_birth": "", - "set_profile_picture": "", + "set_as_profile_picture": "Postavi kao profilnu sliku", + "set_date_of_birth": "Postavi datum rođenja", + "set_profile_picture": "Postavi profilnu sliku", "set_slideshow_to_fullscreen": "", - "settings": "", - "settings_saved": "", - "share": "", - "shared": "", - "shared_by": "", - "shared_by_you": "", - "shared_from_partner": "", + "settings": "Postavke", + "settings_saved": "Postavke su spremljene", + "share": "Podijeli", + "shared": "Podijeljeno", + "shared_by": "Podijelio", + "shared_by_user": "Podijelio {user}", + "shared_by_you": "Podijelili vi", + "shared_from_partner": "Fotografije od {partner}", "shared_links": "", "shared_with_partner": "", "sharing": "", "sharing_sidebar_description": "", "show_album_options": "", - "show_and_hide_people": "", - "show_file_location": "", - "show_gallery": "", - "show_hidden_people": "", - "show_in_timeline": "", + "show_albums": "Prikaži albume", + "show_all_people": "Prikaži sve osobe", + "show_and_hide_people": "Prikaži i sakrij osobe", + "show_file_location": "Pokaži mjesto datoteke", + "show_gallery": "Prikaži galeriju", + "show_hidden_people": "Prikaži skrivene osobe", + "show_in_timeline": "Prikaži na vremenskoj crti", "show_in_timeline_setting_description": "", "show_keyboard_shortcuts": "", "show_metadata": "", diff --git a/i18n/hu.json b/i18n/hu.json index 4bde75b5ef7fa..525275efc676d 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -34,6 +34,11 @@ "authentication_settings_disable_all": "Biztosan letiltod az összes bejelentkezési módot? A bejelentkezés teljesen le lesz tiltva.", "authentication_settings_reenable": "Az újbóli engedélyezéshez használj egySzerver Parancsot.", "background_task_job": "Háttérfeladatok", + "backup_database": "Tartalék Adatbázis", + "backup_database_enable_description": "Adatbázis biztonsági mentések engedélyezése", + "backup_keep_last_amount": "Megőrizendő korábbi biztonsági mentések száma", + "backup_settings": "Biztonsági mentés beállításai", + "backup_settings_description": "Adatbázis mentési beállításainak kezelése", "check_all": "Összes Kipiálása", "cleared_jobs": "{job}: feladatai törölve", "config_set_by_file": "A konfigurációt jelenleg egy konfigurációs fájl állítja be", @@ -43,6 +48,9 @@ "confirm_reprocess_all_faces": "Biztos vagy benne, hogy újra fel szeretnéd dolgozni az összes arcot? Ez a már elnevezett személyeket is törli.", "confirm_user_password_reset": "Biztosan vissza szeretnéd állítani {user} jelszavát?", "create_job": "Feladat létrehozása", + "cron_expression": "Cron kifejezés", + "cron_expression_description": "A beolvasási időköz beállítása a cron formátummal. További információért lásd pl. Crontab Guru", + "cron_expression_presets": "Cron kifejezés előbeállítások", "crontab_guru": "Crontab Guru", "disable_login": "Belépés letiltása", "disabled": "Letiltva", diff --git a/i18n/id.json b/i18n/id.json index b95d02d28da7b..0aa3e408c7477 100644 --- a/i18n/id.json +++ b/i18n/id.json @@ -34,6 +34,11 @@ "authentication_settings_disable_all": "Anda yakin untuk menonaktifkan semua cara login? Login akan dinonaktikan secara menyeluruh.", "authentication_settings_reenable": "Untuk mengaktifkan ulang, gunakan Perintah Server.", "background_task_job": "Tugas Latar Belakang", + "backup_database": "Basis Data Cadangan", + "backup_database_enable_description": "Aktifkan pencadangan basis data", + "backup_keep_last_amount": "Jumlah cadangan untuk disimpan", + "backup_settings": "Pengaturan Pencadangan", + "backup_settings_description": "Kelola pengaturan pencadangan basis data", "check_all": "Periksa Semua", "cleared_jobs": "Tugas terselesaikan untuk: {job}", "config_set_by_file": "Konfigurasi saat ini ditetapkan oleh berkas konfigurasi", @@ -43,6 +48,9 @@ "confirm_reprocess_all_faces": "Apakah Anda yakin ingin memproses semua wajah? Ini juga akan menghapus nama orang.", "confirm_user_password_reset": "Apakah Anda yakin ingin mengatur ulang kata sandi {user}?", "create_job": "Buat tugas", + "cron_expression": "Ekspresi cron", + "cron_expression_description": "Tetapkan interval pemindaian menggunakan format cron. Untuk informasi lebih lanjut, silakan merujuk misalnya ke Crontab Guru", + "cron_expression_presets": "Prasetel ekspresi cron", "disable_login": "Nonaktifkan log masuk", "duplicate_detection_job_description": "Jalankan pembelajaran mesin pada aset untuk mendeteksi gambar yang serupa. Bergantung pada Pencarian Pintar", "exclusion_pattern_description": "Pola pengecualian memungkinkan Anda mengabaikan berkas dan folder ketika memindai pustaka Anda. Ini berguna jika Anda memiliki folder yang berisi berkas yang tidak ingin diimpor, seperti berkas RAW.", @@ -1360,7 +1368,7 @@ "warning": "Peringatan", "week": "Pekan", "welcome": "Selamat datang", - "welcome_to_immich": "Selamat datang di immich", + "welcome_to_immich": "Selamat datang di Immich", "year": "Tahun", "years_ago": "{years, plural, one {# tahun} other {# tahun}} yang lalu", "yes": "Ya", diff --git a/i18n/it.json b/i18n/it.json index 12821fd65463b..fc3e9d97fc505 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -1390,7 +1390,7 @@ "warning": "Attenzione", "week": "Settimana", "welcome": "Benvenuto", - "welcome_to_immich": "Benvenuto in immich", + "welcome_to_immich": "Benvenuto in Immich", "year": "Anno", "years_ago": "{years, plural, one {# anno} other {# anni}} fa", "yes": "Si", diff --git a/i18n/ko.json b/i18n/ko.json index 5ec2a984d5c9b..74c919a715aa6 100644 --- a/i18n/ko.json +++ b/i18n/ko.json @@ -23,17 +23,22 @@ "add_to": "앨범에 추가...", "add_to_album": "앨범에 추가", "add_to_shared_album": "공유 앨범에 추가", - "added_to_archive": "보관함으로 이동되었습니다.", + "added_to_archive": "보관함에 추가되었습니다.", "added_to_favorites": "즐겨찾기에 추가되었습니다.", "added_to_favorites_count": "즐겨찾기에 항목 {count, number}개 추가됨", "admin": { - "add_exclusion_pattern_description": "규칙에 *, ** 및 ? 를 사용할 수 있습니다. \"Raw\" 디렉터리의 모든 파일을 제외하려면 **/Raw/**를, \".tif\"로 끝나는 파일을 제외하려면 **/*.tif를 사용합니다. 절대 경로는 /path/to/ignore/** 와 같은 방식으로 사용하세요.", - "asset_offline_description": "이 외부 라이브러리 항목을 디스크에서 찾을 수 없어 휴지통으로 이동되었습니다. 라이브러리 내에서 파일이 이동된 경우 해당하는 새 항목을 타임라인에서 확인하세요. 이 항목을 복원하려면 파일 경로에 Immich가 접근할 수 있는지 확인한 후, 라이브러리 스캔을 진행하세요.", + "add_exclusion_pattern_description": "규칙에 *, ** 및 ? 를 사용할 수 있습니다. 이름이 \"Raw\"인 디렉터리의 모든 파일을 제외하려면 \"**/Raw/**\"를, \".tif\"로 끝나는 모든 파일을 제외하려면 \"**/*.tif\"를 사용하고, 절대 경로의 경우 \"/path/to/ignore/**\"와 같은 방식으로 사용합니다.", + "asset_offline_description": "외부 라이브러리에 포함된 이 항목을 디스크에서 더이상 찾을 수 없어 휴지통으로 이동되었습니다. 파일이 라이브러리 내에서 이동된 경우 타임라인에서 새로 연결된 항목을 확인하세요. 이 항목을 복원하려면 아래 파일 경로에 Immich가 접근할 수 있는지 확인하고 라이브러리 스캔을 진행하세요.", "authentication_settings": "인증 설정", "authentication_settings_description": "비밀번호, OAuth 및 기타 인증 설정 관리", "authentication_settings_disable_all": "로그인 기능을 모두 비활성화하시겠습니까? 로그인하지 않아도 서버에 접근할 수 있습니다.", "authentication_settings_reenable": "다시 활성화하려면 서버 커맨드를 사용하세요.", "background_task_job": "백그라운드 작업", + "backup_database": "데이터베이스 백업", + "backup_database_enable_description": "데이터베이스 백업 활성화", + "backup_keep_last_amount": "보관할 백업의 개수", + "backup_settings": "백업 설정", + "backup_settings_description": "데이터베이스 백업 설정 관리", "check_all": "모두 확인", "cleared_jobs": "작업 중단: {job}", "config_set_by_file": "현재 설정은 구성 파일에 의해 관리됩니다.", @@ -43,28 +48,31 @@ "confirm_reprocess_all_faces": "모든 얼굴을 다시 처리하시겠습니까? 이름이 지정된 인물을 포함한 모든 인물이 삭제됩니다.", "confirm_user_password_reset": "{user}님의 비밀번호를 재설정하시겠습니까?", "create_job": "작업 생성", + "cron_expression": "Cron 표현식", + "cron_expression_description": "Cron 형식을 사용하여 스캔 주기를 설정합니다. 자세한 내용과 예시는 Crontab Guru를 참조하세요.", + "cron_expression_presets": "Cron 표현식 사전 설정", "crontab_guru": "Crontab Guru", "disable_login": "로그인 비활성화", "disabled": "비활성화", "duplicate_detection_job_description": "기계 학습을 통해 유사한 이미지를 감지합니다. 스마트 검색이 활성화되어 있어야 합니다.", - "exclusion_pattern_description": "제외 규칙을 사용하면 스캔 중 특정 파일과 폴더를 제외할 수 있습니다. 가져오고 싶지 않은 파일(RAW 파일 등)이 존재하는 경우 유용합니다.", + "exclusion_pattern_description": "제외 규칙을 사용하여 라이브러리 스캔 시 특정 파일과 폴더를 제외할 수 있습니다. 폴더에 원하지 않는 파일(RAW 파일 등)이 존재하는 경우 유용합니다.", "external_library_created_at": "외부 라이브러리 ({date}에 생성됨)", "external_library_management": "외부 라이브러리 관리", "face_detection": "얼굴 감지", - "face_detection_description": "기계 학습을 통해 항목에 존재하는 얼굴을 감지합니다. 동영상의 경우 섬네일만 사용합니다. \"새로고침\"은 이미 처리된 항목을 포함한 모든 항목 다시 처리합니다. \"초기화\"는 모든 얼굴 데이터를 삭제합니다. \"누락\"은 처리되지 않은 항목을 대기열에 추가합니다. 얼굴 감지 작업이 완료된 후 얼굴 인식 작업을 진행하여 얼굴을 기존 인물이나 새 인물로 그룹화합니다.", - "facial_recognition_job_description": "감지된 얼굴을 인물로 그룹화합니다. 이 작업은 얼굴 감지 작업이 완료된 후 진행됩니다. \"초기화\"는 모든 얼굴의 그룹화를 다시 진행합니다. \"누락\"은 그룹화가 완료되지 않은 얼굴을 대기열에 추가합니다.", + "face_detection_description": "기계 학습을 통해 항목에 존재하는 얼굴을 감지합니다. 동영상의 경우 섬네일만 사용합니다. \"새로고침\"은 이미 처리된 항목을 포함한 모든 항목을 다시 처리합니다. \"초기화\"는 모든 얼굴 데이터를 삭제합니다. \"누락\"은 처리되지 않은 항목을 대기열에 추가합니다. 얼굴 감지 작업이 완료되면 얼굴 인식 작업이 진행되어 감지된 얼굴을 기존 인물이나 새 인물로 그룹화합니다.", + "facial_recognition_job_description": "감지된 얼굴을 인물로 그룹화합니다. 이 작업은 얼굴 감지 작업이 완료된 후 진행됩니다. \"초기화\"는 모든 얼굴의 그룹화를 다시 진행합니다. \"누락\"은 그룹화되지 않은 얼굴을 대기열에 추가합니다.", "failed_job_command": "{job} 작업에서 {command} 실패", "force_delete_user_warning": "경고: 사용자 및 사용자가 업로드한 모든 항목이 즉시 삭제됩니다. 이 작업은 되돌릴 수 없으며 파일을 복구할 수 없습니다.", - "forcing_refresh_library_files": "모든 파일을 다시 스캔하는 중...", + "forcing_refresh_library_files": "라이브러리의 모든 파일을 다시 스캔하는 중...", "image_format": "형식", "image_format_description": "WebP는 JPEG보다 파일 크기가 작지만 변환에 더 많은 시간이 소요됩니다.", "image_prefer_embedded_preview": "포함된 미리 보기 선호", "image_prefer_embedded_preview_setting_description": "가능한 경우 이미지 처리 시 RAW 사진에 포함된 미리 보기를 사용합니다. 포함된 미리 보기는 카메라에서 생성된 것으로 카메라마다 품질이 다릅니다. 일부 이미지의 경우 더 정확한 색상이 표현될 수 있지만 반대로 더 많은 아티팩트가 있을 수도 있습니다.", "image_prefer_wide_gamut": "넓은 색 영역 선호", "image_prefer_wide_gamut_setting_description": "섬네일 이미지에 Display P3을 사용합니다. 많은 색상을 표현할 수 있어 더 정확한 표현이 가능하지만, 오래된 브라우저를 사용하는 경우 이미지가 다르게 보일 수 있습니다. 색상 왜곡을 방지하기 위해 sRGB 이미지는 이 설정이 적용되지 않습니다.", - "image_preview_description": "메타데이터를 제거한 중간 크기 이미지, 한장씩 볼때나 기계학습에 사용됨", + "image_preview_description": "메타데이터를 제거한 중간 크기의 이미지, 단일 항목을 보는 경우 및 기계 학습에 사용됨", "image_preview_format": "미리 보기 형식", - "image_preview_quality_description": "1부터 100 사이의 미리보기 품질. 값이 높을수록 좋지만 파일 크기가 커져 앱의 반응성이 떨어질 수 있습니다. 또한 값이 낮으면 기계 학습의 품질이 떨어질 수 있습니다.", + "image_preview_quality_description": "1부터 100 사이의 미리보기 품질. 값이 높을수록 좋지만 파일 크기가 커져 앱의 반응성이 떨어질 수 있으며, 값이 낮으면 기계 학습의 품질이 떨어질 수 있습니다.", "image_preview_resolution": "미리 보기 해상도", "image_preview_resolution_description": "사진을 보거나 기계 학습을 실행할 때 사용되는 사진의 해상도를 설정합니다. 높은 해상도를 선택하면 세부 묘사의 손실을 최소화할 수 있지만, 인코딩 시간과 파일 크기가 증가하여 앱의 반응 속도가 느려질 수 있습니다.", "image_preview_title": "미리보기 설정", @@ -102,38 +110,38 @@ "library_tasks_description": "라이브러리 구성 및 확인 작업 수행", "library_watching_enable_description": "외부 라이브러리의 파일 변경 감시", "library_watching_settings": "라이브러리 감시 (실험 기능)", - "library_watching_settings_description": "변경된 파일을 자동으로 감지", - "logging_enable_description": "로깅 활성화", - "logging_level_description": "로깅이 활성화된 경우 사용할 로그 레벨을 선택합니다.", - "logging_settings": "로깅", + "library_watching_settings_description": "파일 변겅을 자동으로 감지", + "logging_enable_description": "로그 기록 활성화", + "logging_level_description": "활성화된 경우 사용할 로그 레벨을 선택합니다.", + "logging_settings": "로그 설정", "machine_learning_clip_model": "CLIP 모델", - "machine_learning_clip_model_description": "CLIP 모델의 종류는 이곳을 참조하세요. 한국어로 검색하려면 Multilingual CLIP 모델을 선택하세요. 변경 후 모든 항목에 대한 스마트 검색 작업을 다시 진행해야 합니다.", + "machine_learning_clip_model_description": "CLIP 모델의 종류는 이곳을 참조하세요. 한국어 등 다국어 검색을 사용하려면 Multilingual CLIP 모델을 선택하세요. 모델을 변경한 후 모든 항목에 대한 스마트 검색 작업을 다시 진행해야 합니다.", "machine_learning_duplicate_detection": "비슷한 항목 감지", "machine_learning_duplicate_detection_enabled": "비슷한 항목 감지 활성화", - "machine_learning_duplicate_detection_enabled_description": "비활성화된 경우에도 완전히 일치하는 항목은 여전히 감지됩니다.", + "machine_learning_duplicate_detection_enabled_description": "비활성화된 경우에도 완전히 동일한 항목은 중복 제거됩니다.", "machine_learning_duplicate_detection_setting_description": "CLIP 임베딩을 사용하여 비슷한 항목 찾기", "machine_learning_enabled": "기계 학습 활성화", - "machine_learning_enabled_description": "비활성화하는 경우 기계 학습 설정 여부와 관계없이 모든 기계 학습 기능이 비활성화됩니다.", + "machine_learning_enabled_description": "비활성화된 경우 아래 설정 여부와 관계없이 모든 기계 학습 기능이 비활성화됩니다.", "machine_learning_facial_recognition": "얼굴 인식", "machine_learning_facial_recognition_description": "이미지에서 얼굴 감지, 인식 및 그룹화", "machine_learning_facial_recognition_model": "얼굴 인식 모델", - "machine_learning_facial_recognition_model_description": "크기에 따라 내림차순으로 나열됩니다. 크기가 큰 모델은 느리고 메모리를 많이 사용하지만 더 나은 결과를 생성합니다. 변경 후 모든 항목의 얼굴 감지 작업을 다시 진행해야 합니다.", + "machine_learning_facial_recognition_model_description": "크기에 따라 내림차순으로 나열됩니다. 크기가 큰 모델은 느리고 메모리를 많이 사용하지만 더 나은 결과를 보입니다. 모델을 변경한 이후 모든 항목의 얼굴 감지 작업을 다시 진행해야 합니다.", "machine_learning_facial_recognition_setting": "얼굴 인식 활성화", "machine_learning_facial_recognition_setting_description": "비활성화된 경우 이미지에서 얼굴 인식을 진행하지 않으며, 탐색 페이지에 인물 목록이 표시되지 않습니다.", "machine_learning_max_detection_distance": "최대 감지 거리", "machine_learning_max_detection_distance_description": "두 이미지를 유사한 이미지로 간주하는 거리의 최댓값을 0.001에서 0.1 사이로 설정합니다. 값이 높으면 민감도가 낮아져 유사한 이미지로 감지하는 비율이 높아지나, 잘못된 결과를 보일 수 있습니다.", "machine_learning_max_recognition_distance": "최대 인식 거리", - "machine_learning_max_recognition_distance_description": "두 얼굴을 동일한 인물로 판단하는 거리의 최댓값을 0에서 2 사이로 설정합니다. 이 값을 낮추면 다른 인물을 동일한 인물로 판단하는 것을 방지할 수 있고, 값을 높이면 동일한 인물을 다른 인물로 판단하는 것을 방지할 수 있습니다. 두 인물을 병합하는 것이 하나의 인물을 둘로 나누는 것보다 쉽기에, 가능한 낮은 임계값을 사용하세요.", - "machine_learning_min_detection_score": "최소 탐지 점수", - "machine_learning_min_detection_score_description": "감지된 얼굴의 최소 신뢰 점수를 0에서 1 사이로 설정합니다. 값이 낮으면 많은 얼굴을 감지하지만 잘못된 결과를 보일 수 있습니다.", + "machine_learning_max_recognition_distance_description": "두 얼굴을 동일인으로 인식하는 거리의 최댓값을 0에서 2 사이로 설정합니다. 이 값을 낮추면 다른 인물을 동일인으로 인식하는 것을 방지할 수 있고, 값을 높이면 동일인을 다른 인물로 인식하는 것을 방지할 수 있습니다. 두 인물을 병합하는 것이 한 인물을 두 명으로 분리하는 것보다 쉬우므로, 가능한 낮은 임계값을 사용하세요.", + "machine_learning_min_detection_score": "최소 신뢰도 점수", + "machine_learning_min_detection_score_description": "감지된 얼굴의 최소 신뢰도 점수를 0에서 1 사이로 설정합니다. 값이 낮으면 많은 얼굴을 감지하지만 잘못된 결과를 보일 수 있습니다.", "machine_learning_min_recognized_faces": "최소 인식 얼굴", - "machine_learning_min_recognized_faces_description": "얼굴을 인식하여 인물을 생성하기 위한 최소 인식 얼굴 수를 설정합니다. 값이 높으면 얼굴 인식이 정확해지지만, 감지된 얼굴이 인물로 그룹화되지 않을 가능성이 증가합니다.", + "machine_learning_min_recognized_faces_description": "인물을 생성하기 위해 인식할 얼굴 수의 최솟값을 설정합니다. 값이 높으면 얼굴 인식이 정확해지지만 감지된 얼굴이 인물에 할당되지 않을 가능성이 증가합니다.", "machine_learning_settings": "기계 학습 설정", "machine_learning_settings_description": "기계 학습 기능 및 설정 관리", "machine_learning_smart_search": "스마트 검색", - "machine_learning_smart_search_description": "CLIP 임베딩을 사용하여 이미지 자연어 검색 지원", + "machine_learning_smart_search_description": "CLIP 임베딩으로 자연어를 사용하여 이미지 검색", "machine_learning_smart_search_enabled": "스마트 검색 활성화", - "machine_learning_smart_search_enabled_description": "비활성화 시 스마트 검색을 위한 이미지 처리를 진행하지 않습니다.", + "machine_learning_smart_search_enabled_description": "비활성화된 경우 스마트 검색을 위한 이미지 처리를 진행하지 않습니다.", "machine_learning_url_description": "기계 학습 서버 URL", "manage_concurrency": "동시성 관리", "manage_log_settings": "로그 설정 관리", @@ -141,7 +149,7 @@ "map_enable_description": "지도 기능 활성화", "map_gps_settings": "지도 및 GPS 설정", "map_gps_settings_description": "지도 및 GPS (역지오코딩) 설정 관리", - "map_implications": "지도 기능은 외부 타일 서비스(tiles.immich.clou를 사용합니다.", + "map_implications": "지도 기능은 외부 타일 서비스(tiles.immich.cloud)에 의존합니다.", "map_light_style": "라이트 스타일", "map_manage_reverse_geocoding_settings": "역지오코딩 설정 관리", "map_reverse_geocoding": "역지오코딩", @@ -162,7 +170,7 @@ "no_pattern_added": "추가된 규칙 없음", "note_apply_storage_label_previous_assets": "참고: 이전에 업로드한 항목에도 스토리지 레이블을 적용하려면 다음을 실행합니다,", "note_cannot_be_changed_later": "주의: 추후 변경할 수 없습니다!", - "note_unlimited_quota": "참고: 할당량을 설정하지 않으려면 0을 입력하세요.", + "note_unlimited_quota": "참고: 무제한 할당량의 경우 0을 입력하세요.", "notification_email_from_address": "보낸 사람 이메일", "notification_email_from_address_description": "보낸 사람의 이메일 주소, 예: \"Immich Photo Server \"", "notification_email_host_description": "이메일 서버의 호스트 (예: smtp.immich.app)", @@ -190,7 +198,7 @@ "oauth_issuer_url": "발급자 URL", "oauth_mobile_redirect_uri": "모바일 리다이렉트 URI", "oauth_mobile_redirect_uri_override": "모바일 리다이렉트 URI 재정의", - "oauth_mobile_redirect_uri_override_description": "OAuth 공급자가 '{callback}' 과 같은 모바일 URI를 제공하지 않는 경우 활성화하세요.", + "oauth_mobile_redirect_uri_override_description": "OAuth 공급자가 '{callback}'과 같은 모바일 URI를 제공하지 않는 경우 활성화하세요.", "oauth_profile_signing_algorithm": "사용자 정보 서명 알고리즘", "oauth_profile_signing_algorithm_description": "사용자 정보 서명에 사용되는 알고리즘을 선택합니다.", "oauth_scope": "스코프", @@ -203,7 +211,7 @@ "oauth_storage_quota_claim": "스토리지 할당량 선택", "oauth_storage_quota_claim_description": "스토리지 할당량을 사용자가 입력한 값으로 자동 설정합니다.", "oauth_storage_quota_default": "스토리지 할당량 기본값 (GiB)", - "oauth_storage_quota_default_description": "입력하지 않은 경우 사용할 GiB 단위의 기본 할당량 (할당량을 설정하지 않으려면 0 입력)", + "oauth_storage_quota_default_description": "입력하지 않은 경우 사용할 GiB 단위의 기본 할당량 (무제한 할당량의 경우 0 입력)", "offline_paths": "누락된 파일", "offline_paths_description": "외부 라이브러리의 항목이 아닌 파일을 수동으로 삭제한 경우 발생할 수 있습니다.", "password_enable_description": "이메일과 비밀번호로 로그인", @@ -213,11 +221,11 @@ "person_cleanup_job": "인물 정리", "quota_size_gib": "할당량 (GiB)", "refreshing_all_libraries": "모든 라이브러리 다시 스캔 중...", - "registration": "관리자 가입", - "registration_description": "첫 번째 사용자이기 때문에 관리자로 지정되었습니다. 관리 작업 및 사용자 생성이 가능합니다.", + "registration": "관리자 계정 생성", + "registration_description": "첫 번째로 생성되는 사용자는 관리자 권한을 부여받으며, 관리 및 사용자 생성이 가능합니다.", "removing_deleted_files": "누락된 파일을 제거하는 중...", "repair_all": "모두 수리", - "repair_matched_items": "동일한 항목 {count, plural, one {#개} other {#개}}를 확인했습니다.", + "repair_matched_items": "동일 항목 {count, plural, one {#개} other {#개}}를 확인했습니다.", "repaired_items": "항목 {count, plural, one {#개} other {#개}}를 수리했습니다.", "require_password_change_on_login": "첫 로그인 시 비밀번호 변경 요구", "reset_settings_to_default": "설정을 기본값으로 복원", @@ -232,10 +240,10 @@ "server_settings": "서버 설정", "server_settings_description": "서버 설정 관리", "server_welcome_message": "환영 메시지", - "server_welcome_message_description": "로그인 페이지에 표시되는 메시지를 설정합니다.", + "server_welcome_message_description": "로그인 페이지에 표시되는 메시지입니다.", "sidecar_job": "사이드카 메타데이터", "sidecar_job_description": "파일 시스템에서 사이드카 메타데이터 파일 탐색 및 동기화", - "slideshow_duration_description": "각 사진을 표시할 초 단위의 시간", + "slideshow_duration_description": "개별 사진이 표시되는 초 단위의 시간", "smart_search_job_description": "기계 학습을 진행하여 스마트 검색 기능 지원", "storage_template_date_time_description": "항목이 생성된 날짜의 타임스탬프를 날짜 및 시간 정보로 사용합니다.", "storage_template_date_time_sample": "시간 형식 예: {date}", @@ -258,7 +266,7 @@ "theme_custom_css_settings_description": "Immich에 적용할 사용자 정의 CSS(Cascading Style Sheets) 설정", "theme_settings": "테마 설정", "theme_settings_description": "Immich 웹 인터페이스 사용자 정의", - "these_files_matched_by_checksum": "동일한 체크섬을 가진 파일 목록입니다.", + "these_files_matched_by_checksum": "체크섬이 동일한 파일 목록입니다.", "thumbnail_generation_job": "섬네일 생성", "thumbnail_generation_job_description": "각 항목에 대한 큰 섬네일, 작은 섬네일, 흐린 섬네일 및 인물 섬네일 생성", "transcode_policy_description": "", @@ -278,7 +286,7 @@ "transcoding_audio_codec": "오디오 코덱", "transcoding_audio_codec_description": "Opus는 가장 좋은 품질의 옵션이지만 기기 및 소프트웨어가 오래된 경우 호환되지 않을 수 있습니다.", "transcoding_bitrate_description": "최대 비트레이트를 초과하는 동영상 또는 허용되지 않는 형식의 동영상", - "transcoding_codecs_learn_more": "이곳에서 사용되는 용어에 대한 자세한 내용은 FFmpeg 문서의 H.264 코덱, HEVC 코덱VP9 코덱을 참조하세요.", + "transcoding_codecs_learn_more": "여기에서 사용되는 용어에 대한 자세한 내용은 FFmpeg 문서의 H.264 코덱, HEVC 코덱VP9 코덱 항목을 참조하세요.", "transcoding_constant_quality_mode": "Constant quality mode", "transcoding_constant_quality_mode_description": "ICQ는 CQP보다 나은 성능을 보이나 일부 기기의 하드웨어 가속에서 지원되지 않을 수 있습니다. 이 옵션을 설정하면 품질 기반 인코딩 시 지정된 모드를 우선적으로 사용합니다. NVENC에서는 ICQ를 지원하지 않아 이 설정이 적용되지 않습니다.", "transcoding_constant_rate_factor": "Constant rate factor (-crf)", @@ -343,7 +351,7 @@ "user_settings_description": "사용자 설정 관리", "user_successfully_removed": "{email}이(가) 성공적으로 제거되었습니다.", "version_check_enabled_description": "버전 확인 활성화", - "version_check_implications": "버전 확인 기능은 주기적으로 github.com에 요청을 보냅니다.", + "version_check_implications": "주기적으로 github.com에 요청을 보내 최신 버전을 확인합니다.", "version_check_settings": "버전 확인", "version_check_settings_description": "최신 버전 알림 설정 관리", "video_conversion_job": "동영상 트랜스코드", @@ -358,10 +366,10 @@ "age_years": "{years, plural, other {#세}}", "album_added": "공유 앨범 초대", "album_added_notification_setting_description": "공유 앨범으로 초대를 받은 경우 이메일 알림 받기", - "album_cover_updated": "앨범 커버를 변경했습니다.", + "album_cover_updated": "앨범 커버 업데이트됨", "album_delete_confirmation": "{album} 앨범을 삭제하시겠습니까?", "album_delete_confirmation_description": "이 앨범을 공유한 경우 다른 사용자가 더 이상 앨범에 접근할 수 없습니다.", - "album_info_updated": "앨범 정보가 수정되었습니다.", + "album_info_updated": "앨범 정보 업데이트됨", "album_leave": "앨범에서 나가시겠습니까?", "album_leave_confirmation": "{album} 앨범에서 나가시겠습니까?", "album_name": "앨범 이름", @@ -371,8 +379,8 @@ "album_share_no_users": "이미 모든 사용자와 앨범을 공유 중이거나 다른 사용자가 없는 것 같습니다.", "album_updated": "항목 추가 알림", "album_updated_setting_description": "공유 앨범에 항목이 추가된 경우 이메일 알림 받기", - "album_user_left": "{album} 앨범에서 나왔습니다.", - "album_user_removed": "{user}님을 앨범에서 제거했습니다.", + "album_user_left": "{album} 앨범에서 나옴", + "album_user_removed": "{user}님을 앨범에서 제거함", "album_with_link_access": "링크가 있는 경우 누구나 이 앨범의 사진과 인물을 볼 수 있습니다.", "albums": "앨범", "albums_count": "앨범 {count, plural, one {{count, number}개} other {{count, number}개}}", @@ -386,7 +394,7 @@ "allow_public_user_to_upload": "모든 사용자의 업로드 허용", "anti_clockwise": "반시계 방향", "api_key": "API 키", - "api_key_description": "이 값은 한 번만 표시됩니다. 창을 닫기 전 반드시 복사하세요.", + "api_key_description": "이 값은 한 번만 표시됩니다. 창을 닫기 전 반드시 복사해주세요.", "api_key_empty": "키 이름은 비어 있을 수 없습니다.", "api_keys": "API 키", "app_settings": "앱 설정", @@ -401,9 +409,9 @@ "are_you_sure_to_do_this": "계속 진행하시겠습니까?", "asset_added_to_album": "앨범에 추가되었습니다.", "asset_adding_to_album": "앨범에 추가 중...", - "asset_description_updated": "설명이 변경되었습니다.", - "asset_filename_is_offline": "{filename} 항목이 누락되었습니다.", - "asset_has_unassigned_faces": "항목에 알 수 없는 인물이 있습니다.", + "asset_description_updated": "항목의 설명이 업데이트되었습니다.", + "asset_filename_is_offline": "{filename} 항목 누락됨", + "asset_has_unassigned_faces": "항목에 할당되지 않은 얼굴이 있음", "asset_hashing": "해시 확인 중...", "asset_offline": "누락된 항목", "asset_offline_description": "디스크에서 항목을 더이상 찾을 수 없습니다. 서버 관리자에게 연락하여 도움을 받으세요.", @@ -458,7 +466,7 @@ "change_password": "비밀번호 변경", "change_password_description": "첫 로그인이거나 비밀번호가 초기화되어 비밀번호를 설정해야 합니다. 아래에 새 비밀번호를 입력하세요.", "change_your_password": "비밀번호 변경", - "changed_visibility_successfully": "숨김 여부가 성공적으로 변경되었습니다.", + "changed_visibility_successfully": "표시 여부가 성공적으로 변경되었습니다.", "check_all": "모두 확인", "check_logs": "로그 확인", "choose_matching_people_to_merge": "병합할 인물 선택", @@ -549,7 +557,7 @@ "display_order": "표시 순서", "display_original_photos": "원본 이미지 표시", "display_original_photos_setting_description": "원본 사진이 웹과 호환되는 경우 섬네일 대신 원본을 표시합니다. 사진이 표시되는 속도가 느려질 수 있습니다.", - "do_not_show_again": "다시 표시하지 않음", + "do_not_show_again": "이 메시지를 다시 표시하지 않음", "documentation": "문서", "done": "완료", "download": "다운로드", @@ -572,24 +580,24 @@ }, "edit": "편집", "edit_album": "앨범 수정", - "edit_avatar": "프로필 편집", + "edit_avatar": "프로필 수정", "edit_date": "날짜 변경", "edit_date_and_time": "날짜 및 시간 변경", - "edit_exclusion_pattern": "제외 규칙 편집", - "edit_faces": "인물 변경", - "edit_import_path": "가져올 경로 편집", - "edit_import_paths": "가져올 경로 편집", + "edit_exclusion_pattern": "제외 규칙 수정", + "edit_faces": "얼굴 수정", + "edit_import_path": "가져올 경로 수정", + "edit_import_paths": "가져올 경로 수정", "edit_key": "키 수정", - "edit_link": "링크 편집", + "edit_link": "링크 수정", "edit_location": "위치 변경", "edit_name": "이름 변경", - "edit_people": "인물 변경", - "edit_tag": "태그 편집", + "edit_people": "인물 수정", + "edit_tag": "태그 수정", "edit_title": "제목 변경", "edit_user": "사용자 수정", - "edited": "펀집되었습니다.", + "edited": "공유 링크가 수정되었습니다.", "editor": "편집자", - "editor_close_without_save_prompt": "변경 사항이 반영되지 않습니다.", + "editor_close_without_save_prompt": "변경 사항이 저장되지 않습니다.", "editor_close_without_save_title": "편집을 종료하시겠습니까?", "editor_crop_tool_h2_aspect_ratios": "종횡비", "editor_crop_tool_h2_rotation": "회전", @@ -597,41 +605,41 @@ "empty": "", "empty_album": "", "empty_trash": "휴지통 비우기", - "empty_trash_confirmation": "휴지통을 비우시겠습니까? 휴지통에 있는 모든 항목이 Immich에서 영구적으로 제거됩니다. 이 작업은 되돌릴 수 없습니다!", + "empty_trash_confirmation": "휴지통을 비우시겠습니까? 휴지통에 있는 모든 항목이 Immich에서 영구적으로 삭제됩니다. 이 작업은 되돌릴 수 없습니다!", "enable": "활성화", "enabled": "활성화됨", "end_date": "종료일", "error": "오류", - "error_loading_image": "사진을 불러오는 중 문제가 발생했습니다.", + "error_loading_image": "이미지 로드 오류", "error_title": "오류 - 문제가 발생했습니다", "errors": { "cannot_navigate_next_asset": "다음 항목으로 이동할 수 없습니다.", "cannot_navigate_previous_asset": "이전 항목으로 이동할 수 없습니다.", "cant_apply_changes": "변경 사항을 적용할 수 없습니다.", "cant_change_activity": "활동을 {enabled, select, true {비활성화} other {활성화}}할 수 없습니다.", - "cant_change_asset_favorite": "즐겨찾기 상태를 변경할 수 없습니다.", + "cant_change_asset_favorite": "즐겨찾기에 추가/제거할 수 없습니다.", "cant_change_metadata_assets_count": "항목 {count, plural, one {#개} other {#개}}의 메타데이터를 변경할 수 없습니다.", - "cant_get_faces": "얼굴을 불러올 수 없습니다.", - "cant_get_number_of_comments": "댓글의 개수를 불러올 수 없습니다.", - "cant_search_people": "인물을 검색할 수 없습니다.", - "cant_search_places": "장소를 검색할 수 없습니다.", + "cant_get_faces": "얼굴을 불러올 수 없음", + "cant_get_number_of_comments": "댓글 수를 불러올 수 없음", + "cant_search_people": "인물을 검색할 수 없음", + "cant_search_places": "장소를 검색할 수 없음", "cleared_jobs": "{job} 작업 중단됨", - "error_adding_assets_to_album": "앨범에 항목을 추가하는 중 문제가 발생했습니다.", - "error_adding_users_to_album": "앨범에 사용자를 추가하는 중 문제가 발생했습니다.", - "error_deleting_shared_user": "공유한 사용자를 제거하는 중 문제가 발생했습니다.", - "error_downloading": "{filename} 다운로드 중 문제가 발생했습니다.", - "error_hiding_buy_button": "구매 버튼을 숨기는 중 문제가 발생했습니다.", - "error_removing_assets_from_album": "앨범에서 항목을 제거하는 중 문제가 발생했습니다. 콘솔에서 세부 정보를 확인하세요.", - "error_selecting_all_assets": "모든 항목을 선택하는 중 문제가 발생했습니다.", + "error_adding_assets_to_album": "앨범에 항목을 추가하던 중 오류가 발생했습니다.", + "error_adding_users_to_album": "앨범에 사용자를 추가하던 중 오류가 발생했습니다.", + "error_deleting_shared_user": "공유된 사용자를 제거하던 중 오류가 발생했습니다.", + "error_downloading": "{filename} 다운로드 오류", + "error_hiding_buy_button": "구매 버튼을 숨기던 중 오류가 발생했습니다.", + "error_removing_assets_from_album": "앨범에서 항목을 제거하던 중 오류가 발생했습니다. 콘솔에서 세부 정보를 확인하세요.", + "error_selecting_all_assets": "모든 항목을 선택하던 중 오류가 발생했습니다.", "exclusion_pattern_already_exists": "이 제외 규칙은 이미 존재합니다.", "failed_job_command": "{job} 작업 {command} 실패", "failed_to_create_album": "앨범을 생성하지 못했습니다.", "failed_to_create_shared_link": "공유 링크를 생성하지 못했습니다.", - "failed_to_edit_shared_link": "공유 링크를 편집하지 못했습니다.", - "failed_to_get_people": "인물을 불러오지 못했습니다.", - "failed_to_load_asset": "항목을 불러오지 못했습니다.", - "failed_to_load_assets": "항목을 불러오지 못했습니다.", - "failed_to_load_people": "인물을 불러오지 못했습니다.", + "failed_to_edit_shared_link": "공유 링크를 수정하지 못했습니다.", + "failed_to_get_people": "인물 로드 실패", + "failed_to_load_asset": "항목 로드 실패", + "failed_to_load_assets": "항목 로드 실패", + "failed_to_load_people": "인물 로드 실패", "failed_to_remove_product_key": "제품 키를 제거하지 못했습니다.", "failed_to_stack_assets": "스택을 만들지 못했습니다.", "failed_to_unstack_assets": "스택을 해제하지 못했습니다.", @@ -652,14 +660,14 @@ "unable_to_archive_unarchive": "{archived, select, true {보관함으로 항목을 이동할} other {보관함에서 항목을 제거할}} 수 없습니다.", "unable_to_change_album_user_role": "사용자의 역할을 변경할 수 없습니다.", "unable_to_change_date": "날짜를 변경할 수 없습니다.", - "unable_to_change_favorite": "즐겨찾기 상태를 변경할 수 없습니다.", + "unable_to_change_favorite": "즐겨찾기에 추가/제거할 수 없습니다.", "unable_to_change_location": "위치를 변경할 수 없습니다.", "unable_to_change_password": "비밀번호를 변경할 수 없습니다.", - "unable_to_change_visibility": "인물 {count, plural, one {#명} other {#명}}의 숨김 여부를 변경할 수 없습니다.", + "unable_to_change_visibility": "인물 {count, plural, one {#명} other {#명}}의 표시 여부를 변경할 수 없음", "unable_to_check_item": "", "unable_to_check_items": "", "unable_to_complete_oauth_login": "OAuth 로그인을 완료할 수 없습니다.", - "unable_to_connect": "연결할 수 없습니다.", + "unable_to_connect": "연결할 수 없음", "unable_to_connect_to_server": "서버에 연결할 수 없습니다.", "unable_to_copy_to_clipboard": "클립보드에 복사할 수 없습니다. https를 통해 접속 중인지 확인하세요.", "unable_to_create_admin_account": "관리자 계정을 생성할 수 없습니다.", @@ -668,18 +676,18 @@ "unable_to_create_user": "사용자를 생성할 수 없습니다.", "unable_to_delete_album": "앨범을 삭제할 수 없습니다.", "unable_to_delete_asset": "항목을 삭제할 수 없습니다.", - "unable_to_delete_assets": "항목을 삭제하는 중 문제가 발생했습니다.", + "unable_to_delete_assets": "항목 삭제 중 오류 발생", "unable_to_delete_exclusion_pattern": "제외 규칙을 삭제할 수 없습니다.", - "unable_to_delete_import_path": "가져올 경로를 삭제할 수 없습니다.", + "unable_to_delete_import_path": "가져오기 경로를 삭제할 수 없습니다.", "unable_to_delete_shared_link": "공유 링크를 삭제할 수 없습니다.", "unable_to_delete_user": "사용자를 삭제할 수 없습니다.", "unable_to_download_files": "파일을 다운로드할 수 없습니다.", - "unable_to_edit_exclusion_pattern": "제외 규칙을 편집할 수 없습니다.", - "unable_to_edit_import_path": "가져올 경로를 편집할 수 없습니다.", + "unable_to_edit_exclusion_pattern": "제외 규칙을 수정할 수 없습니다.", + "unable_to_edit_import_path": "가져오기 경로를 수정할 수 없습니다.", "unable_to_empty_trash": "휴지통을 비울 수 없습니다.", "unable_to_enter_fullscreen": "전체 화면으로 전환할 수 없습니다.", - "unable_to_exit_fullscreen": "전체 화면을 종료할 수 없습니다.", - "unable_to_get_comments_number": "댓글의 개수를 불러올 수 없습니다.", + "unable_to_exit_fullscreen": "전체 화면에서 나갈 수 없습니다.", + "unable_to_get_comments_number": "댓글 수를 불러올 수 없습니다.", "unable_to_get_shared_link": "공유 링크를 불러오지 못했습니다.", "unable_to_hide_person": "인물을 숨길 수 없습니다.", "unable_to_link_motion_video": "모션 비디오를 연결할 수 없습니다", @@ -692,7 +700,7 @@ "unable_to_log_out_device": "기기에서 로그아웃할 수 없습니다.", "unable_to_login_with_oauth": "OAuth로 로그인할 수 없습니다.", "unable_to_play_video": "동영상을 재생할 수 없습니다.", - "unable_to_reassign_assets_existing_person": "항목을 {name, select, null {다른 인물에} other {#에}} 할당할 수 없습니다.", + "unable_to_reassign_assets_existing_person": "항목을 {name, select, null {다른 인물에게} other {{name}에게}} 할당할 수 없습니다.", "unable_to_reassign_assets_new_person": "항목을 새 인물에 할당할 수 없습니다.", "unable_to_refresh_user": "사용자를 새로 고칠 수 없습니다.", "unable_to_remove_album_users": "앨범에서 사용자를 제거할 수 없습니다.", @@ -708,7 +716,7 @@ "unable_to_reset_password": "비밀번호를 초기화할 수 없습니다.", "unable_to_resolve_duplicate": "비슷한 항목을 처리할 수 없습니다.", "unable_to_restore_assets": "항목을 복원할 수 없습니다.", - "unable_to_restore_trash": "휴지통을 복원할 수 없습니다.", + "unable_to_restore_trash": "휴지통에서 항목을 복원할 수 없음", "unable_to_restore_user": "사용자 삭제를 취소할 수 없습니다.", "unable_to_save_album": "앨범을 저장할 수 없습니다.", "unable_to_save_api_key": "API 키를 수정할 수 없습니다.", @@ -721,7 +729,7 @@ "unable_to_set_feature_photo": "대표 사진을 지정할 수 없습니다.", "unable_to_set_profile_picture": "프로필 사진을 설정할 수 없습니다.", "unable_to_submit_job": "작업을 수행할 수 없습니다.", - "unable_to_trash_asset": "휴지통으로 이동할 수 없습니다.", + "unable_to_trash_asset": "휴지통으로 항목을 이동할 수 없음", "unable_to_unlink_account": "계정 연결을 해제할 수 없습니다.", "unable_to_unlink_motion_video": "모션 비디오 연결을 해제할 수 없습니다.", "unable_to_update_album_cover": "앨범 커버를 변경할 수 없습니다.", @@ -729,7 +737,7 @@ "unable_to_update_library": "라이브러리를 업데이트할 수 없습니다.", "unable_to_update_location": "위치를 변경할 수 없습니다.", "unable_to_update_settings": "설정을 변경할 수 없습니다.", - "unable_to_update_timeline_display_status": "타임라인 표시 설정을 변경할 수 없습니다.", + "unable_to_update_timeline_display_status": "타임라인 표시 여부를 변경할 수 없습니다.", "unable_to_update_user": "사용자를 업데이트할 수 없습니다.", "unable_to_upload_file": "파일을 업로드할 수 없습니다." }, @@ -753,10 +761,10 @@ "face_unassigned": "알 수 없음", "failed_to_get_people": "인물 불러오기 실패", "favorite": "즐겨찾기", - "favorite_or_unfavorite_photo": "즐겨찾기 추가 및 제거", + "favorite_or_unfavorite_photo": "즐겨찾기 추가/제거", "favorites": "즐겨찾기", "feature": "", - "feature_photo_updated": "대표 사진이 설정되었습니다.", + "feature_photo_updated": "대표 사진 업데이트됨", "featurecollection": "", "features": "기능", "features_setting_description": "앱 기능 관리", @@ -852,6 +860,7 @@ "license_failed_activation": "라이선스를 활성화하지 못했습니다. 이메일로 발송된 키를 정확히 입력했는지 확인하세요!", "light": "라이트", "like_deleted": "좋아요가 삭제되었습니다.", + "link_motion_video": "모션 비디오 링크", "link_options": "링크 옵션", "link_to_oauth": "OAuth에 연결", "linked_oauth_account": "OAuth 계정이 연결되었습니다.", @@ -869,7 +878,8 @@ "longitude": "경도", "look": "보기", "loop_videos": "동영상 반복", - "loop_videos_description": "상세 보기에서 동영상을 자동으로 반복 재생합니다.", + "loop_videos_description": "상세 보기에서 자동으로 동영상을 반복 재생합니다.", + "main_branch_warning": "현재 개발 버전을 사용 중입니다. 정식 버전을 사용하는 것을 강력히 권장합니다!", "make": "제조사", "manage_shared_links": "공유 링크 관리", "manage_sharing_with_partners": "파트너와 공유 관리", @@ -891,10 +901,10 @@ "menu": "메뉴", "merge": "병합", "merge_people": "인물 병합", - "merge_people_limit": "한 번에 최대 5개의 얼굴만 병합할 수 있습니다.", + "merge_people_limit": "한 번에 최대 5개의 얼굴만 합칠 수 있습니다.", "merge_people_prompt": "인물들을 병합하시겠습니까? 이 작업은 되돌릴 수 없습니다.", - "merge_people_successfully": "인물을 성공적으로 병합했습니다.", - "merged_people_count": "인물 {count, plural, one {#명} other {#명}}을 병합했습니다.", + "merge_people_successfully": "인물을 성공적으로 합쳤습니다.", + "merged_people_count": "인물 {count, plural, one {#명} other {#명}}을 합쳤습니다.", "minimize": "최소화", "minute": "분", "missing": "누락", @@ -920,12 +930,12 @@ "no_albums_with_name_yet": "아직 해당하는 이름의 앨범이 없는 것 같습니다.", "no_albums_yet": "아직 앨범이 없는 것 같습니다.", "no_archived_assets_message": "사진과 동영상을 보관함으로 이동하여 목록에서 숨기기", - "no_assets_message": "이곳을 클릭하여 첫 이미지를 업로드하세요", + "no_assets_message": "여기를 클릭하여 첫 사진을 업로드하세요.", "no_duplicates_found": "비슷한 항목을 찾을 수 없습니다.", "no_exif_info_available": "EXIF 정보 없음", "no_explore_results_message": "더 많은 사진을 업로드하여 탐색 기능을 사용하세요.", "no_favorites_message": "즐겨찾기에 좋아하는 사진과 동영상을 추가하기", - "no_libraries_message": "외부 라이브러리를 생성하여 사진과 동영상 가져오기", + "no_libraries_message": "외부 라이브러리를 생성하여 기존 사진과 동영상을 확인하세요.", "no_name": "이름 없음", "no_places": "장소 없음", "no_results": "결과가 없습니다.", @@ -952,9 +962,9 @@ "onboarding_welcome_description": "몇 가지 일반적인 설정을 진행하겠습니다.", "onboarding_welcome_user": "{user}님, 환영합니다", "online": "온라인", - "only_favorites": "즐겨찾기만 표시", + "only_favorites": "즐겨찾기만", "only_refreshes_modified_files": "변경된 파일만 다시 스캔", - "open_in_map_view": "지도 뷰에서 보기", + "open_in_map_view": "지도 보기에서 열기", "open_in_openstreetmap": "OpenStreetMap에서 열기", "open_the_search_filters": "검색 필터 열기", "options": "옵션", @@ -988,7 +998,7 @@ "paused": "일시 정지됨", "pending": "진행 중", "people": "인물", - "people_edits_count": "인물 {count, plural, one {#명} other {#명}}을 변경했습니다.", + "people_edits_count": "인물 {count, plural, one {#명} other {#명}}을 수정했습니다.", "people_feature_description": "사진 및 동영상을 인물 그룹별로 탐색", "people_sidebar_description": "사이드바에 인물 링크 표시", "perform_library_tasks": "", @@ -1022,7 +1032,7 @@ "previous_memory": "이전 추억", "previous_or_next_photo": "이전 또는 다음 이미지로", "primary": "주요", - "privacy": "프라이버시", + "privacy": "개인 정보", "profile_image_of_user": "{user}님의 프로필 이미지", "profile_picture_set": "프로필 사진이 설정되었습니다.", "public_album": "공개 앨범", @@ -1040,7 +1050,7 @@ "purchase_button_select": "선택", "purchase_failed_activation": "등록하지 못했습니다. 이메일로 전송된 키를 정확히 입력했는지 확인하세요!", "purchase_individual_description_1": "개인 사용자용", - "purchase_individual_description_2": "서포터 배지 및 표시", + "purchase_individual_description_2": "서포터 배지", "purchase_individual_title": "개인", "purchase_input_suggestion": "제품 키를 보유하고 있나요? 아래에 제품 키를 입력하세요.", "purchase_license_subtitle": "Immich를 구매하여 지속적인 개발에 도움을 주세요.", @@ -1056,21 +1066,21 @@ "purchase_remove_server_product_key": "서버 제품 키 제거", "purchase_remove_server_product_key_prompt": "서버 제품 키를 제거하시겠습니까?", "purchase_server_description_1": "서버 전체에 적용", - "purchase_server_description_2": "서포터 배지 및 표시", + "purchase_server_description_2": "서포터 배지", "purchase_server_title": "서버", "purchase_settings_server_activated": "서버 제품 키는 관리자가 관리합니다.", "range": "", "rating": "등급", "rating_clear": "등급 초기화", "rating_count": "{count, plural, one {#점} other {#점}}", - "rating_description": "상세 정보에 EXIF의 등급 정보 표시", + "rating_description": "상세 정보 패널에 EXIF 등급 태그 표시", "raw": "", "reaction_options": "반응 옵션", "read_changelog": "변경 사항 보기", "reassign": "다시 할당", "reassigned_assets_to_existing_person": "항목 {count, plural, one {#개} other {#개}}가 {name, select, null {다른 인물에} other {{name}에}} 할당되었습니다.", "reassigned_assets_to_new_person": "항목 {count, plural, one {#개} other {#개}}가 새 인물에 할당되었습니다.", - "reassing_hint": "선택한 항목의 인물 변경", + "reassing_hint": "기존 인물에 선택한 항목 할당", "recent": "최근", "recent_searches": "최근 검색", "refresh": "새로고침", @@ -1081,8 +1091,8 @@ "refreshed": "새로고침이 완료되었습니다.", "refreshes_every_file": "기존 파일 및 새 파일 스캔", "refreshing_encoded_video": "인코딩을 다시 진행하는 중...", - "refreshing_faces": "얼굴 새로고침 중", - "refreshing_metadata": "메타데이터를 갱신하는 중...", + "refreshing_faces": "얼굴 새로고침 중...", + "refreshing_metadata": "메타데이터를 새로 고치는 중...", "regenerating_thumbnails": "섬네일을 다시 생성하는 중...", "remove": "제거", "remove_assets_album_confirmation": "앨범에서 항목 {count, plural, one {#개} other {#개}}를 제거하시겠습니까?", @@ -1101,14 +1111,14 @@ "removed_tagged_assets": "항목 {count, plural, one {#개} other {#개}}에서 태그를 제거함", "rename": "이름 바꾸기", "repair": "수리", - "repair_no_results_message": "추적되지 않거나 누락된 파일이 이곳에 표시됩니다.", + "repair_no_results_message": "추적되지 않거나 누락된 파일이 여기에 표시됩니다.", "replace_with_upload": "파일 바꾸기", "repository": "리포지터리", "require_password": "비밀번호 필요", "require_user_to_change_password_on_first_login": "사용자가 처음 로그인할 때 비밀번호를 변경하도록 요구", "reset": "초기화", "reset_password": "비밀번호 재설정", - "reset_people_visibility": "인물 숨김 여부 초기화", + "reset_people_visibility": "인물 표시 여부 초기화", "reset_settings_to_default": "", "reset_to_default": "기본값으로 복원", "resolve_duplicates": "비슷한 항목 확인", @@ -1207,12 +1217,12 @@ "show_and_hide_people": "인물 숨기기", "show_file_location": "파일 위치 표시", "show_gallery": "갤러리 표시", - "show_hidden_people": "숨긴 인물 표시", + "show_hidden_people": "숨겨진 인물 표시", "show_in_timeline": "타임라인에 표시", - "show_in_timeline_setting_description": "이 사용자의 사진 및 동영상을 타임라인에 표시", + "show_in_timeline_setting_description": "타임라인에 이 사용자의 사진과 동영상을 표시", "show_keyboard_shortcuts": "키보드 단축키 표시", "show_metadata": "메타데이터 표시", - "show_or_hide_info": "정보 표시 및 숨기기", + "show_or_hide_info": "정보 표시/숨기기", "show_password": "비밀번호 표시", "show_person_options": "인물 옵션 표시", "show_progress_bar": "진행 표시줄 표시", @@ -1222,7 +1232,7 @@ "show_supporter_badge_description": "서포터 배지 표시", "shuffle": "셔플", "sidebar": "사이드바", - "sidebar_display_description": "뷰 링크를 사이드바에 표시", + "sidebar_display_description": "보기 링크를 사이드바에 표시", "sign_out": "로그아웃", "sign_up": "로그인", "size": "크기", @@ -1243,7 +1253,7 @@ "stack_duplicates": "비슷한 항목 스택", "stack_select_one_photo": "스택의 대표 사진 선택", "stack_selected_photos": "선택한 이미지 스택", - "stacked_assets_count": "항목 {count, plural, one {#개} other {#개}}의 스택을 만들었습니다.", + "stacked_assets_count": "항목 {count, plural, one {#개} other {#개}} 스택됨", "stacktrace": "스택 추적", "start": "시작", "start_date": "시작일", @@ -1266,10 +1276,10 @@ "sync": "동기화", "tag": "태그", "tag_assets": "항목 태그", - "tag_created": "{tag} 태그가 생성되었습니다.", + "tag_created": "태그 생성됨: {tag}", "tag_feature_description": "사진 및 동영상을 주제별 그룹화된 태그로 탐색", "tag_not_found_question": "태그를 찾을 수 없나요? 새 태그를 생성하세요.", - "tag_updated": "{tag} 태그를 수정했습니다.", + "tag_updated": "태그 업데이트됨: {tag}", "tagged_assets": "항목 {count, plural, one {#개} other {#개}}에 태그를 적용함", "tags": "태그", "template": "템플릿", @@ -1295,7 +1305,7 @@ "trash_all": "모두 삭제", "trash_count": "{count, number}개 삭제", "trash_delete_asset": "휴지통 이동/삭제", - "trash_no_results_message": "휴지통으로 이동된 항목이 이곳에 표시됩니다.", + "trash_no_results_message": "삭제된 사진과 동영상이 여기에 표시됩니다.", "trashed_items_will_be_permanently_deleted_after": "휴지통으로 이동된 항목은 {days, plural, one {#일} other {#일}} 후 영구적으로 삭제됩니다.", "type": "형식", "unarchive": "보관함에서 제거", @@ -1307,6 +1317,7 @@ "unknown_album": "", "unknown_year": "알 수 없는 연도", "unlimited": "무제한", + "unlink_motion_video": "모션 비디오 링크 해제", "unlink_oauth": "OAuth 연결 해제", "unlinked_oauth_account": "OAuth 계정 연결이 해제되었습니다.", "unnamed_album": "이름 없는 앨범", @@ -1323,7 +1334,7 @@ "updated_password": "비밀번호가 변경되었습니다.", "upload": "업로드", "upload_concurrency": "업로드 동시성", - "upload_errors": "업로드가 완료되었습니다. 항목 {count, plural, one {#개} other {#개}}는 업로드하지 못했습니다. 업로드된 항목을 보려면 페이지를 새로고침하세요.", + "upload_errors": "업로드가 완료되었습니다. 항목 {count, plural, one {#개} other {#개}}를 업로드하지 못했습니다. 업로드된 항목을 보려면 페이지를 새로고침하세요.", "upload_progress": "전체 {total, number}개 중 {processed, number}개 완료, {remaining, number}개 대기 중", "upload_skipped_duplicates": "동일한 항목 {count, plural, one {#개} other {#개}}를 건너뛰었습니다.", "upload_status_duplicates": "중복", @@ -1348,8 +1359,8 @@ "version": "버전", "version_announcement_closing": "당신의 친구, Alex가", "version_announcement_message": "안녕하세요, 새 버전의 Immich를 사용할 수 있습니다. 자세한 내용은 릴리스 노트를 참조하세요. WatchTower 등의 자동 업데이트 기능을 사용하는 경우 의도하지 않은 동작을 방지하기 위해 docker-compose.yml.env 구성이 최신인지 확인하세요.", - "version_history": "버전 히스토리", - "version_history_item": "버전 {version}, {date} 설치됨", + "version_history": "버전 기록", + "version_history_item": "{date} 버전 {version} 설치", "video": "동영상", "video_hover_setting": "마우스 오버 재생", "video_hover_setting_description": "마우스를 동영상 위에 올리면 재생이 시작됩니다. 비활성화된 경우에도 재생 아이콘에 마우스를 올리면 재생이 시작됩니다.", @@ -1360,12 +1371,12 @@ "view_all": "모두 보기", "view_all_users": "모든 사용자 보기", "view_in_timeline": "타임라인에서 보기", - "view_links": "링크 보기", + "view_links": "링크 확인", "view_next_asset": "다음 항목 보기", "view_previous_asset": "이전 항목 보기", "view_stack": "스택 보기", "viewer": "뷰어", - "visibility_changed": "인물 {count, plural, one {#명} other {#명}}의 숨김 여부가 변경되었습니다.", + "visibility_changed": "인물 {count, plural, one {#명} other {#명}}의 표시 여부가 변경됨", "waiting": "대기", "warning": "경고", "week": "주", @@ -1375,5 +1386,5 @@ "years_ago": "{years, plural, one {#년} other {#년}} 전", "yes": "네", "you_dont_have_any_shared_links": "생성한 공유 링크가 없습니다.", - "zoom_image": "확대" + "zoom_image": "이미지 확대" } diff --git a/i18n/lt.json b/i18n/lt.json index 399ef31c3e3fe..b148ccb8e1c05 100644 --- a/i18n/lt.json +++ b/i18n/lt.json @@ -50,6 +50,7 @@ "failed_job_command": "Darbo {job} komanda {command} nepavyko", "force_delete_user_warning": "ĮSPĖJIMAS: Šis veiksmas iš karto pašalins naudotoją ir visą jo informaciją. Šis žingsnis nesugrąžinamas ir failų nebus galima atkurti.", "forcing_refresh_library_files": "Priverstinai atnaujinami visi failai bilbiotekoje", + "image_format": "Formatas", "image_format_description": "WebP sukuria mažesnius failus nei JPEG, bet lėčiau juos apdoroja.", "image_prefer_embedded_preview": "Pageidautinai rodyti įterptą peržiūrą", "image_prefer_embedded_preview_setting_description": "", @@ -60,6 +61,7 @@ "image_preview_resolution_description": "Naudojama peržiūrint vieną nuotrauką ir mašininiam mokymui. Didesnė rezoliucija gali išsaugoti daugiau detalių, bet ilgiau užtrukti apdoroti ir sumažinti programos greitumą.", "image_quality": "Kokybė", "image_quality_description": "Vaizdo kokybė nuo 1 iki 100. Aukštesnė kokybė yra geresnė, tačiau sukuriami didesni failai. Ši parinktis turi įtakos peržiūros ir miniatiūrų vaizdams.", + "image_resolution": "Rezoliucija", "image_settings": "Nuotraukos nustatymai", "image_settings_description": "Keisti sugeneruotų nuotraukų kokybę ir rezoliuciją", "image_thumbnail_format": "Miniatūros formatas", @@ -119,7 +121,7 @@ "manage_concurrency": "Tvarkyti lygiagretumą", "manage_log_settings": "", "map_dark_style": "Tamsioji tema", - "map_enable_description": "", + "map_enable_description": "Įgalinti žemėlapio funkcijas", "map_gps_settings": "Žemėlapio ir GPS nustatymai", "map_gps_settings_description": "Tvarkyti žemėlapio ir GPS (atvirkštinio geokodavimo) nustatymus", "map_light_style": "Šviesioji tema", @@ -132,9 +134,13 @@ "map_style_description": "", "metadata_extraction_job": "Metaduomenų nuskaitymas", "metadata_extraction_job_description": "Kiekvieno bibliotekos elemento metaduomenų nuskaitymas, tokių kaip GPS koordinatės, veidai ar rezoliucija", + "metadata_settings": "Metaduomenų nustatymai", + "metadata_settings_description": "Tvarkyti metaduomenų nustatymus", + "migration_job": "Migracija", "migration_job_description": "", "no_paths_added": "Keliai nepridėti", "no_pattern_added": "Šablonas nepridėtas", + "note_cannot_be_changed_later": "PASTABA: Vėliau to pakeisti negalima!", "notification_email_from_address": "", "notification_email_from_address_description": "", "notification_email_host_description": "", @@ -148,7 +154,7 @@ "notification_email_test_email_failed": "Nepavyko išsiųsti bandomojo el. laiško, patikrinkite savo nustatymus", "notification_email_test_email_sent": "Bandomasis el. laiškas buvo išsiųstas į {email}. Patikrinkite savo pašto dėžutę.", "notification_email_username_description": "", - "notification_enable_email_notifications": "", + "notification_enable_email_notifications": "Įgalinti el. pašto pranešimus", "notification_settings": "Pranešimų nustatymai", "notification_settings_description": "Tvarkyti pranešimų nustatymus, įskaitant el. pašto", "oauth_auto_launch": "Paleisti automatiškai", @@ -179,15 +185,18 @@ "password_settings_description": "Tvarkyti prisijungimo slaptažodžiu nustatymus", "paths_validated_successfully": "Visi keliai patvirtinti sėkmingai", "refreshing_all_libraries": "Perkraunamos visos bibliotekos", + "registration": "Administratoriaus registracija", "registration_description": "Kadangi esate pirmasis šio sistemos naudotojas, jums bus priskirta administratoriaus rolė, ir būsite atsakingas už administracines užduotis ir papildomų naudotojų kūrimą.", "repair_all": "Pataisyti visus", "require_password_change_on_login": "Reikalauti, kad naudotojas pasikeistų slaptažodį po pirmojo prisijungimo", "reset_settings_to_default": "Atstatyti nustatymus į numatytuosius", + "reset_settings_to_recent_saved": "Nustatymų atstatymas į neseniai išsaugotus nustatymus", + "send_welcome_email": "Siųsti sveikinimo el. laišką", "server_external_domain_settings": "Išorinis domenas", "server_external_domain_settings_description": "", "server_settings": "Serverio nustatymai", "server_settings_description": "Tvarkyti serverio nustatymus", - "server_welcome_message": "", + "server_welcome_message": "Sveikinimo pranešimas", "server_welcome_message_description": "Žinutė, rodoma prisijungimo puslapyje.", "sidecar_job_description": "", "slideshow_duration_description": "", @@ -200,7 +209,7 @@ "storage_template_settings_description": "", "system_settings": "Sistemos nustatymai", "tag_cleanup_job": "Žymų išvalymas", - "theme_custom_css_settings": "", + "theme_custom_css_settings": "Individualizuotas CSS", "theme_custom_css_settings_description": "", "theme_settings": "Temos nustatymai", "theme_settings_description": "", @@ -227,9 +236,9 @@ "transcoding_constant_rate_factor": "", "transcoding_constant_rate_factor_description": "", "transcoding_disabled_description": "", - "transcoding_hardware_acceleration": "", + "transcoding_hardware_acceleration": "Techninės įrangos spartinimas", "transcoding_hardware_acceleration_description": "", - "transcoding_hardware_decoding": "", + "transcoding_hardware_decoding": "Aparatinis dekodavimas", "transcoding_hardware_decoding_setting_description": "", "transcoding_hevc_codec": "HEVC kodekas", "transcoding_max_b_frames": "", @@ -249,7 +258,7 @@ "transcoding_settings": "", "transcoding_settings_description": "", "transcoding_target_resolution": "", - "transcoding_target_resolution_description": "", + "transcoding_target_resolution_description": "Didesnės skiriamosios gebos gali išsaugoti daugiau detalių, tačiau jas koduoti užtrunka ilgiau, failų dydžiai yra didesni ir gali sumažėti programos jautrumas.", "transcoding_temporal_aq": "", "transcoding_temporal_aq_description": "", "transcoding_threads": "", @@ -263,7 +272,7 @@ "transcoding_two_pass_encoding_setting_description": "", "transcoding_video_codec": "Video kodekas", "transcoding_video_codec_description": "", - "trash_enabled_description": "", + "trash_enabled_description": "Įgalinti šiukšliadėžės funkcijas", "trash_number_of_days": "Dienų skaičius", "trash_number_of_days_description": "", "trash_settings": "Šiukšliadėžės nustatymai", @@ -1050,8 +1059,10 @@ "unknown_year": "Nežinomi metai", "unlink_oauth": "", "unlinked_oauth_account": "", + "unnamed_album_delete_confirmation": "Ar tikrai norite ištrinti šį albumą?", "unsaved_change": "Neišsaugoti pakeitimai", "unselect_all": "", + "unselect_all_duplicates": "Atžymėti visus dublikatus", "unstack": "Išgrupuoti", "unstacked_assets_count": "{count, plural, one {Išgrupuotas # elementas} few {Išgrupuoti # elementai} other {Išgrupuota # elementų}}", "up_next": "", @@ -1062,7 +1073,7 @@ "upload_status_duplicates": "Dublikatai", "upload_status_errors": "Klaidos", "upload_status_uploaded": "Įkelta", - "url": "", + "url": "URL", "usage": "", "user": "Naudotojas", "user_id": "Naudotojo ID", @@ -1070,19 +1081,19 @@ "username": "Naudotojo vardas", "users": "Naudotojai", "utilities": "Priemonės", - "validate": "", + "validate": "Validuoti", "variables": "Kintamieji", "version": "Versija", "version_announcement_closing": "Tavo draugas, Alex", "version_history": "Versijų istorija", "version_history_item": "Versija {version} įdiegta {date}", "video": "Vaizdo įrašas", - "video_hover_setting_description": "", + "video_hover_setting_description": "Atkurti vaizdo įrašo miniatiūrą, kai pelė užvedama ant elemento. Net ir išjungus, atkūrimą galima pradėti užvedus pelės žymeklį ant atkūrimo piktogramos.", "videos": "Video", "videos_count": "{count, plural, one {# vaizdo įrašas} few {# vaizdo įrašai} other {# vaizdo įrašų}}", "view": "Rodyti", "view_album": "Rodyti albumą", - "view_all": "", + "view_all": "Peržiūrėti viską", "view_all_users": "Peržiūrėti visus naudotojus", "view_links": "Rodyti nuorodas", "view_next_asset": "", @@ -1092,7 +1103,7 @@ "waiting": "Laukiama", "warning": "Įspėjimas", "week": "Savaitė", - "welcome_to_immich": "", + "welcome_to_immich": "Sveiki atvykę į Immich", "year": "Metai", "yes": "Taip", "zoom_image": "Priartinti vaizdą" diff --git a/i18n/lv.json b/i18n/lv.json index 614309f857c08..b2c57d7595815 100644 --- a/i18n/lv.json +++ b/i18n/lv.json @@ -2,7 +2,7 @@ "about": "Par", "account": "Konts", "account_settings": "Konta iestatījumi", - "acknowledge": "Atzīt", + "acknowledge": "Pieņemt", "action": "Darbība", "actions": "Darbības", "active": "Aktīvs", @@ -446,6 +446,7 @@ "error": "", "error_loading_image": "", "errors": { + "cant_get_faces": "Nevar iegūt sejas", "cant_search_people": "Neizdevās veikt peronu meklēšanu", "failed_to_create_album": "Neizdevās izveidot albumu", "unable_to_add_album_users": "", @@ -715,16 +716,26 @@ "primary": "", "profile_picture_set": "", "public_share": "", + "purchase_button_never_show_again": "Nekad vairs nerādīt", + "purchase_button_reminder": "Atgādināt man pēc 30 dienām", + "purchase_button_remove_key": "Noņemt atslēgu", + "purchase_button_select": "Izvēlēties", + "purchase_individual_description_2": "Atbalstītāja statuss", + "purchase_panel_title": "Atbalstīt projektu", + "purchase_remove_product_key": "Noņemt produkta atslēgu", + "purchase_server_description_1": "Visam serverim", + "purchase_server_description_2": "Atbalstītāja statuss", + "purchase_server_title": "Serveris", "range": "", "raw": "", "reaction_options": "", - "read_changelog": "", + "read_changelog": "Lasīt izmaiņu sarakstu", "recent": "", "recent_searches": "", "refresh": "", "refreshed": "", "refreshes_every_file": "", - "remove": "", + "remove": "Noņemt", "remove_deleted_assets": "", "remove_from_album": "Noņemt no albuma", "remove_from_favorites": "Noņemt no izlases", @@ -734,9 +745,9 @@ "removed_from_archive": "Noņēma no arhīva", "removed_from_favorites": "Noņēma no izlases", "rename": "Pārsaukt", - "repair": "", + "repair": "Remonts", "repair_no_results_message": "", - "replace_with_upload": "", + "replace_with_upload": "Aizstāt ar augšupielādi", "require_password": "", "require_user_to_change_password_on_first_login": "Pieprasīt lietotājam mainīt paroli pēc pirmās pieteikšanās", "reset": "", @@ -764,8 +775,9 @@ "scan_new_library_files": "", "scan_settings": "", "search": "Meklēt", - "search_albums": "", + "search_albums": "Meklēt albumus", "search_by_context": "", + "search_by_filename_example": "piemēram, IMG_1234.JPG vai PNG", "search_camera_make": "", "search_camera_model": "", "search_city": "", @@ -780,7 +792,7 @@ "search_type": "", "search_your_photos": "Meklēt Jūsu fotoattēlus", "searching_locales": "", - "second": "", + "second": "Sekunde", "select_album_cover": "Izvēlieties albuma vāciņu", "select_all": "", "select_all_duplicates": "Atlasīt visus dublikātus", @@ -795,6 +807,7 @@ "server": "", "server_online": "Serveris tiešsaistē", "server_stats": "Servera statistika", + "server_version": "Servera versija", "set": "", "set_as_album_cover": "", "set_as_profile_picture": "", @@ -810,10 +823,13 @@ "shared_links": "Kopīgotās saites", "sharing": "Kopīgošana", "sharing_sidebar_description": "", - "show_album_options": "", - "show_file_location": "", - "show_gallery": "", - "show_hidden_people": "", + "show_album_options": "Rādīt albuma iespējas", + "show_albums": "Rādīt albumus", + "show_all_people": "Rādīt visus cilvēkus", + "show_and_hide_people": "Rādīt un slēpt cilvēkus", + "show_file_location": "Rādīt faila atrašanās vietu", + "show_gallery": "Rādīt galeriju", + "show_hidden_people": "Rādīt paslēptos cilvēkus", "show_in_timeline": "", "show_in_timeline_setting_description": "", "show_keyboard_shortcuts": "", @@ -823,9 +839,11 @@ "show_person_options": "", "show_progress_bar": "", "show_search_options": "", + "show_supporter_badge": "Atbalstītāja nozīmīte", + "show_supporter_badge_description": "Rādīt atbalstītāja nozīmīti", "shuffle": "", "sign_up": "", - "size": "", + "size": "Izmērs", "skip_to_content": "", "slideshow": "Slīdrāde", "slideshow_settings": "Slīdrādes iestatījumi", @@ -854,7 +872,7 @@ "support": "Atbalsts", "support_and_feedback": "Atbalsts un atsauksmes", "swap_merge_direction": "", - "sync": "", + "sync": "Sinhronizēt", "template": "", "theme": "Dizains", "theme_selection": "", diff --git a/i18n/ms.json b/i18n/ms.json index b80aa1e185a6a..cec6f00273728 100644 --- a/i18n/ms.json +++ b/i18n/ms.json @@ -31,6 +31,92 @@ "asset_offline_description": "Aset pustaka luaran ini tidak lagi ditemui pada cakera dan telah dialihkan ke sampah. Jika fail telah dialihkan dalam pustaka, semak garis masa anda untuk aset baharu yang sepadan. Untuk memulihkan aset ini, sila pastikan bahawa laluan fail di bawah boleh diakses oleh Immich dan mengimbas pustaka.", "authentication_settings": "Tetapan Pengesahan", "authentication_settings_description": "Urus kata laluan, OAuth dan tetapan pengesahan lain", - "authentication_settings_disable_all": "Adakah anda pasti mahu melumpuhkan semua kaedah log masuk? Log masuk akan dilumpuhkan sepenuhnya." + "authentication_settings_disable_all": "Adakah anda pasti mahu melumpuhkan semua kaedah log masuk? Log masuk akan dilumpuhkan sepenuhnya.", + "authentication_settings_reenable": "Untuk menghidupkan semula, guna Arahan Pelayan.", + "background_task_job": "Tugas Latar Belakang", + "check_all": "Tanda Semua", + "cleared_jobs": "Kerja telah dibersihkan untuk: {job}", + "config_set_by_file": "Konfigurasi kini ditetapkan oleh fail konfigurasi", + "confirm_delete_library": "Adakah anda pasti mahu memadamkan {library}?", + "confirm_delete_library_assets": "Adakah anda pasti mahu memadamkan pustaka ini? Ini akan memadam {count, plural, one {# aset yang terkandung} other {semua # aset yang terkandung}} daripada Immich dan tidak boleh dibuat asal. Fail akan kekal pada disk.", + "confirm_email_below": "Untuk mengesahkan, sila taip \"{email}\" dibawah", + "confirm_reprocess_all_faces": "Adakah anda pasti mahu memproses semula semua wajah? Ini juga akan membersihkan orang bernama.", + "confirm_user_password_reset": "Adakah anda pasti mahu menetapkan semula kata laluan {user}?", + "create_job": "Cipta tugas", + "disable_login": "Lumpuhkan fungsi log masuk", + "duplicate_detection_job_description": "Jalankan pembelajaran mesin pada aset untuk mengesan imej yang serupa. Bergantung pada Carian Pintar", + "exclusion_pattern_description": "Corak pengecualian membolehkan anda mengabaikan fail dan folder semasa mengimbas pustaka anda. Ini berguna jika anda mempunyai folder yang mengandungi fail yang anda tidak mahu import, seperti fail RAW.", + "external_library_created_at": "Pustaka luaran (dicipta pada {date})", + "external_library_management": "Pengurusan Perpustakaan Luar", + "face_detection": "Pengesanan wajah", + "face_detection_description": "Kesan wajah dalam aset menggunakan pembelajaran mesin. Untuk video, hanya lakaran kecil dipertimbangkan. \"Segar Semula\" memproses semula semua aset. \"Tetapkan Semula\" juga mengosongkan semua data wajah semasa. \"Hilang\" baris gilir aset yang belum diproses lagi. Wajah yang dikesan akan beratur untuk Pengecaman Wajah selepas Pengesanan Wajah selesai, menghimpunkannya kepada orang sedia ada atau baharu.", + "facial_recognition_job_description": "Kumpulan wajah yang dikesan ke dalam orang. Langkah ini dijalankan selepas Pengesanan Wajah selesai. \"Tetapkan semula\" mengelompokkan semula semua wajah. \"Hilang\" jalankan proses pada wajah yang tidak mempunyai orang yang ditetapkan.", + "failed_job_command": "Perintah {command} gagal untuk kerja: {job}", + "force_delete_user_warning": "AMARAN: Ini akan mengalih keluar pengguna dan semua aset dengan serta-merta. Ia tidak boleh dibuat asal dan fail tidak boleh dipulihkan.", + "forcing_refresh_library_files": "Memaksa muat semula semua fail perpustakaan", + "image_format": "Format", + "image_format_description": "WebP menghasilkan fail yang lebih kecil daripada JPEG, tetapi lebih perlahan untuk mengekod.", + "image_prefer_embedded_preview": "Cadangkan pratonton terbenam", + "image_prefer_embedded_preview_setting_description": "Gunakan pratonton terbenam dalam foto RAW sebagai input kepada pemprosesan imej apabila tersedia. Cara ini boleh menghasilkan warna yang lebih tepat untuk sesetengah imej, tetapi kualiti pratonton bergantung pada kamera dan imej mungkin mempunyai lebih banyak artifak mampatan.", + "image_prefer_wide_gamut": "Cadangkan warna gamut yang luas", + "image_prefer_wide_gamut_setting_description": "Gunakan Paparan P3 untuk lakaran kenit. Ini lebih baik mengekalkan kerancakan imej dengan ruang warna yang luas, tetapi imej mungkin kelihatan berbeza pada peranti lama dengan versi penyemak imbas lama. Imej sRGB disimpan sebagai sRGB untuk mengelakkan peralihan warna.", + "image_preview_description": "Imej bersaiz sederhana dengan metadata yang dilucutkan, digunakan semasa melihat aset tunggal dan untuk pembelajaran mesin", + "image_preview_quality_description": "Kualiti pratonton dari 1-100. Lebih tinggi adalah lebih baik, tetapi menghasilkan fail yang lebih besar dan boleh mengurangkan responsif apl. Menetapkan nilai yang rendah boleh menjejaskan kualiti pembelajaran mesin.", + "image_preview_title": "Tetapan Pratonton", + "image_quality": "Kualiti", + "image_resolution": "Resolusi", + "image_resolution_description": "Resolusi yang lebih tinggi boleh meningkatkan ketajaman imej tetapi mengambil masa yang lebih lama untuk mengekod, mempunyai saiz fail yang lebih besar dan boleh mengurangkan responsif apl.", + "image_settings": "Tetapan Imej", + "image_settings_description": "Urus kualiti dan resolusi imej yang dihasilkan", + "image_thumbnail_description": "Lakaran kecil dengan metadata yang dilucutkan, digunakan semasa melihat kumpulan foto seperti garis masa utama", + "image_thumbnail_quality_description": "Kualiti lakaran kenit daripada 1-100. Lebih tinggi adalah lebih baik, tetapi menghasilkan fail yang lebih besar dan boleh mengurangkan responsif apl.", + "image_thumbnail_title": "Tetapan Lakaran Kenit", + "job_concurrency": "Konkurensi {job}", + "job_created": "Tugas yang dicipta", + "job_not_concurrency_safe": "Konkurensi tugas ini tidak selamat.", + "job_settings": "Tetapan Tugas", + "job_settings_description": "Urus konkurensi tugas", + "job_status": "Status Tugasan", + "jobs_delayed": "{jobCount, plural, other {# tertangguh}}", + "jobs_failed": "{jobCount, plural, other {# gagal}}", + "library_created": "Pustaka dicipta: {library}", + "library_cron_expression": "Ungkapan Cron", + "library_cron_expression_description": "Tetapkan selang pengimbasan menggunakan format cron. Untuk maklumat lanjut sila rujuk cth. Crontab Guru", + "library_cron_expression_presets": "Pratetap ungkapan Cron", + "library_deleted": "Pustaka dipadamkan", + "library_import_path_description": "Tentukan folder untuk diimport. Folder ini, termasuk subfolder, akan diimbas untuk imej dan video.", + "library_scanning": "Pengimbasan Berkala", + "library_scanning_description": "Konfigurasikan pengimbasan perpustakaan berkala", + "library_scanning_enable_description": "Dayakan pengimbasan perpustakaan berkala", + "library_settings": "Perpustakaan Luaran", + "library_settings_description": "Urus tetapan perpustakaan luaran", + "library_tasks_description": "Laksanakan tugas perpustakaan", + "library_watching_enable_description": "Perhatikan perpustakaan luaran untuk perubahan fail", + "library_watching_settings": "Perhati perpustakaan (EKSPERIMEN)", + "library_watching_settings_description": "Perhati fail yang diubah secara automatik", + "logging_enable_description": "Dayakan pengelogan", + "logging_level_description": "Apabila didayakan, tahap log yang hendak digunakan.", + "logging_settings": "Log", + "machine_learning_clip_model": "Model CLIP", + "machine_learning_clip_model_description": "Nama model CLIP disenaraikan di sini. Ambil perhatian bahawa anda mesti menjalankan semula tugas 'Carian Pintar' untuk semua imej selepas menukar model.", + "machine_learning_duplicate_detection": "Pengesanan Pendua", + "machine_learning_duplicate_detection_enabled": "Dayakan pengesanan pendua", + "machine_learning_duplicate_detection_enabled_description": "Jika dilumpuhkan, aset yang betul-betul serupa masih akan dinyahduakan.", + "machine_learning_duplicate_detection_setting_description": "Gunakan pembenaman CLIP untuk mencari kemungkinan pendua", + "machine_learning_enabled": "Dayakan pembelajaran mesin", + "machine_learning_enabled_description": "Jika dilumpuhkan, semua ciri Pembelajaran Mesin akan dilumpuhkan tanpa mengira tetapan di bawah.", + "machine_learning_facial_recognition": "Pengecaman Wajah", + "machine_learning_facial_recognition_description": "Mengesan, mengecam dan mengumpulkan wajah dalam imej", + "machine_learning_facial_recognition_model": "Model pengecaman wajah", + "machine_learning_facial_recognition_model_description": "Model disenaraikan dalam susunan saiz menurun. Model yang lebih besar adalah lebih perlahan dan menggunakan lebih banyak memori, tetapi menghasilkan hasil yang lebih baik. Ambil perhatian bahawa anda mesti menjalankan semula kerja Pengesanan Wajah untuk semua imej apabila menukar model.", + "machine_learning_facial_recognition_setting": "Dayakan pengecaman wajah", + "machine_learning_facial_recognition_setting_description": "Jika dilumpuhkan, imej tidak akan dikodkan untuk pengecaman wajah dan tidak akan mengisi bahagian Orang dalam halaman Teroka.", + "machine_learning_max_detection_distance": "Jarak pengesanan maksimum", + "machine_learning_max_detection_distance_description": "Jarak maksimum antara dua imej untuk menganggapnya sebagai pendua, antara 0.001-0.1. Nilai yang lebih tinggi akan mengesan lebih banyak pendua, tetapi mungkin menghasilkan positif palsu.", + "machine_learning_max_recognition_distance": "Jarak pengecaman maksimum", + "machine_learning_max_recognition_distance_description": "Jarak maksimum antara dua muka untuk dianggap sebagai orang yang sama, antara 0-2. Menurunkan ini boleh menghalang pelabelan dua orang sebagai orang yang sama, manakala menaikkannya boleh menghalang pelabelan orang yang sama sebagai dua orang yang berbeza. Ambil perhatian bahawa adalah lebih mudah untuk menggabungkan dua orang daripada membelah satu orang kepada dua, jadi silap pada bahagian ambang yang lebih rendah apabila boleh.", + "machine_learning_min_detection_score": "Skor pengesanan minimum", + "machine_learning_min_detection_score_description": "Skor keyakinan minimum untuk wajah dikesan dari 0-1. Nilai yang lebih rendah akan mengesan lebih banyak muka tetapi mungkin menghasilkan positif palsu.", + "machine_learning_min_recognized_faces": "Minimum mengenali wajah" } } diff --git a/i18n/nl.json b/i18n/nl.json index d437ae17f7361..43df47474a4e7 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -34,6 +34,11 @@ "authentication_settings_disable_all": "Weet je zeker dat je alle inlogmethoden wilt uitschakelen? Inloggen zal volledig worden uitgeschakeld.", "authentication_settings_reenable": "Gebruik een servercommando om opnieuw in te schakelen.", "background_task_job": "Achtergrondtaken", + "backup_database": "Backup Database", + "backup_database_enable_description": "Database back-ups activeren", + "backup_keep_last_amount": "Maximaal aantal back-ups om te bewaren", + "backup_settings": "Back-up instellingen", + "backup_settings_description": "Database back-up instellingen beheren", "check_all": "Controleer het logboek", "cleared_jobs": "Taken gewist voor: {job}", "config_set_by_file": "Instellingen worden momenteel beheerd door een configuratiebestand", @@ -43,6 +48,9 @@ "confirm_reprocess_all_faces": "Weet je zeker dat je alle gezichten opnieuw wilt verwerken? Hiermee worden ook alle mensen gewist.", "confirm_user_password_reset": "Weet u zeker dat je het wachtwoord van {user} wilt resetten?", "create_job": "Taak maken", + "cron_expression": "Cron expressie", + "cron_expression_description": "Stel de scaninterval in met het cron-formaat. Voor meer informatie kun je kijken naar bijvoorbeeld Crontab Guru", + "cron_expression_presets": "Cron-expressie presets", "crontab_guru": "Crontab Guru", "disable_login": "Inloggen uitschakelen", "disabled": "Uitgeschakeld", diff --git a/i18n/nn.json b/i18n/nn.json new file mode 100644 index 0000000000000..0967ef424bce6 --- /dev/null +++ b/i18n/nn.json @@ -0,0 +1 @@ +{} diff --git a/i18n/pl.json b/i18n/pl.json index 26b89008a740b..0629a89f358ad 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -34,6 +34,11 @@ "authentication_settings_disable_all": "Czy jesteś pewny, że chcesz wyłączyć wszystkie metody logowania? Logowanie będzie całkowicie wyłączone.", "authentication_settings_reenable": "Aby ponownie włączyć, użyj Polecenia serwera.", "background_task_job": "Zadania w Tle", + "backup_database": "Kopia zapasowa bazy danych", + "backup_database_enable_description": "Włącz kopię zapasową bazy danych", + "backup_keep_last_amount": "Ile poprzednich kopii zapasowych przechowywać", + "backup_settings": "Ustawienia kopii zapasowej", + "backup_settings_description": "Zarządzaj ustawieniami kopii zapasowej bazy dnaych", "check_all": "Zaznacz Wszystko", "cleared_jobs": "Usunięto zadania dla: {job}", "config_set_by_file": "Konfiguracja pochodzi z pliku konfiguracyjnego", @@ -43,6 +48,9 @@ "confirm_reprocess_all_faces": "Czy na pewno chcesz ponownie przetworzyć wszystkie twarze? Spowoduje to utratę nazwanych osób.", "confirm_user_password_reset": "Czy na pewno chcesz zresetować hasło użytkownika {user}?", "create_job": "Utwórz zadanie", + "cron_expression": "Wyrażenie Cron", + "cron_expression_description": "Ustaw intwerwał skanowania przy pomocy formatu Cron'a. Po więcej informacji na temat formatu Cron zobacz . Crontab Guru", + "cron_expression_presets": "Predefiniowane wyrażenia Cron'a", "crontab_guru": "Crontab Guru", "disable_login": "Wyłącz logowanie", "disabled": "Wyłączone", diff --git a/i18n/pt.json b/i18n/pt.json index dda003243e4b6..55ac6c31946c5 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -34,6 +34,11 @@ "authentication_settings_disable_all": "Tem a certeza que deseja desativar todos os métodos de início de sessão? O início de sessão será completamente desativado.", "authentication_settings_reenable": "Para reativar, use um Comando de servidor.", "background_task_job": "Tarefas em segundo plano", + "backup_database": "Cópia de Segurança da Base de Dados", + "backup_database_enable_description": "Ativar cópias de segurança da base de dados", + "backup_keep_last_amount": "Quantidade de cópias de segurança anteriores a manter", + "backup_settings": "Definições de Cópia de Segurança", + "backup_settings_description": "Gerir definições de cópia de segurança da base de dados", "check_all": "Selecionar Tudo", "cleared_jobs": "Eliminadas as tarefas de: {job}", "config_set_by_file": "A configuração está atualmente definida por um ficheiro de configuração", @@ -43,6 +48,9 @@ "confirm_reprocess_all_faces": "Tem a certeza de que deseja reprocessar todos os rostos? Isto também limpará os nomes das pessoas.", "confirm_user_password_reset": "Tem a certeza de que deseja redefinir a palavra-passe de {user}?", "create_job": "Criar tarefa", + "cron_expression": "Expressão Cron", + "cron_expression_description": "Definir o intervalo de análise utilizando o formato Cron. Para mais informações, por favor veja o Crontab Guru", + "cron_expression_presets": "Predefinições das expressões Cron", "crontab_guru": "Guru do Crontab", "disable_login": "Desativar inicio de sessão", "disabled": "", diff --git a/i18n/pt_BR.json b/i18n/pt_BR.json index 7b6d5ed40d806..78f5ce71874e1 100644 --- a/i18n/pt_BR.json +++ b/i18n/pt_BR.json @@ -910,10 +910,10 @@ "menu": "Menu", "merge": "Mesclar", "merge_people": "Mesclar pessoas", - "merge_people_limit": "Só é possível combinar até 5 rostos de uma só vez", + "merge_people_limit": "Só é possível mesclar até 5 pessoas de uma só vez", "merge_people_prompt": "Tem certeza que deseja mesclar estas pessoas? Esta ação é irreversível.", "merge_people_successfully": "Pessoas mescladas com sucesso", - "merged_people_count": "{count, plural, one {# pessoa foi combinada} other {# pessoas foram combinadas}}", + "merged_people_count": "{count, plural, one {# pessoa foi mesclada} other {# pessoas foram mescladas}}", "minimize": "Minimizar", "minute": "Minuto", "missing": "Faltando", @@ -1294,7 +1294,7 @@ "theme": "Tema", "theme_selection": "Selecionar tema", "theme_selection_description": "Defina automaticamente o tema como claro ou escuro com base na preferência do sistema do seu navegador", - "they_will_be_merged_together": "Eles serão combinados", + "they_will_be_merged_together": "Eles serão mesclados", "third_party_resources": "Recursos de terceiros", "time_based_memories": "Memórias baseada no tempo", "timezone": "Fuso horário", diff --git a/i18n/ro.json b/i18n/ro.json index 6ff5b043fb788..4078f656b965d 100644 --- a/i18n/ro.json +++ b/i18n/ro.json @@ -28,6 +28,7 @@ "added_to_favorites_count": "Adăugat {count, number} la favorite", "admin": { "add_exclusion_pattern_description": "Adăugați modele de excludere. Globing folosind *, ** și ? este suportat. Pentru a ignora toate fișierele din orice director numit „Raw”, utilizați „**/Raw/**”. Pentru a ignora toate fișierele care se termină în „.tif”, utilizați „**/*.tif”. Pentru a ignora o cale absolută, utilizați „/path/to/ignore/**”.", + "asset_offline_description": "Acest material din biblioteca externă nu se mai găsește pe disc și a fost mutat în coșul de gunoi. Dacă fișierul a fost mutat în bibliotecă, verificați cronologia pentru noul material corespunzător. Pentru a restabili acest material, asigurați-vă că calea fișierului de mai jos poate fi accesată de Immich și scanați biblioteca.", "authentication_settings": "Setări de autentificare", "authentication_settings_description": "Gestionează parola, OAuth și alte setări de autentificare", "authentication_settings_disable_all": "Ești sigur că vrei sa dezactivezi toate metodele de autentificare? Autentificarea va fi complet dezactivată.", diff --git a/i18n/ru.json b/i18n/ru.json index 88026147cb188..98e150a6b372e 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -34,6 +34,11 @@ "authentication_settings_disable_all": "Вы уверены, что хотите отключить все методы входа? Вход будет полностью отключен.", "authentication_settings_reenable": "Чтобы снова включить, используйте Команда Сервера.", "background_task_job": "Фоновые задачи", + "backup_database": "Резервное копирование базы данных", + "backup_database_enable_description": "Включить резервное копирование базы данных", + "backup_keep_last_amount": "Количество хранимых резервных копий", + "backup_settings": "Настройки резервного копирования", + "backup_settings_description": "Управление настройками резервного копирования базы данных", "check_all": "Проверить все", "cleared_jobs": "Очищены задачи для: {job}", "config_set_by_file": "Настроено с помощью файла конфигурации", @@ -43,6 +48,9 @@ "confirm_reprocess_all_faces": "Вы уверены, что хотите повторно определить все лица? Будут также удалены имена со всех лиц.", "confirm_user_password_reset": "Вы уверены, что хотите сбросить пароль пользователя {user}?", "create_job": "Создать задание", + "cron_expression": "Выражение cron", + "cron_expression_description": "Задайте интервал сканирований в формате cron. Для получения дополнительной информации, ознакомьтесь с Crontab Guru", + "cron_expression_presets": "Предустановки выражений cron", "crontab_guru": "Crontab Guru", "disable_login": "Отключить вход", "disabled": "Выключено", @@ -228,7 +236,7 @@ "search_jobs": "Поиск заданий...", "send_welcome_email": "Отправить приветственное письмо", "server_external_domain_settings": "Внешний домен", - "server_external_domain_settings_description": "Домен для общедоступных ссылок, включая http(s)://", + "server_external_domain_settings_description": "Домен для публичных ссылок, включая http(s)://", "server_settings": "Настройки сервера", "server_settings_description": "Управление настройками сервера", "server_welcome_message": "Приветственное сообщение", @@ -530,11 +538,11 @@ "delete_key": "Удалить ключ", "delete_library": "Удалить библиотеку", "delete_link": "Удалить ссылку", - "delete_shared_link": "Удалить общую ссылку", + "delete_shared_link": "Удалить публичную ссылку", "delete_tag": "Удалить тег", "delete_tag_confirmation_prompt": "Вы уверены, что хотите удалить тег {tagName}?", "delete_user": "Удалить пользователя", - "deleted_shared_link": "Удалена публичная ссылка", + "deleted_shared_link": "Публичная ссылка удалена", "deletes_missing_assets": "Удаляет объекты, отсутствующие на диске", "description": "Описание", "details": "Подробности", @@ -626,8 +634,8 @@ "exclusion_pattern_already_exists": "Такая модель исключения уже существует.", "failed_job_command": "Команда {command} не выполнена для задачи: {job}", "failed_to_create_album": "Не удалось создать альбом", - "failed_to_create_shared_link": "Не удалось создать общую ссылку", - "failed_to_edit_shared_link": "Не удалось изменить общую ссылку", + "failed_to_create_shared_link": "Не удалось создать публичную ссылку", + "failed_to_edit_shared_link": "Не удалось изменить публичную ссылку", "failed_to_get_people": "Не удалось получить информацию о людях", "failed_to_load_asset": "Ошибка загрузки объекта", "failed_to_load_assets": "Ошибка загрузки объектов", @@ -642,7 +650,7 @@ "quota_higher_than_disk_size": "Вы установили квоту, превышающую размер диска", "repair_unable_to_check_items": "Невозможно проверить {count, select, one {элемент} other {элементы}}", "unable_to_add_album_users": "Невозможно добавить пользователей в альбом", - "unable_to_add_assets_to_shared_link": "Не удалось добавить ресурсы к общей ссылке", + "unable_to_add_assets_to_shared_link": "Не удалось добавить объекты к публичной ссылке", "unable_to_add_comment": "Невозможно добавить комментарий", "unable_to_add_exclusion_pattern": "Невозможно добавить шаблон исключения", "unable_to_add_import_path": "Не удается добавить путь импорта", @@ -671,7 +679,7 @@ "unable_to_delete_assets": "Ошибка при удалении ресурсов", "unable_to_delete_exclusion_pattern": "Не удается удалить шаблон исключения", "unable_to_delete_import_path": "Не удается удалить путь импорта", - "unable_to_delete_shared_link": "Не удается удалить общую ссылку", + "unable_to_delete_shared_link": "Не удалось удалить публичную ссылку", "unable_to_delete_user": "Не удается удалить пользователя", "unable_to_download_files": "Невозможно скачать файлы", "unable_to_edit_exclusion_pattern": "Невозможно отредактировать шаблон исключения", @@ -680,7 +688,7 @@ "unable_to_enter_fullscreen": "Не удается войти в полноэкранный режим", "unable_to_exit_fullscreen": "Не удается выйти из полноэкранного режима", "unable_to_get_comments_number": "Не удалось получить количество комментариев", - "unable_to_get_shared_link": "Не удалось получить общую ссылку", + "unable_to_get_shared_link": "Не удалось получить публичную ссылку", "unable_to_hide_person": "Невозможно скрыть персону", "unable_to_link_motion_video": "Не удается связать движущееся видео", "unable_to_link_oauth_account": "Не удается связать учетную запись OAuth", @@ -697,7 +705,7 @@ "unable_to_refresh_user": "Невозможно обновить пользователя", "unable_to_remove_album_users": "Не удалось удалить пользователей из альбома", "unable_to_remove_api_key": "Не удается удалить ключ API", - "unable_to_remove_assets_from_shared_link": "Невозможно удалить объекты из общей ссылки", + "unable_to_remove_assets_from_shared_link": "Невозможно удалить объекты из публичной ссылки", "unable_to_remove_comment": "", "unable_to_remove_deleted_assets": "Не удается удалить автономные файлы", "unable_to_remove_library": "Не удается удалить библиотеку", @@ -771,7 +779,7 @@ "folders": "Папки", "folders_feature_description": "Просмотр папок с фотографиями и видео в файловой системе", "force_re-scan_library_files": "Принудительное повторное сканирование всех файлов библиотеки", - "forward": "Переслать", + "forward": "Вперёд", "general": "Общие", "get_help": "Получить помощь", "getting_started": "Приступая к работе", @@ -890,7 +898,7 @@ "loop_videos_description": "Включить циклическое воспроизведение видео.", "main_branch_warning": "Вы используете версию для разработки; мы настоятельно рекомендуем использовать релизную версию!", "make": "Производитель", - "manage_shared_links": "Управление общими ссылками", + "manage_shared_links": "Управление публичными ссылками", "manage_sharing_with_partners": "Управление обменом информацией с партнерами. Эта функция позволяет вашему партнеру видеть ваши фотографии и видеозаписи, кроме тех, которые находятся в Архиве и Корзине", "manage_the_app_settings": "Управление настройками приложения", "manage_your_account": "Управление учётной записью", @@ -1104,13 +1112,13 @@ "regenerating_thumbnails": "Восстановление миниатюр", "remove": "Удалить", "remove_assets_album_confirmation": "Вы уверены, что хотите удалить {count, plural, one {# объект} few {# объекта} many {# объектов} other {# объектов}} из альбома?", - "remove_assets_shared_link_confirmation": "Вы уверены, что хотите удалить {count, plural, one {# объект} few {# объекта} many {# объектов} other {# объектов}} из этого общего доступа?", + "remove_assets_shared_link_confirmation": "Вы уверены, что хотите удалить {count, plural, one {# объект} few {# объекта} many {# объектов} other {# объектов}} из этой публичной ссылки?", "remove_assets_title": "Удалить объекты?", "remove_custom_date_range": "Удалить пользовательский диапазон дат", "remove_deleted_assets": "Удаление автономных файлов", "remove_from_album": "Удалить из альбома", "remove_from_favorites": "Удалить из избранного", - "remove_from_shared_link": "Удалить из общей ссылки", + "remove_from_shared_link": "Удалить из публичной ссылки", "remove_user": "Удалить пользователя", "removed_api_key": "Удален ключ API: {name}", "removed_from_archive": "Удален из архива", @@ -1211,8 +1219,8 @@ "shared_by_user": "Владелец: {user}", "shared_by_you": "Вы поделились", "shared_from_partner": "Фото от {partner}", - "shared_link_options": "Параметры общих ссылок", - "shared_links": "Общие ссылки", + "shared_link_options": "Параметры публичных ссылок", + "shared_links": "Публичные ссылки", "shared_photos_and_videos_count": "{assetCount, plural, other {# фото и видео.}}", "shared_with_partner": "Совместно с {partner}", "sharing": "Общие", @@ -1391,10 +1399,10 @@ "warning": "Предупреждение", "week": "Неделя", "welcome": "Добро пожаловать", - "welcome_to_immich": "Добро пожаловать в immich", + "welcome_to_immich": "Добро пожаловать в Immich", "year": "Год", "years_ago": "{years, plural, one {# год} few {# года} many {# лет} other {# года}} назад", "yes": "Да", - "you_dont_have_any_shared_links": "У вас нет общих ссылок", - "zoom_image": "Увеличить" + "you_dont_have_any_shared_links": "У вас нет публичных ссылок", + "zoom_image": "Приблизить" } diff --git a/i18n/sk.json b/i18n/sk.json index 1d0cd834fc31b..112c080835d16 100644 --- a/i18n/sk.json +++ b/i18n/sk.json @@ -38,6 +38,7 @@ "cleared_jobs": "Hotové úlohy pre: {job}", "config_set_by_file": "Konfigurácia je v súčasnosti nastavená konfiguračným súborom", "confirm_delete_library": "Naozaj chcete vymazať knižnicu {library}?", + "confirm_delete_library_assets": "Ste si isti, že chcete vymazať túto knižnicu? Tato operácia nenávratne odstráni {count, plural, one {# contained asset} other {all # contained assets}} súborov z Immich. Súbory sa budu ponechané na disku.", "confirm_email_below": "Pre potvrdenie zadajte \"{email}\" nižšie", "confirm_reprocess_all_faces": "Naozaj chcete spracovať všetky tváre znova? Tento proces vymaže pomenovaných ľudí.", "confirm_user_password_reset": "Naozaj chcete resetovať heslo pre {user}?", @@ -130,18 +131,21 @@ "machine_learning_settings": "Nastavenia strojového učenia", "machine_learning_settings_description": "Spravovať funkcie a nastavenia strojového učenia", "machine_learning_smart_search": "Inteligentné vyhľadávanie", - "machine_learning_smart_search_description": "", + "machine_learning_smart_search_description": "Významove vyhladavanie v obrázkoch pomocou CLIP vzorov", "machine_learning_smart_search_enabled": "Povoliť inteligentné vyhľadávanie", - "machine_learning_smart_search_enabled_description": "", + "machine_learning_smart_search_enabled_description": "Ak je vypnuté, obrázky nebudú spracované pre inteligentné vyhľadávanie.", "machine_learning_url_description": "URL adresa servera pre strojové učenie", + "manage_concurrency": "Správa súbežnosti", "manage_log_settings": "Spravovať nastavenia logovania", "map_dark_style": "Tmavý štýl", "map_enable_description": "Povoliť funkcie mapy", "map_gps_settings": "Nastavenia Mapy & GPS", + "map_gps_settings_description": "Sprava Mapovych a GPS (Reverzne Geokodovanie) Nastavení", + "map_implications": "Táto funkčnosť sa spolieha na externý servis spracovania mapových dlaždíc (tiles.immich.cloud)", "map_light_style": "Svetlý štýl", - "map_reverse_geocoding": "", + "map_reverse_geocoding": "Reverzné Geokódovanie", "map_reverse_geocoding_enable_description": "Povoliť reverzné geokódovanie", - "map_reverse_geocoding_settings": "", + "map_reverse_geocoding_settings": "Nastavenia reverzného geokódovania", "map_settings": "Mapa", "map_settings_description": "Spravovať nastavenia mapy", "map_style_description": "", @@ -152,12 +156,13 @@ "metadata_settings_description": "Spravovať nastavenia metadát", "migration_job": "Migrácia", "migration_job_description": "", + "note_unlimited_quota": "Poznámka: Použite 0 pre neobmedzený limit", "notification_email_from_address": "Z adresy", - "notification_email_from_address_description": "", - "notification_email_host_description": "", + "notification_email_from_address_description": "E-mailová adresa odosielateľa, príklad: \"Immich Photo Server \"", + "notification_email_host_description": "Adresa emailového serveru (príklad: smtp.immich.app)", "notification_email_ignore_certificate_errors": "Ignorovať chyby certifikátu", - "notification_email_ignore_certificate_errors_description": "", - "notification_email_password_description": "", + "notification_email_ignore_certificate_errors_description": "Ignorovať chyby pri overení TLS certifikátu (neodporúča sa)", + "notification_email_password_description": "Heslo pre komunikáciu s emailovým serverom", "notification_email_port_description": "Porty e-mailového servera (napr. 25, 465, alebo 587)", "notification_email_sent_test_email_button": "Odoslať testovací e-mail a uložiť", "notification_email_setting_description": "Nastavenie pre odosielanie e-mailových upozornení", @@ -168,11 +173,11 @@ "notification_enable_email_notifications": "Povoliť e-mailové upozornenia", "notification_settings": "Nastavenia upozornení", "notification_settings_description": "Spravovať nastavenia upozornení, vrátane emailu", - "oauth_auto_launch": "", - "oauth_auto_launch_description": "", - "oauth_auto_register": "", - "oauth_auto_register_description": "", - "oauth_button_text": "", + "oauth_auto_launch": "Automatické spustenie", + "oauth_auto_launch_description": "Automatické spustenie OAuth prihlásenia, pri otvorení prihlasovacej stránky", + "oauth_auto_register": "Automatická regristrácia", + "oauth_auto_register_description": "Automatické zaregistrovanie nového požívateľa pri prihlásení pomocou OAuth", + "oauth_button_text": "Text tlačítka", "oauth_client_id": "Client ID", "oauth_client_secret": "Client Secret", "oauth_enable_description": "Prihlásiť sa pomocou OAuth", @@ -183,32 +188,37 @@ "oauth_scope": "", "oauth_settings": "OAuth", "oauth_settings_description": "Spravovať nastavenia prihlásenia OAuth", + "oauth_settings_more_details": "Pre viac informácii o tejto funkcii, prejdite na docs.", "oauth_signing_algorithm": "", "oauth_storage_label_claim": "", "oauth_storage_label_claim_description": "", "oauth_storage_quota_claim": "", "oauth_storage_quota_claim_description": "", - "oauth_storage_quota_default": "", + "oauth_storage_quota_default": "Predvolený limit úložiska (GiB)", "oauth_storage_quota_default_description": "", "password_enable_description": "Prihlásiť sa pomocou emailu a hesla", "password_settings": "Prihlásenie cez heslo", "password_settings_description": "Spravovať nastavenia prihlásenia cez heslo", "refreshing_all_libraries": "Obnovujú sa všetky knižnice", "registration": "Registrácia administrátora", + "registration_description": "Keďže ste prvým používateľom v systéme, budú vám pridelené správcovské práva na vykonávanie všetkých úloh a vrátane tvorby nových používateľov.", "repair_all": "Opraviť Všetko", + "repair_matched_items": "Zhody {count, plural, one {# item} other {# items}}", + "repaired_items": "Opravených {count, plural, one {# item} other {# items}}", "require_password_change_on_login": "Vyžadovať od používateľa zmenu hesla pri prvom prihlásení", "reset_settings_to_default": "Obnoviť pôvodné nastavenia", + "reset_settings_to_recent_saved": "Obnoviť naposledy uložené nastavenia", "scanning_library": "Knižnica sa skenuje", "search_jobs": "Vyhľadať úlohy...", "send_welcome_email": "Odoslať uvítací e-mail", "server_external_domain_settings": "Externá doména", - "server_external_domain_settings_description": "", + "server_external_domain_settings_description": "Verejná doména pre zdieľané odkazy, vrátane http(s)://", "server_settings": "Nastavenia servera", "server_settings_description": "Spravovať nastavenia servera", "server_welcome_message": "Uvítacia správa", "server_welcome_message_description": "Správa, ktorá sa zobrazí na prihlasovacej stránke.", "sidecar_job_description": "", - "slideshow_duration_description": "", + "slideshow_duration_description": "Čas zobrazenia obrázku v sekundách", "smart_search_job_description": "", "storage_template_enable_description": "", "storage_template_hash_verification_enabled": "", @@ -384,7 +394,7 @@ "confirm": "Potvrdiť", "confirm_admin_password": "Potvrdiť Administrátorské Heslo", "confirm_delete_shared_link": "Ste si istý, že chcete odstrániť tento zdieľaný odkaz?", - "confirm_password": "Potvrďiť heslo", + "confirm_password": "Potvrdiť heslo", "contain": "", "context": "Kontext", "continue": "Pokračovať", diff --git a/i18n/sr_Cyrl.json b/i18n/sr_Cyrl.json index 8ff3b5ed1bfa2..6becd38278624 100644 --- a/i18n/sr_Cyrl.json +++ b/i18n/sr_Cyrl.json @@ -34,6 +34,11 @@ "authentication_settings_disable_all": "Да ли сте сигурни да желите да oneмогућите све методе пријављивања? Пријава ће бити потпуно oneмогућена.", "authentication_settings_reenable": "Да бисте поново омогућили, користите команду сервера.", "background_task_job": "Позадински задаци", + "backup_database": "Резервна копија базе података", + "backup_database_enable_description": "Омогућите резервне копије базе података", + "backup_keep_last_amount": "Количина претходних резервних копија за чување", + "backup_settings": "Подешавања резервне копије", + "backup_settings_description": "Управљајте поставкама резервне копије базе података", "check_all": "Провери све", "cleared_jobs": "Очишћени послови за {job}", "config_set_by_file": "Конфигурацију тренутно поставља конфигурациони фајл", @@ -43,6 +48,9 @@ "confirm_reprocess_all_faces": "Да ли сте сигурни да желите да поново обрадите сва лица? Ово ће такође обрисати именоване особе.", "confirm_user_password_reset": "Да ли сте сигурни да желите да ресетујете лозинку корисника {user}?", "create_job": "Креирајте посао", + "cron_expression": "Cron израз (expression)", + "cron_expression_description": "Подесите интервал скенирања користећи cron формат. За више информација погледајте нпр. Crontab Guru", + "cron_expression_presets": "Предефинисана подешавања Cron израза (expression)", "crontab_guru": "Guru servisnih zadataka", "disable_login": "oneмогући пријаву", "disabled": "", @@ -432,7 +440,7 @@ "birthdate_set_description": "Датум рођења се користи да би се израчунале године ове особе у добу одређене фотографије.", "blurred_background": "Замућена позадина", "bugs_and_feature_requests": "Грешке и захтеви за функције", - "build": "Build", + "build": "Под-верзија (Build)", "build_image": "Сагради (Буилд) имаге", "bulk_delete_duplicates_confirmation": "Да ли сте сигурни да желите групно да избришете {count, plural, one {# дуплиран елеменат} few {# дуплирана елемента} other {# дуплираних елемената}}? Ово ће задржати највеће средство сваке групе и трајно избрисати све друге дупликате. Не можете поништити ову радњу!", "bulk_keep_duplicates_confirmation": "Да ли сте сигурни да желите да задржите {count, plural, one {1 дуплирану датотеку} few {# дуплиране датотеке} other {# дуплираних датотека}}? Ово ће решити све дуплиране групе без брисања било чега.", @@ -1391,7 +1399,7 @@ "warning": "Упозорење", "week": "Недеља", "welcome": "Добродошли", - "welcome_to_immich": "Добродошли у immich", + "welcome_to_immich": "Добродошли у Имич (Immich)", "year": "Година", "years_ago": "пре {years, plural, one {# године} other {# година}}", "yes": "Да", diff --git a/i18n/sr_Latn.json b/i18n/sr_Latn.json index dbe2f4f72bc25..b13a9ccc73175 100644 --- a/i18n/sr_Latn.json +++ b/i18n/sr_Latn.json @@ -34,6 +34,11 @@ "authentication_settings_disable_all": "Da li ste sigurni da želite da onemogućite sve metode prijavljivanja? Prijava će biti potpuno onemogućena.", "authentication_settings_reenable": "Da biste ponovo omogućili, koristite komandu servera.", "background_task_job": "Pozadinski zadaci", + "backup_database": "Rezervna kopija baze podataka", + "backup_database_enable_description": "Omogućite rezervne kopije baze podataka", + "backup_keep_last_amount": "Količina prethodnih rezervnih kopija za čuvanje", + "backup_settings": "Podešavanja rezervne kopije", + "backup_settings_description": "Upravljajte postavkama rezervne kopije baze podataka", "check_all": "Proveri sve", "cleared_jobs": "Očišćeni poslovi za: {job}", "config_set_by_file": "Konfiguraciju trenutno postavlja konfiguracioni fajl", @@ -43,6 +48,9 @@ "confirm_reprocess_all_faces": "Da li ste sigurni da želite da ponovo obradite sva lica? Ovo će takođe obrisati imenovane osobe.", "confirm_user_password_reset": "Da li ste sigurni da želite da resetujete lozinku korisnika {user}?", "create_job": "Kreirajte posao", + "cron_expression": "Cron izraz (expression)", + "cron_expression_description": "Podesite interval skeniranja koristeći cron format. Za više informacija pogledajte npr. Crontab Guru", + "cron_expression_presets": "Predefinisana podešavanja Cron izraza (expression)", "crontab_guru": "Guru servisnih zadataka", "disable_login": "Onemogući prijavu", "disabled": "", @@ -432,7 +440,7 @@ "birthdate_set_description": "Datum rođenja se koristi da bi se izračunale godine ove osobe u dobu određene fotografije.", "blurred_background": "Zamućena pozadina", "bugs_and_feature_requests": "Greške (bugs) i zahtevi za funkcije", - "build": "Build", + "build": "Pod-verzija (Build)", "build_image": "Sagradi (Build) image", "bulk_delete_duplicates_confirmation": "Da li ste sigurni da želite grupno da izbrišete {count, plural, one {# dupliran elemenat} few {# duplirana elementa} other {# dupliranih elemenata}}? Ovo će zadržati najveće sredstvo svake grupe i trajno izbrisati sve druge duplikate. Ne možete poništiti ovu radnju!", "bulk_keep_duplicates_confirmation": "Da li ste sigurni da želite da zadržite {count, plural, one {1 dupliranu datoteku} few {# duplirane datoteke} other {# dupliranih datoteka}}? Ovo će rešiti sve duplirane grupe bez brisanja bilo čega.", diff --git a/i18n/sv.json b/i18n/sv.json index 646c753d23ea9..aa91ec61e1a37 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -34,6 +34,9 @@ "authentication_settings_disable_all": "Är du säker på att du vill inaktivera alla inloggningsmetoder? Inloggning kommer att helt inaktiveras.", "authentication_settings_reenable": "För att återaktivera, använd Server Command.", "background_task_job": "Bakgrundsaktiviteter", + "backup_database": "Databassäkerhetskopia", + "backup_database_enable_description": "Slå på säkerhetskopia", + "backup_keep_last_amount": "Antal säkerhetskopior att behålla", "check_all": "Välj alla", "cleared_jobs": "Rensade jobben för:{job}", "config_set_by_file": "Konfigurationen är satt av en konfigurationsfil", @@ -43,6 +46,8 @@ "confirm_reprocess_all_faces": "Är du säker på att du vill återprocessa alla ansikten? Detta kommer också rensa namngivna personer.", "confirm_user_password_reset": "Är du säker på att du vill återställa {user}’s lösenord?", "create_job": "Skapa jobb", + "cron_expression": "Cron uttryck", + "cron_expression_description": "Sätt skanningsintervall genom att använda cron format. För mer information se Crontab Guru", "crontab_guru": "Crontab-guru", "disable_login": "Inaktivera inloggning", "disabled": "Inaktiverad", @@ -311,7 +316,7 @@ "transcoding_temporal_aq_description": "Gäller endast NVENC. Ökar kvaliteten på scener med hög detaljrikedom och låg rörelse. Kanske inte är kompatibel med äldre enheter.", "transcoding_threads": "Trådar", "transcoding_threads_description": "Högre värden leder till snabbare kodning, men lämnar mindre utrymme för servern att bearbeta andra uppgifter medan den är aktiv. Detta värde bör inte vara mer än antalet CPU-kärnor. Maximerar användningen om den är inställd på 0.", - "transcoding_tone_mapping": "", + "transcoding_tone_mapping": "Ton mappning", "transcoding_tone_mapping_description": "Försöker att bevara utseendet på HDR-videor när de konverteras till SDR. Varje algoritm gör olika avvägningar för färg, detaljer och ljusstyrka. Hable bevarar detaljer, Mobius bevarar färg och Reinhard bevarar ljusstyrkan.", "transcoding_tone_mapping_npl": "", "transcoding_tone_mapping_npl_description": "Färgerna kommer att justeras för att se normala ut för en visning av denna ljusstyrka. Kontraintuitivt ökar lägre värden videons ljusstyrka och vice versa eftersom det kompenserar för skärmens ljusstyrka. 0 ställer in detta värde automatiskt.", @@ -536,6 +541,7 @@ "direction": "Riktning", "disabled": "Inaktiverad", "disallow_edits": "Tillåt inte redigeringar", + "discord": "Discord", "discover": "Upptäck", "dismiss_all_errors": "Avvisa alla fel", "dismiss_error": "Avvisa fel", @@ -673,23 +679,31 @@ "unable_to_empty_trash": "Kunde inte tömma papperskorgen", "unable_to_enter_fullscreen": "Kunde inte växla till fullskärm", "unable_to_exit_fullscreen": "Kunde inte avsluta fullskärm", + "unable_to_get_comments_number": "Det gick inte att hämta antalet kommentarer", "unable_to_get_shared_link": "Det gick inte att hämta delad länk", "unable_to_hide_person": "Det går inte att dölja personen", "unable_to_link_motion_video": "Det går inte att länka rörlig video", "unable_to_link_oauth_account": "Det gick inte att länka OAuth-kontot", "unable_to_load_album": "Det gick inte att ladda albumet", "unable_to_load_asset_activity": "", - "unable_to_load_items": "", - "unable_to_load_liked_status": "", + "unable_to_load_items": "Kunde inte ladda objekt", + "unable_to_load_liked_status": "kunde inte ladda gillade status", + "unable_to_log_out_all_devices": "Det gick inte att logga ut alla enheter", + "unable_to_log_out_device": "Det gick inte att logga ut enheten", + "unable_to_login_with_oauth": "Det gick inte att logga in med OAuth", "unable_to_play_video": "Kunde inte spela upp video", - "unable_to_refresh_user": "", - "unable_to_remove_album_users": "", + "unable_to_reassign_assets_new_person": "Kunde inte tilldela objekt till en annan person", + "unable_to_refresh_user": "Kunde inte ladda om användaren", + "unable_to_remove_album_users": "Kunde inte ta bort personen från albumet", + "unable_to_remove_api_key": "Det gick inte att ta bort API Keyet", + "unable_to_remove_assets_from_shared_link": "Kunde inte ta bort objekt från delade länkar", "unable_to_remove_comment": "", + "unable_to_remove_deleted_assets": "Kunde inte ta bort offline filer", "unable_to_remove_library": "Kunde inte ta bort bibliotek", "unable_to_remove_partner": "Kunde inte ta bort partner", "unable_to_remove_reaction": "Kunde inte ta bort reaktion", "unable_to_remove_user": "", - "unable_to_repair_items": "", + "unable_to_repair_items": "kunde inte reparera objekt", "unable_to_reset_password": "Kunde inte återställa lösenord", "unable_to_resolve_duplicate": "", "unable_to_restore_assets": "", @@ -1088,6 +1102,7 @@ "view_album": "Visa Album", "view_all": "Visa alla", "view_all_users": "Visa alla användare", + "view_in_timeline": "Visa i tidslinjen", "view_links": "Visa länkar", "view_next_asset": "Visa nästa objekt", "view_previous_asset": "Visa föregående objekt", diff --git a/i18n/th.json b/i18n/th.json index 6665c6ec83ef2..e964bdd1980b7 100644 --- a/i18n/th.json +++ b/i18n/th.json @@ -1,7 +1,7 @@ { "about": "เกี่ยวกับ", "account": "บัญชี", - "account_settings": "ตั้งค่าบัญชี", + "account_settings": "การตั้งค่าบัญชี", "acknowledge": "รับทราบ", "action": "การดำเนินการ", "actions": "การดำเนินการ", @@ -17,19 +17,19 @@ "add_import_path": "เพิ่มพาธนำเข้า", "add_location": "เพิ่มตำแหน่ง", "add_more_users": "เพิ่มผู้ใช้งาน", - "add_partner": "เพิ่มพันธมิตร", + "add_partner": "เพิ่มคู่หู", "add_path": "เพิ่มพาธ", "add_photos": "เพิ่มรูปภาพ", "add_to": "เพิ่มเข้า...", "add_to_album": "เพิ่มเข้าอัลบั้ม", - "add_to_shared_album": "เพิ่มเข้าอัลบั้มที่แชร์", + "add_to_shared_album": "เพิ่มลงในอัลบั้มที่แชร์กัน", "added_to_archive": "เพิ่มเข้าที่เก็บถาวร", "added_to_favorites": "เพิ่มเข้ารายการโปรด", "added_to_favorites_count": "{count, number} รูปถูกเพิ่มเข้ารายการโปรด", "admin": { - "add_exclusion_pattern_description": "เพิ่มรูปแบบการยกเว้น การ Glob โดยใช้ *, ** และ ? ถูกรองรับ ถ้าต้องการละเว้นไฟล์ทั้งหมดในไดเร็กทอรีใดๆที่ชื่อว่า \"Raw\" ให้ใช้ \"**/Raw/**\" ถ้าต้องการละเว้นไฟล์ทั้งหมดที่ลงท้ายด้วย \".tif\" ให้ใช้ \"**/*.tif\" ถ้าต้องการละเว้นพาธที่เริ่มจากไดเรกทอรีบนสุดให้ใช้ \"/พาธ/ที่ต้องการ/ละเว้น/**\"", + "add_exclusion_pattern_description": "เพิ่มรูปแบบข้อยกเว้น รองรับการใช้ *, ** และ ? หากต้องการละเว้นไฟล์ทั้งหมดในไดเร็กทอรีที่ชื่อว่า \"Raw\" ให้ใช้ \"**/Raw/**\" ถ้าต้องการละเว้นไฟล์ทั้งหมดที่ลงท้ายด้วย \".tif\" ให้ใช้ \"**/*.tif\" ถ้าต้องการละเว้นพาธที่เริ่มจากไดเรกทอรีบนสุดให้ใช้ \"/พาธ/ที่ต้องการ/ละเว้น/**\"", "asset_offline_description": "Immich", - "authentication_settings": "ตั้งค่าการเข้าถึง", + "authentication_settings": "การตั้งค่าการเข้าถึง", "authentication_settings_description": "จัดการรหัสผ่าน, OAuth, และตั้งค่าการเข้าถึงอื่นๆ", "authentication_settings_disable_all": "คุณแน่ใจว่าต้องการปิดวิธีการล็อกอินทั้งหมดหรือไม่? ล็อกอินจะถูกปิดทั้งหมด", "authentication_settings_reenable": "เพื่อเปิดใหม่ ให้ใช้คำสั่งเซิร์ฟเวอร์", @@ -38,58 +38,58 @@ "cleared_jobs": "เคลียร์งานสำหรับ: {job}", "config_set_by_file": "ปัจจุบันการกำหนดค่าถูกตั้งค่าโดยไฟล์กำหนดค่า", "confirm_delete_library": "คุณแน่ใจว่าอยากลบคลังภาพ {library} หรือไม่?", - "confirm_delete_library_assets": "คุณแน่ใจว่าอยากลบคลังภาพนี้หรือไม่? การกระทำนี้จะลบ {count, plural, one {# สี่อในคลัง} other {# สี่อในคลังทั้งหมด}} ออกจาก Immich โดยถาวรและไม่สามารถยกเลิกได้ ไฟล์จะยังคงอยู่บนดิสก์", - "confirm_email_below": "เพื่อยืนยัน พิมพ์ \"{email}\" ด้านล่าง", - "confirm_reprocess_all_faces": "คุณแน่ใจว่าคุณต้องการประมวลผลใบหน้าทั้งหมดใหม่หรือไม่? คนที่มีชื่อจะถูกลบไปด้วย", + "confirm_delete_library_assets": "คุณแน่ใจว่าอยากลบคลังภาพนี้หรือไม่? สี่อทั้งหมด {count} สี่อในคลังจะถูกลบออกจาก Immich โดยถาวร ไฟล์จะยังคงอยู่บนดิสก์", + "confirm_email_below": "เพื่อยืนยัน พิมพ์ \"{email}\" ข้างล่าง", + "confirm_reprocess_all_faces": "คุณแน่ใจว่าคุณต้องการประมวลผลใบหน้าทั้งหมดใหม่? ชื่อคนจะถูกลบไปด้วย", "confirm_user_password_reset": "คุณแน่ใจว่าต้องการรีเซ็ตรหัสผ่านของ {user} หรือไม่?", "crontab_guru": "Crontab Guru", "disable_login": "ปิดการล็อกอิน", "disabled": "", "duplicate_detection_job_description": "ใช้ machine learning กับสี่อเพื่อตรวจจับรูปภาพที่คล้ายกัน โดยใช้การค้นหาอัจฉริยะ", - "exclusion_pattern_description": "รูปแบบการยกเว้นสามารถละเว้นไฟล์และโฟลเดอร์ขณะสแกนคลังภาพของคุณ มีประโยชน์เมื่อมีโฟลเดอร์ที่มีไฟล์ที่ไม่อยากนำเข้า เช่นไฟล์ RAW", + "exclusion_pattern_description": "ข้อยกเว้นสามารถละเว้นไฟล์และโฟลเดอร์ขณะสแกนคลังภาพของคุณ มีประโยชน์เมื่อโฟลเดอร์มีไฟล์ที่ไม่อยากนำเข้า เช่นไฟล์ RAW", "external_library_created_at": "คลังภาพภายนอก (ถูกสร้างเมื่อ {date})", "external_library_management": "การจัดการคลังภาพภายนอก", "face_detection": "การตรวจจับใบหน้า", - "face_detection_description": "ตรวจจับใบหน้าในสี่อโดยใช้ machine learning สำหรับวิดีโอ จะใช้ภาพตัวอย่างจากวิดีโอเท่านั้น \"ทั้งหมด\" จะประมวลผลสี่อทั้งหมด \"ขาดหาย\" จะประมวลผลสี่อที่ยังไม่ได้ประมวลผล ใบหน้าที่ถูกตรวจจับแล้วจะถูกเข้าคิวประมวลผลการจดจำใบหน้า เพิ่มเข้าไปในกลุ่มที่มีอยู่แล้วหรือคนใหม่", + "face_detection_description": "ตรวจจับใบหน้าในสี่อโดยใช้ machine learning วิดีโอจะใช้ภาพตัวอย่างจากวิดีโอเท่านั้น \"ทั้งหมด\" จะประมวลผลสี่อทั้งหมด \"ขาดหาย\" จะประมวลผลสี่อที่ยังไม่ได้ประมวลผล ใบหน้าที่ถูกตรวจจับแล้วจะถูกเข้าคิวประมวลผลการจดจำใบหน้า เพิ่มเข้าไปในกลุ่มที่มีอยู่แล้วหรือคนใหม่", "facial_recognition_job_description": "นำใบหน้าที่ตรวจจับได้ไปจับกลุ่มตามผู้คน ขั้นตอนนี้ทำงานหลังจากตรวจจับใบหน้าสำเร็จ \"ทั้งหมด\" จะจำกลุ่มใบหน้าทั้งหมดใหม่ \"ขาดหาย\" จะจัดคิวใบหน้าที่ยังไม่ได้ระบุคน", "failed_job_command": "คำสั่ง {command} ของงาน {job} ล้มเหลว", - "force_delete_user_warning": "คําเตือน: ขั้นตอนนี้จะลบผู้ใช้และสื่อทั้งหมดทันที ขั้นตอนนี้จะย้อนกลับมาไม่ได้และกู้คืนไฟล์ไม่ได้.", + "force_delete_user_warning": "คําเตือน: ขั้นตอนนี้จะลบผู้ใช้งานและสื่อทั้งหมดทันที ไม่สามารถย้อนกลับมาได้และกู้คืนไฟล์ไม่ได้", "forcing_refresh_library_files": "บังคับรีเฟรชไฟล์ทั้งหมด", "image_format": "Format", - "image_format_description": "WebP จะสร้างไฟล์ที่เล็กกว่า JPEG แต่ใช้เวลา encode นานกว่า", + "image_format_description": "WebP จะให้ไฟล์ที่เล็กกว่า JPEG แต่ใช้เวลาแปลงไฟล์นานกว่า", "image_prefer_embedded_preview": "ใช้พรีวิวแบบฝังตัว", "image_prefer_embedded_preview_setting_description": "ใช้พรีวิวฝังตัวในรูปภาพ RAW ในการวิเคราะห์รูปภาพถ้ามี แต่คุณภาพรูปภาพขึ้นอยู่กับกล้อง และอาจจะมีสิ่งตกค้างจากการย่อขนาดไฟล์", "image_prefer_wide_gamut": "ใช้ช่วงสีกว้าง", - "image_prefer_wide_gamut_setting_description": "ใช้ Display P3 สำหรับภาพย่อ ซึ่งจะรักษาความมีชีวิตชีวาของภาพที่ใช้ปริภูมิสีกว้าง แต่ภาพบนอุปกรณ์หรือเบราว์เซอร์เก่าอาจปรากฏแตกต่างออกไป ภาพ sRGB จะถูกเก็บเป็น sRGB เพื่อป้องกันไม่ให้สีเคลื่อน", + "image_prefer_wide_gamut_setting_description": "ใช้การแสดงผลแบบ P3 สําหรับภาพตัวอย่าง คงความเข้มและความกว้างขอบเขตสี แต่ภาพอาจดูแตกต่างกันในอุปกรณ์เก่าที่มีเว็บเบราว์เซอร์รุ่นเก่า ภาพ sRGB จะถูกเก็บในรูปแบบ sRGB เพื่อลดการเคลื่อนของสี", "image_preview_format": "รูปแบบพรีวิว", "image_preview_resolution": "ความละเอียดพรีวิว", "image_preview_resolution_description": "ใช้เมื่อดูรูปเดียวและสำหรับ machine learning ความละเอียดสูงสามารถเก็บรายละเอียดดีกว่าแต่ใช้เวลา encode นานกว่า ขนาดไฟล์ใหญ่กว่า และลดการตอบสนองของแอป", "image_quality": "คุณภาพ", "image_quality_description": "คุณภาพรูปจาก 1-100 ค่าสูงมีคุณภาพสูงกว่าแต่ขนาดไฟล์ใหญ่กว่า ตัวเลือกนี้ส่งผลต่อภาพพรีวิวและภาพขนาดย่อ", - "image_settings": "ตั้งค่ารูปภาพ", - "image_settings_description": "จัดการคุณภาพและความละเอียดของภาพที่สร้างขึ้น", + "image_settings": "การตั้งค่ารูปภาพ", + "image_settings_description": "จัดการคุณภาพและความคมชัดของภาพที่สร้างขึ้น", "image_thumbnail_format": "รูปแบบภาพย่อ", "image_thumbnail_resolution": "ความละเอียดภาพย่อ", "image_thumbnail_resolution_description": "ใช้เมื่อดูกลุ่มรูปภาพ (ไทม์ไลน์หลัก, หน้าอัลบั้ม, ฯลฯ) ความสะเอียดที่สูงกว่าจะเก็บรายละเอียดได้มากกว่าแต่ใช้เวลา encode นานกว่า ขนาดไฟล์ใหญ่กว่า และลดการตอบสนองของแอป", - "job_concurrency": "{job} พร้อมกัน", + "job_concurrency": "{job} งานพร้อมกัน", "job_not_concurrency_safe": "งานนี้ทำงานพร้อมกันแบบปลอดภัยไม่ได้", "job_settings": "การตั้งค่างาน", "job_settings_description": "จัดการการทำหลายงานพร้อมกัน", "job_status": "สถานะงาน", - "jobs_delayed": "{jobCount, plural, other {# ล่าช้า}}", - "jobs_failed": "{jobCount, plural, other {# ล้มเหลว}}", + "jobs_delayed": "{jobCount} งานล่าช้า", + "jobs_failed": "{jobCount} งานล้มเหลว", "library_created": "สร้างคลังภาพ: {library}", "library_cron_expression": "รูปแบบ Cron", "library_cron_expression_description": "ตั้งช่วงเวลาสแกนโดยใช้รูปแบบ cron สามารถดูข้อมูลเพิ่มเติมได้ที่ Crontab Guru", "library_cron_expression_presets": "แม่แบบรูปแบบ Cron", "library_deleted": "คลังภาพถูกลบ", - "library_import_path_description": "ระบุโฟลเดอร์เพื่อนําเข้า โฟลเดอร์นี้และโฟลเดอร์ย่อยจะถูกค้นหาภาพและวิดีโอ", + "library_import_path_description": "ระบุโฟลเดอร์เพื่อนําเข้า โฟลเดอร์นี้และโฟลเดอร์ย่อยจะถูกค้นหาภาพและวิดีโอ.", "library_scanning": "การสแกนเป็นระยะ", - "library_scanning_description": "ตั้งค่าการสแกนเป็นระยะ", - "library_scanning_enable_description": "เปิดการสแกนเป็นระยะ", + "library_scanning_description": "ตั้งค่าการสแกนคลังภาพเป็นระยะ", + "library_scanning_enable_description": "เปิดการสแกนคลังภาพเป็นระยะ", "library_settings": "คลังภาพภายนอก", "library_settings_description": "จัดการการตั้งค่าคลังภาพภายนอก", - "library_tasks_description": "ปฏิบัติงานคลังภาพ", + "library_tasks_description": "ทำงานคลังภาพ", "library_watching_enable_description": "ดูคลังภาพภายนอกสำหรับการเปลี่ยนแปลงของไฟล์", "library_watching_settings": "การดูคลังภาพภายนอก (ฟีเจอร์ทดลอง)", "library_watching_settings_description": "หาไฟล์ที่เปลี่ยนแปลงโดยอัตโนมัติ", @@ -104,12 +104,12 @@ "machine_learning_duplicate_detection_setting_description": "ใช้ CLIP เพื่อแสดงที่มีแนวโน้มซ้ํา", "machine_learning_enabled": "เปิดใช้ machine learning", "machine_learning_enabled_description": "หากปิดใช้งาน คุณสมบัติ ML ทั้งหมดจะปิดการใช้งานโดยไม่คํานึงถึงการตั้งค่าด้านล่าง.", - "machine_learning_facial_recognition": "การตรวจจับใบหน้า", - "machine_learning_facial_recognition_description": "ตรวจจับ จำแนก และรวมกลุ่มใบหน้าในภาพ", - "machine_learning_facial_recognition_model": "โมเดลสำหรับการตรวจจับใบหน้า", + "machine_learning_facial_recognition": "การจดจำใบหน้า", + "machine_learning_facial_recognition_description": "ตรวจจับ จดจำ และจำแนกใบหน้าในภาพ", + "machine_learning_facial_recognition_model": "โมเดลสำหรับการจดจำใบหน้า", "machine_learning_facial_recognition_model_description": "โมเดลเรียงตามขนาดลดหลั่นลงมา โมเดลที่ใหญ่กว่าจะประมวลผลช้ากว่าและใช้หน่วยความจำมากกว่า แต่ให้ผลลัพธ์ที่ดีขึ้น หมายเหตุไว้ว่าเมื่อเปลี่ยนโมเดล คุณต้องรันงานตรวจจับใบหน้าทุกภาพใหม่ทั้งหมด", "machine_learning_facial_recognition_setting": "เปิดใช้การจดจําใบหน้า", - "machine_learning_facial_recognition_setting_description": "หากปิดใช้งาน จะไม่มีการตรวจจับใบหน้าบนรูปภาพและจะไม่มีส่วนผู้คนในหน้าเว็บ", + "machine_learning_facial_recognition_setting_description": "หากปิดใช้งาน จะไม่มีการจดจำใบหน้าบนรูปภาพและจะไม่มีส่วนผู้คนในหน้าเว็บ", "machine_learning_max_detection_distance": "ระยะทางการตรวจจับสูงสุด", "machine_learning_max_detection_distance_description": "ระยะห่างระหว่างสองภาพที่ไกลสุดที่ถือว่าเป็นภาพซ้ำ ค่าระหว่าง 0.001-0.1 ค่ายิ่งสูงจะยิ่งเจอภาพซ้ำมากขึ้น แต่อาจมีผลผิดพลาด", "machine_learning_max_recognition_distance": "ระยะทางการจดจำสูงสุด", @@ -118,8 +118,8 @@ "machine_learning_min_detection_score_description": "ค่าความมั่นใจในการตรวจจับใบหน้า จาก 0-1 ค่ายิ่งต่ำจะยิ่งตรวจจับใบหน้ามากขึ้น แต่อาจมีผลผิดพลาด", "machine_learning_min_recognized_faces": "จดจำใบหน้าขั้นต่ำ", "machine_learning_min_recognized_faces_description": "จำนวนใบหน้าขั้นต่ำที่จะสร้างคนขึ้นมา การเพิ่มค่านี้จะทำให้การจดจำใบหน้าแม่นยำกว่าแต่เพิ่มโอกาสที่ใบหน้าจะไม่ถูกมอบหมายให้กับบุคคล", - "machine_learning_settings": "การตั้งค่า Machine Learning", - "machine_learning_settings_description": "การจัดการฟีเจอร์และการตั้งค่า machine learning", + "machine_learning_settings": "การตั้งค่า machine learning", + "machine_learning_settings_description": "จัดการการตั้งค่า machine learning", "machine_learning_smart_search": "การค้นหาอัจฉริยะ", "machine_learning_smart_search_description": "ค้นหาภาพโดยใช้ความหมายจากการใช้ CLIP", "machine_learning_smart_search_enabled": "เปิดใช้งานการค้นหาอัจฉริยะ", @@ -137,11 +137,11 @@ "map_reverse_geocoding": "ประมวลผลชื่อทางภูมิศาสตร์", "map_reverse_geocoding_enable_description": "เปิดใช้งานประมวลผลชื่อทางภูมิศาสตร์", "map_reverse_geocoding_settings": "การตั้งค่าประมวลผลชื่อทางภูมิศาสตร์", - "map_settings": "แผนที่", + "map_settings": "การตั้งค่าแผนที่และ GPS", "map_settings_description": "จัดการการตั้งค่าแผนที่", "map_style_description": "URL ไปยังธีมแผนที่ style.json", "metadata_extraction_job": "ดึงข้อมูล metadata", - "metadata_extraction_job_description": "ดึงข้อมูล metadata จากสื่อ เช่น GPS ใบหน้าและความละเอียด", + "metadata_extraction_job_description": "ดึงข้อมูล metadata จากสื่อ เช่น GPS และความคมชัด", "metadata_faces_import_setting": "เปิดการนำเข้าข้อมูลใบหน้า", "metadata_faces_import_setting_description": "นำเข้าข้อมูลใบหน้าจาก EXIF ของไฟล์ภาพและไฟล์ประกอบ", "metadata_settings": "การตั้งค่า Metadata", @@ -154,17 +154,17 @@ "note_cannot_be_changed_later": "หมายเหตุ: ไม่สามารถเปลี่ยนภายหลังได้!", "note_unlimited_quota": "หมายเหตุ: ใส่เลข 0 สําหรับโควต้าไม่จํากัด", "notification_email_from_address": "จากที่อยู่", - "notification_email_from_address_description": "อีเมลผู้ส่ง อย่างเช่น \"Immich Photo Server \"", + "notification_email_from_address_description": "อีเมลผู้ส่ง อย่างเช่น \"Immich Photo Server \"", "notification_email_host_description": "ที่อยู่เซิร์ฟเวอร์อีเมล (เช่น smtp.immich.app)", "notification_email_ignore_certificate_errors": "ไม่สนใจข้อผิดพลาดเกี่ยวกับใบรับรอง", "notification_email_ignore_certificate_errors_description": "ไม่สนใจการยืนยันใบรับรอง TLS ผิดพลาด (ไม่แนะนำ)", "notification_email_password_description": "รหัสผ่านที่ใช้เมื่อเข้าถึงเซิร์ฟเวอร์อีเมล", "notification_email_port_description": "พอร์ตของเซิร์ฟเวอร์อีเมล (เช่น 25, 465, หรือ 587)", - "notification_email_sent_test_email_button": "ส่งอีเมลทดสอบและบันทึก", + "notification_email_sent_test_email_button": "ส่งอีเมลทดลองและบันทึก", "notification_email_setting_description": "การตั้งค่าสำหรับการส่งการแจ้งเตือนอีเมล", "notification_email_test_email": "ส่งอีเมลทดลอง", - "notification_email_test_email_failed": "ส่งอีเมลทดลองล้มเหลว โปรดตรวจสอบค่าที่ตั้ง", - "notification_email_test_email_sent": "อีเมลทดสอบถูกส่งไปยัง {email} กรุณาตรวจสอบกล่องจดหมาย", + "notification_email_test_email_failed": "ส่งอีเมลทดลองล้มเหลว โปรดตรวจสอบค่าที่ตั้งไว้", + "notification_email_test_email_sent": "อีเมลทดลองถูกส่งไปยัง {email} กรุณาตรวจสอบกล่องจดหมาย", "notification_email_username_description": "ชื่อผู้ใช้งานเมื่อเข้าถึงเซิร์ฟเวอร์อีเมล", "notification_enable_email_notifications": "เปิดการแจ้งเตือนผ่านอีเมล", "notification_settings": "การตั้งค่าการแจ้งเตือน", @@ -180,7 +180,7 @@ "oauth_issuer_url": "ผู้ออก URL", "oauth_mobile_redirect_uri": "URI เปลี่ยนเส้นทางบนโทรศัพท์", "oauth_mobile_redirect_uri_override": "แทนที่ URI เปลี่ยนเส้นทางบนโทรศัพท์", - "oauth_mobile_redirect_uri_override_description": "เปิดเมื่อ OAuth ไม่รองรับ URI บนอุปกรณ์ เช่น '{callback}'", + "oauth_mobile_redirect_uri_override_description": "เปิดเมื่อ 'app.immich:/' เป็น URI ที่เปลี่ยนเส้นทางไม่ถูกต้อง", "oauth_profile_signing_algorithm": "อัลกอริทึมการรับรองบัญชีผู้ใช้", "oauth_profile_signing_algorithm_description": "อัลกอริทึมใช้ในการรับรองบัญชีผู้ใช้", "oauth_scope": "ขอบเขต", @@ -199,7 +199,7 @@ "password_enable_description": "ล็อกอินกับอีเมลและรหัสผ่าน", "password_settings": "ล็อกอินผ่านรหัสผ่าน", "password_settings_description": "จัดการการตั้งค่าของการล็อกอินผ่านรหัสผ่าน", - "paths_validated_successfully": "พาธทั้งหมดถูกตรวจสอบสำเร็จแล้ว", + "paths_validated_successfully": "เส้นทางทั้งหมดถูกตรวจสอบสำเร็จแล้ว", "quota_size_gib": "โควตา (GiB)", "refreshing_all_libraries": "รีเฟรชคลังภาพทั้งหมด", "registration": "ลงทะเบียนผู้จัดการ", @@ -208,15 +208,15 @@ "repair_all": "ซ่อมแซมทั้งหมด", "repair_matched_items": "จับคู่ {count, plural, one {# รายการ} other {# รายการ}}", "repaired_items": "ซ่อมแซม {count, plural, one {# รายการ} other {# รายการ}}", - "require_password_change_on_login": "บังคับผู้ใช้ให้เปลี่ยนรหัสผ่านเมื่อเข้าสู่ระบบครั้งแรก", + "require_password_change_on_login": "บังคับผู้ใช้งานให้เปลี่ยนรหัสผ่านเมื่อเข้าสู่ระบบครั้งแรก", "reset_settings_to_default": "ตั้งค่าการตั้งค่าเป็นค่าเริ่มต้น", "reset_settings_to_recent_saved": "ตั้งค่าการตั้งค่าเป็นค่าล่าสุด", "scanning_library_for_changed_files": "สแกนคลังภาพสำหรับไฟล์ที่เปลี่ยนไป", "scanning_library_for_new_files": "สแกนคลังภาพสำหรับไฟล์ใหม่", "send_welcome_email": "ส่งอีเมลต้อนรับ", "server_external_domain_settings": "โดเมนภายนอก", - "server_external_domain_settings_description": "โดเมนสำหรับลิงก์แชร์สาธารณะ รวม http(s)://", - "server_settings": "ตั้งค่าเซิร์ฟเวอร์", + "server_external_domain_settings_description": "โดเมนสำหรับลิงก์แชร์สาธารณะ แบบมี http(s)://", + "server_settings": "การตั้งค่าเซิร์ฟเวอร์", "server_settings_description": "จัดการการตั้งค่าเซิร์ฟเวอร์", "server_welcome_message": "ข้อความต้อนรับ", "server_welcome_message_description": "ข้อความที่แสดงบนหน้าล็อกอิน", @@ -227,13 +227,13 @@ "storage_template_date_time_description": "เวลาประทับบนสื่อถูกใช้สำหรับข้อมูลวันเวลา", "storage_template_date_time_sample": "ตัวอย่างเวลา {date}", "storage_template_enable_description": "เปิดใช้งานการจัดเทมเพลตที่เก็บข้อมูล", - "storage_template_hash_verification_enabled": "เปิดใช้การตรวจสอบ Hash แล้ว", + "storage_template_hash_verification_enabled": "ตรวจสอบ hash ไม่ผ่าน", "storage_template_hash_verification_enabled_description": "เปิดใช้งานการตรวจสอบ hash ห้ามปิดใช้งานเว้นแต่คุณจะเข้าใจผลกระทบ", "storage_template_migration": "การย้ายเทมเพลตที่เก็บข้อมูล", "storage_template_migration_description": "ใช้{template}ปัจจุบันกับสื่อที่อัพโหลดก่อนหน้านี้", "storage_template_migration_job": "", "storage_template_settings": "เทมเพลตการจัดเก็บข้อมูล", - "storage_template_settings_description": "", + "storage_template_settings_description": "จัดการโครงสร้างโฟลเดอร์และชื่อไฟล์ที่อัพโหลด", "system_settings": "การตั้งค่าระบบ", "theme_custom_css_settings": "CSS กําหนดเอง", "theme_custom_css_settings_description": "Cascading Style Sheets ช่วยให้ปรับแต่งเค้าโครง Immich ได้", @@ -252,14 +252,14 @@ "transcoding_accepted_audio_codecs": "แบบไฟล์เสียงที่ยอมรับ", "transcoding_accepted_audio_codecs_description": "เลือกแบบไฟล์เสียงที่จะไม่ถูกแปลงใหม่ ใช้สําหรับกฏการแปลงแบบไฟล์", "transcoding_accepted_video_codecs": "แบบไฟล์วิดีโอที่ยอมรับ", - "transcoding_accepted_video_codecs_description": "เลือกแบบไฟล์วิดีโอที่จะไม่ถูกแปลงใหม่ ใช้สําหรับกฏการแปลงแบบไฟล์", - "transcoding_advanced_options_description": "ตัวเลือกที่ผู้ใช้ส่วนใหญ่ไม่จำเป็นต้องเปลี่ยน", + "transcoding_accepted_video_codecs_description": "เลือกแบบไฟล์วิดีโอที่จะไม่ถูกแปลงใหม่ ใช้สําหรับกฎการแปลงแบบไฟล์", + "transcoding_advanced_options_description": "ตัวเลือกที่ผู้ใช้งานส่วนใหญ่ไม่จำเป็นต้องเปลี่ยน", "transcoding_audio_codec": "แบบไฟล์เสียง", - "transcoding_audio_codec_description": "Opus ให้คุณภาพสูงสุด แต่อุปกรณ์เก่าหรือซอฟต์แวร์เก่าอาจจะเข้ากันไม่ได้", + "transcoding_audio_codec_description": "Opus ให้คุณภาพสูงสุด แต่อาจจะเข้ากันไม่ได้กับอุปกรณ์เก่าหรือซอฟต์แวร์เก่า", "transcoding_bitrate_description": "วิดีโอมีค่า bitrate สูงกว่าค่าสูงสุดหรือไฟล์วิดีโอไม่รองรับ", "transcoding_constant_quality_mode": "โหมดคุณภาพคงที่", - "transcoding_constant_quality_mode_description": "ICQ ดีกว่า CQP แต่อุปกรณ์บางตัวอาจจะไม่รองรับโหมดนี้ การตั้งค่าตัวนี้จะเลือกโหมดที่ระบุไว้เมื่อใช้การแปลงคุณภาพไฟล์ ไม่สน NVENC เพราะไม่รองรับ ICQ", - "transcoding_constant_rate_factor": "ปัจจัยค่าคงที่ (-crf)", + "transcoding_constant_quality_mode_description": "ICQ ดีกว่า CQP แต่อุปกรณ์บางตัวอาจจะไม่รองรับโหมดนี้ การตั้งค่าตัวนี้จะเลือกโหมดที่ระบุไว้เมื่อใช้การแปลงคุณภาพไฟล์ ไม่สนใจ NVENC เพราะไม่รองรับ ICQ", + "transcoding_constant_rate_factor": "ตัวแปรค่าคงที่ (-crf)", "transcoding_constant_rate_factor_description": "คุณภาพของวิดีโอ ค่าโดยปกติคือ 23 สําหรับ H.264, 28 สําหรับ HEVC, 31 สําหรับ VP9 และ 35 สําหรับ AV1 ค่าต่ำกว่าคุณภาพจะดีกว่า แต่ไฟล์จะขนาดใหญ่กว่า", "transcoding_disabled_description": "ไม่แปลงไฟล์วิดีโอเลย อาจเล่นวิดีโอในเครื่องเล่นบางตัวไม่ได้", "transcoding_hardware_acceleration": "การเร่งความเร็วด้วยฮาร์ดแวร์", @@ -273,7 +273,7 @@ "transcoding_max_bitrate_description": "การตั้งค่า bitrate สูงสุดจะสามารถคาดเดาขนาดไฟล์ได้มากขึ้นโดยไม่กระทบคุณภาพ สำหรับความคมชัด 720p ค่าทั่วไปคือ 2600k สําหรับ VP9 หรือ HEVC, 4500k สําหรับ H.264 ปิดการตั้งค่าเมี่อตั้งค่าเป็น 0", "transcoding_max_keyframe_interval": "", "transcoding_max_keyframe_interval_description": "", - "transcoding_optimal_description": "วิดีโอที่สูงกว่าความละเอียดเป้าหมายหรือไม่ได้อยู่ในรูปแบบที่รองรับ", + "transcoding_optimal_description": "วีดิโอมีความคมชัดสูงกว่าเป้าหมายหรืออยู่ในรูปแบบที่รับไม่ได้", "transcoding_preferred_hardware_device": "", "transcoding_preferred_hardware_device_description": "", "transcoding_preset_preset": "", @@ -282,91 +282,91 @@ "transcoding_reference_frames_description": "", "transcoding_required_description": "", "transcoding_settings": "", - "transcoding_settings_description": "", - "transcoding_target_resolution": "ความละเอียดเป้าหมาย", - "transcoding_target_resolution_description": "", + "transcoding_settings_description": "จัดการข้อมูลความคมชัดและแบบไฟล์วิดีโอ", + "transcoding_target_resolution": "เป้าหมายความคมชัด", + "transcoding_target_resolution_description": "ความคมชัดที่สูงกว่าจะเก็บรายละเอียดดีกว่าแต่ใช้เวลาแปลงไฟล์นานกว่า ขนาดไฟล์ใหญ่กว่า และลดการตอบสนองของแอพ", "transcoding_temporal_aq": "", "transcoding_temporal_aq_description": "", "transcoding_threads": "เธรด", - "transcoding_threads_description": "", + "transcoding_threads_description": "ค่ายิ่งเยอะจะแปลงไฟล์เร็วกว่า แต่จะเหลือพื้นที่ให้เซิร์ฟเวอร์ประมวลผลงานอื่นน้อยลงเมื่อทํางานนี้ ค่านี้ไม่ควรมากกว่าจํานวน CPU core จะประมวลผลเต็มที่เมื่อตั้งเป็น 0", "transcoding_tone_mapping": "การฉายโทนสี", "transcoding_tone_mapping_description": "", "transcoding_tone_mapping_npl": "", "transcoding_tone_mapping_npl_description": "", - "transcoding_transcode_policy": "", - "transcoding_two_pass_encoding": "", - "transcoding_two_pass_encoding_setting_description": "", - "transcoding_video_codec": "", - "transcoding_video_codec_description": "", - "trash_enabled_description": "", - "trash_number_of_days": "", - "trash_number_of_days_description": "", + "transcoding_transcode_policy": "กฎการแปลงไฟล์", + "transcoding_two_pass_encoding": "การแปลงไฟล์สองรอบ", + "transcoding_two_pass_encoding_setting_description": "การแปลงไฟล์สองรอบจะช่วยให้ได้วิดีโอที่ดีขึ้น เมื่อเปิดใช้งาน bitrate สูงสุด (จำเป็นสำหรับไฟล์ H.264 และ HEVC) โหมดนี้จะใช้ช่วง bitrate ที่ขึ้นอยู่กับค่า bitrate สูงสุดและไม่สนใจ CRF สำหรับ VP9 สามารถใช้ค่า CRF ได้ถ้าปิดใช้งาน bitrate สูงสุด", + "transcoding_video_codec": "แบบไฟล์วิดีโอ", + "transcoding_video_codec_description": "VP9 มีประสิทธิภาพสูงและเข้ากันกับเว็บได้ดี แต่ใช้เวลาแปลงไฟล์นานกว่า HEVC มีประสิทธิภาพคล้ายกัน แต่เข้ากันกับเว็บได้น้อยกว่า H.264 เข้ากันกับทุกอุปกรณ์ และแปลงไฟล์เร็ว แต่ได้ไฟล์ที่ใหญ่ขึ้น AV1 เป็นไฟล์ที่มีประสิทธิภาพมากที่สุด แต่ไม่เข้ากันกับอุปกรณ์เก่า", + "trash_enabled_description": "เปิดใช้งานถังขยะ", + "trash_number_of_days": "จํานวนวัน", + "trash_number_of_days_description": "จํานวนวันที่เก็บสื่อไว้ในถังขยะก่อนที่จะลบถาวร", "trash_settings": "การตั้งค่าถังขยะ", "trash_settings_description": "จัดการการตั้งค่าถังขยะ", - "user_delete_delay_settings": "", - "user_delete_delay_settings_description": "", - "user_settings": "", - "user_settings_description": "", - "version_check_enabled_description": "", - "version_check_settings": "", - "version_check_settings_description": "", - "video_conversion_job_description": "" + "user_delete_delay_settings": "ลบการถ่วงเวลา", + "user_delete_delay_settings_description": "จํานวนวันหลังจากที่เอาออกเพื่อลบบัญชีผู้ใช้และสื่อถาวร งานลบบัญชีผู้ใช้ทํางานทุกเที่ยงคืนเพื่อตรวจสอบผู้ใช้ที่พร้อมที่จะถูกลบข้อมูลแล้ว การตั้งค่าครั้งนี้จะมีผลครั้งต่อไป", + "user_settings": "การตั้งค่าผู้ใช้", + "user_settings_description": "จัดการการตั้งค่าผู้ใช้", + "version_check_enabled_description": "เช็ค GitHub เป็นระยะ ๆ เพื่อตรวจสอบรุ่นใหม่", + "version_check_settings": "ตรวจสอบรุ่น", + "version_check_settings_description": "เปิด/ปิดการแจ้งเตือนรุ่นใหม่", + "video_conversion_job_description": "แปลงไฟล์วิดีโอเพึ่อรองรับบราวเซอร์และเครื่องเล่นอื่น ๆ มากขึ้น" }, - "admin_email": "อีเมลผู้ดูแล", - "admin_password": "รหัสผ่านผู้ดูแล", - "administration": "การจัดการ", + "admin_email": "อีเมลผู้ดูแลระบบ", + "admin_password": "รหัสผ่านผู้ดูแลระบบ", + "administration": "การดูแลระบบ", "advanced": "ขั้นสูง", "age_months": "อายุ {months, plural, one {# เดือน} other {# เดือน}}", "age_year_months": "อายุ 1 ปี {months, plural, one {# เดือน} other {# เดือน}}", "age_years": "{years, plural, other {อายุ #}}", "album_added": "เพิ่มอัลบั้มแล้ว", - "album_added_notification_setting_description": "", - "album_cover_updated": "", - "album_info_updated": "", - "album_name": "", - "album_options": "", - "album_updated": "", - "album_updated_setting_description": "", + "album_added_notification_setting_description": "แจ้งเตือนอีเมลเมื่อคุณถูกเพิ่มไปในอัลบั้มที่แชร์กัน", + "album_cover_updated": "อัพเดทหน้าปกอัลบั้มแล้ว", + "album_info_updated": "อัพเดทข้อมูลอัลบั้มแล้ว", + "album_name": "ชื่ออัลบั้ม", + "album_options": "ตัวเลือกอัลบั้ม", + "album_updated": "อัพเดทอัลบั้มแล้ว", + "album_updated_setting_description": "แจ้งเตือนอีเมลเมื่ออัลบั้มที่แชร์กันมีสื่อใหม่", "albums": "อัลบั้ม", "all": "ทั้งหมด", "all_albums": "อัลบั้มทั้งหมด", - "all_people": "ผู้คนทั้งหมด", + "all_people": "ทุกคน", "all_videos": "วิดีโอทั้งหมด", - "allow_dark_mode": "", - "allow_edits": "", - "api_key": "คีย์ API", - "api_keys": "คีย์ API", - "app_settings": "การตั้งค่าแอป", - "appears_in": "ปรากฏอยู่ใน", + "allow_dark_mode": "อนุญาตโหมดมืด", + "allow_edits": "อนุญาตให้แก้ไขได้", + "api_key": "กุญแจ API", + "api_keys": "กุญแจ API", + "app_settings": "การตั้งค่าแอพ", + "appears_in": "อยู่ใน", "archive": "เก็บถาวร", - "archive_or_unarchive_photo": "", + "archive_or_unarchive_photo": "เก็บ/ไม่เก็บภาพถาวร", "archived": "เก็บถาวร", "are_these_the_same_person": "เป็นคนเดียวกันหรือไม่?", - "asset_offline": "", + "asset_offline": "สื่อออฟไลน์", "asset_skipped": "ข้ามแล้ว", "asset_uploaded": "อัปโหลดแล้ว", "asset_uploading": "กำลังอัปโหลด...", - "assets": "ทรัพยากร", - "authorized_devices": "", + "assets": "สื่อ", + "authorized_devices": "อุปกรณ์ที่ได้รับอนุญาต", "back": "กลับ", "backward": "กลับหลัง", - "blurred_background": "", + "blurred_background": "พื้นหลังแบบเบลอ", "camera": "กล้อง", - "camera_brand": "", - "camera_model": "", + "camera_brand": "ยี่ห้อกล้อง", + "camera_model": "รุ่นกล้อง", "cancel": "ยกเลิก", - "cancel_search": "", - "cannot_merge_people": "", - "cannot_update_the_description": "", + "cancel_search": "ยกเลิกการค้นหา", + "cannot_merge_people": "ไม่สามารถรวมกลุ่มคนได้", + "cannot_update_the_description": "ไม่สามารถอัพเดทรายละเอียดได้", "cant_apply_changes": "", "cant_get_faces": "", "cant_search_people": "", "cant_search_places": "", - "change_date": "", + "change_date": "เปลี่ยนวันที่", "change_expiration_time": "เปลี่ยนเวลาหมดอายุ", - "change_location": "", - "change_name": "", - "change_name_successfully": "", + "change_location": "เปลี่ยนตําแหน่ง", + "change_name": "เปลี่ยนชื่อ", + "change_name_successfully": "เปลี่ยนชื่อเรียบร้อยแล้ว", "change_password": "เปลี่ยนรหัสผ่าน", "change_your_password": "", "changed_visibility_successfully": "", @@ -382,61 +382,61 @@ "comment_options": "", "comments_are_disabled": "", "confirm": "ยืนยัน", - "confirm_admin_password": "", + "confirm_admin_password": "ยืนยันรหัสผ่านผู้ดูแลระบบ", "confirm_password": "ยืนยันรหัสผ่าน", "contain": "มี", "context": "บริบท", "continue": "ต่อไป", - "copied_image_to_clipboard": "", - "copy_error": "", - "copy_file_path": "คัดลอกพาธไฟล์", - "copy_image": "", - "copy_link": "", - "copy_link_to_clipboard": "", - "copy_password": "", - "copy_to_clipboard": "", + "copied_image_to_clipboard": "คัดลอกภาพไปยังคลิปบอร์ดแล้ว", + "copy_error": "คัดลอกข้อผิดพลาด", + "copy_file_path": "คัดลอกพาธของไฟล์", + "copy_image": "คัดลอกภาพ", + "copy_link": "คัดลอกลิงก์", + "copy_link_to_clipboard": "คัดลอกลิงก์ไปยังคลิปบอร์ด", + "copy_password": "คัดลอกรหัสผ่าน", + "copy_to_clipboard": "คัดลอกไปยังคลิปบอร์ด", "country": "ประเทศ", "cover": "ปก", "covers": "ปก", "create": "สร้าง", "create_album": "สร้างอัลบั้ม", - "create_library": "", + "create_library": "สร้างคลังภาพ", "create_link": "สร้างลิงก์", "create_link_to_share": "สร้างลิงก์เพื่อแชร์", - "create_new_person": "", - "create_new_user": "", - "create_user": "", - "created": "สร้าง", - "current_device": "", - "custom_locale": "", - "custom_locale_description": "", + "create_new_person": "สร้างคนใหม่", + "create_new_user": "สร้างผู้ใช้งานใหม่", + "create_user": "สร้างผู้ใช้", + "created": "สร้างแล้ว", + "current_device": "อุปกรณ์ปัจจุบัน", + "custom_locale": "ปรับภาษาท้องถิ่นเอง", + "custom_locale_description": "ใช้รูปแบบวันที่และตัวเลขจากภาษาและขอบเขต", "dark": "มืด", - "date_after": "", + "date_after": "วันที่หลังจาก", "date_and_time": "วันและเวลา", - "date_before": "", + "date_before": "วันที่ก่อน", "date_range": "ช่วงวันที่", "day": "วัน", - "default_locale": "", - "default_locale_description": "", + "default_locale": "ภาษาท้องถิ่นปกติ", + "default_locale_description": "ใช้รูปแบบวันที่และตัวเลขจากเบราว์เซอร์ของคุณ", "delete": "ลบออก", "delete_album": "ลบอัลบั้ม", - "delete_key": "", - "delete_library": "", - "delete_link": "", + "delete_key": "ลบกุญแจ", + "delete_library": "ลบคลังภาพ", + "delete_link": "ลบลิงก์", "delete_shared_link": "ลบลิงก์ที่แชร์", - "delete_user": "", - "deleted_shared_link": "", + "delete_user": "ลบผู้ใช้", + "deleted_shared_link": "ลบลิงก์ที่แชร์แล้ว", "description": "รายละเอียด", "details": "รายละเอียด", - "direction": "ทิศทาง", - "disallow_edits": "", + "direction": "เส้นทาง", + "disallow_edits": "ไม่อนุญาตให้แก้ไข", "discover": "ค้นพบ", - "dismiss_all_errors": "", - "dismiss_error": "", + "dismiss_all_errors": "ปฏิเสธข้อผิดพลาดทั้งหมด", + "dismiss_error": "ปฏิเสธข้อผิดพลาด", "display_options": "", "display_order": "", "display_original_photos": "", - "display_original_photos_setting_description": "", + "display_original_photos_setting_description": "เมื่อดูสื่อให้แสดงภาพต้นฉบับแทนภาพตัวอย่างเมื่อไฟล์สื่อเปิดได้บนเว็บ อาจทําให้แสดง ภาพได้ช้าลง", "done": "เสร็จ", "download": "ดาวน์โหลด", "downloading": "กำลังดาวน์โหลด", @@ -448,21 +448,21 @@ "months": "", "years": "" }, - "edit_album": "", - "edit_avatar": "", - "edit_date": "", - "edit_date_and_time": "", - "edit_exclusion_pattern": "", - "edit_faces": "", - "edit_import_path": "แก้ไขพาธนำเข้า", - "edit_import_paths": "แก้ไขพาธนำเข้า", - "edit_key": "", + "edit_album": "แก้ไขอัลบั้ม", + "edit_avatar": "แก้ไขตัวละคร", + "edit_date": "แก้ไขวันที่", + "edit_date_and_time": "แก้ไขวันที่และเวลา", + "edit_exclusion_pattern": "แก้ไขข้อยกเว้น", + "edit_faces": "แก้ไขหน้า", + "edit_import_path": "แก้ไขพาธนําเข้า", + "edit_import_paths": "แก้ไขพาธนําเข้า", + "edit_key": "แก้ไขกุญแจ", "edit_link": "แก้ไขลิงก์", "edit_location": "แก้ไขตำแหน่ง", "edit_name": "แก้ไขชื่อ", - "edit_people": "", - "edit_title": "", - "edit_user": "", + "edit_people": "แก้ไขผู้คน", + "edit_title": "แก้ไขชื่อ", + "edit_user": "แก้ไขผู้ใช้", "edited": "แก้ไขแล้ว", "editor": "ผู้แก้ไข", "email": "อีเมล", @@ -471,61 +471,61 @@ "empty_trash": "ทิ้งจากถังขยะ", "enable": "เปิดใช้งาน", "enabled": "เปิดใช้งาน", - "end_date": "", + "end_date": "วันสิ้นสุด", "error": "เกิดข้อผิดพลาด", - "error_loading_image": "", + "error_loading_image": "เกิดข้อผิดพลาดระหว่างโหลดภาพ", "errors": { "import_path_already_exists": "พาธนำเข้านี้มีอยู่แล้ว", - "unable_to_add_album_users": "", - "unable_to_add_comment": "", - "unable_to_add_partners": "", - "unable_to_change_album_user_role": "", - "unable_to_change_date": "", - "unable_to_change_location": "", + "unable_to_add_album_users": "ไม่สามารถเพิ่มผู้ใช้ไปยังอัลบั้มได้", + "unable_to_add_comment": "ไม่สามารถเพิ่มความเห็นได้", + "unable_to_add_partners": "ไม่สามารถเพิ่มคู่หูได้", + "unable_to_change_album_user_role": "ไม่สามารถเปลี่ยนบทบาทผู้ใช้ในอัลบั้มได้", + "unable_to_change_date": "ไม่สามารถเปลี่ยนวันที่ได้", + "unable_to_change_location": "ไม่สามารถเปลี่ยนตําแหน่งได้", "unable_to_check_item": "", "unable_to_check_items": "", "unable_to_create_admin_account": "", - "unable_to_create_library": "", - "unable_to_create_user": "", - "unable_to_delete_album": "ไม่สามารถลบอัลบั้ม", - "unable_to_delete_asset": "", - "unable_to_delete_user": "", - "unable_to_empty_trash": "", - "unable_to_enter_fullscreen": "", - "unable_to_exit_fullscreen": "", - "unable_to_hide_person": "", - "unable_to_load_album": "", - "unable_to_load_asset_activity": "", - "unable_to_load_items": "", - "unable_to_load_liked_status": "", - "unable_to_play_video": "", - "unable_to_refresh_user": "", - "unable_to_remove_album_users": "", + "unable_to_create_library": "ไม่สามารถสร้างคลังภาพได้", + "unable_to_create_user": "ไม่สามารถสร้างผู้ใช้ได้", + "unable_to_delete_album": "ไม่สามารถลบอัลบั้มได้", + "unable_to_delete_asset": "ไม่สามารถลบสื่อได้", + "unable_to_delete_user": "ไม่สามารถลบผู้ใช้ได้", + "unable_to_empty_trash": "ไม่สามารถลบถังขยะได้", + "unable_to_enter_fullscreen": "ไม่สามารถเปิดเต็มจอได้", + "unable_to_exit_fullscreen": "ไม่สามารถออกโหมดเต็มจอได้", + "unable_to_hide_person": "ไม่สามารถซ่อนบุคคลได้", + "unable_to_load_album": "ไม่สามารถโหลดอัลบั้มได้", + "unable_to_load_asset_activity": "ไม่สามารถโหลดข้อมูลของสื่อได้", + "unable_to_load_items": "ไม่สามารถโหลดรายการได้", + "unable_to_load_liked_status": "ไม่สามารถโหลดสถานะ like ได้", + "unable_to_play_video": "ไม่สามารถเล่นวิดีโอได้", + "unable_to_refresh_user": "ไม่สามารถรีเฟรชผู้ใช้ได้", + "unable_to_remove_album_users": "ไม่สามารถลบผู้ใช้ออกจากอัลบั้มได้", "unable_to_remove_comment": "", - "unable_to_remove_library": "", - "unable_to_remove_partner": "", - "unable_to_remove_reaction": "", + "unable_to_remove_library": "ไม่สามารถลบคลังภาพได้", + "unable_to_remove_partner": "ไม่สามารถลบคู่หูได้", + "unable_to_remove_reaction": "ไม่สามารถลบ reaction ได้", "unable_to_remove_user": "", - "unable_to_repair_items": "", - "unable_to_reset_password": "", - "unable_to_resolve_duplicate": "", - "unable_to_restore_assets": "", - "unable_to_restore_trash": "", - "unable_to_restore_user": "", - "unable_to_save_album": "", - "unable_to_save_name": "", - "unable_to_save_profile": "", - "unable_to_save_settings": "", - "unable_to_scan_libraries": "", - "unable_to_scan_library": "", - "unable_to_set_profile_picture": "", - "unable_to_submit_job": "", - "unable_to_trash_asset": "", - "unable_to_unlink_account": "", - "unable_to_update_library": "", - "unable_to_update_location": "", - "unable_to_update_settings": "", - "unable_to_update_user": "" + "unable_to_repair_items": "ไม่สามารถซ่อมแซมรายการได้", + "unable_to_reset_password": "ไม่สามารถตั้งรหัสผ่านใหม่ได้", + "unable_to_resolve_duplicate": "ไม่สามารถแก้ไขของซ้ำได้", + "unable_to_restore_assets": "ไม่สามารถเรียกคืนสื่อได้", + "unable_to_restore_trash": "ไม่สามารถเรียกคืนถังขยะได้", + "unable_to_restore_user": "ไม่สามารถเรียกคืนผู้ใช้ได้", + "unable_to_save_album": "ไม่สามารถบันทึกอัลบั้มได้", + "unable_to_save_name": "ไม่สามารถบันทึกชื่อได้", + "unable_to_save_profile": "ไม่สามารถบันทึกโปรไฟล์ได้", + "unable_to_save_settings": "ไม่สามารถบันทึกการตั้งค่าได้", + "unable_to_scan_libraries": "ไม่สามารถสแกนคลังภาพได้", + "unable_to_scan_library": "ไม่สามารถสแกนคลังภาพได้", + "unable_to_set_profile_picture": "ไม่สามารถตั้งภาพโปรไฟล์ได้", + "unable_to_submit_job": "ไม่สามารถส่งงานได้", + "unable_to_trash_asset": "ไม่สามารถทิ้งสื่อได้", + "unable_to_unlink_account": "ไม่สามารถยกเลิกการเชื่อมโยงบัญชีผู้ใช้ได้", + "unable_to_update_library": "ไม่สามารถอัพเดทคลังภาพได้", + "unable_to_update_location": "ไม่สามารถอัพเดทตําแหน่งได้", + "unable_to_update_settings": "ไม่สามารถอัพเดทการตั้งค่าได้", + "unable_to_update_user": "ไม่สามารถอัพเดทผู้ใช้ได้" }, "every_day_at_onepm": "", "every_night_at_midnight": "", @@ -537,20 +537,20 @@ "expired": "หมดอายุแล้ว", "explore": "สํารวจ", "extension": "ส่วนต่อขยาย", - "external_libraries": "", + "external_libraries": "ภายนอกคลังภาพ", "failed_to_get_people": "", "favorite": "รายการโปรด", - "favorite_or_unfavorite_photo": "", + "favorite_or_unfavorite_photo": "โปรดหรือไม่โปรดภาพ", "favorites": "รายการโปรด", "feature": "", - "feature_photo_updated": "", + "feature_photo_updated": "อัพเดทภาพเด่นแล้ว", "featurecollection": "", "file_name": "", "file_name_or_extension": "", "filename": "ชื่อไฟล์", "files": "", "filetype": "ชนิดไฟล์", - "filter_people": "", + "filter_people": "กรองผู้คน", "fix_incorrect_match": "", "force_re-scan_library_files": "", "forward": "ไปข้างหน้า", @@ -562,124 +562,124 @@ "go_to_share_page": "", "group_albums_by": "", "has_quota": "", - "hide_gallery": "", + "hide_gallery": "ซ่อนคลังภาพ", "hide_password": "", - "hide_person": "", + "hide_person": "ซ่อนบุคคล", "host": "โฮสต์", "hour": "ชั่วโมง", "image": "รูปภาพ", "img": "", "immich_logo": "", - "import_path": "", - "in_archive": "", - "include_archived": "รวมเก็บถาวร", - "include_shared_albums": "", - "include_shared_partner_assets": "", - "individual_share": "", + "import_path": "นำเข้าพาธ", + "in_archive": "ในที่เก็บถาวร", + "include_archived": "รวมไฟล์เก็บถาวร", + "include_shared_albums": "รวมอัลบั้มที่แชร์กัน", + "include_shared_partner_assets": "รวมสื่อที่แชร์กับคู่หู", + "individual_share": "แชร์ส่วนตัว", "info": "ข้อมูล", "interval": { - "day_at_onepm": "", + "day_at_onepm": "ทุกวันเวลาบ่ายโมง", "hours": "", - "night_at_midnight": "", - "night_at_twoam": "" + "night_at_midnight": "ทุกเที่ยงคืน", + "night_at_twoam": "ทุกวันเวลาตี 2" }, - "invite_people": "", + "invite_people": "เชิญผู้คน", "invite_to_album": "เชิญเข้าอัลบั้ม", "job_settings_description": "", "jobs": "งาน", "keep": "เก็บ", - "keyboard_shortcuts": "", + "keyboard_shortcuts": "ปุ่มพิมพ์ลัด", "language": "ภาษา", - "language_setting_description": "", - "last_seen": "", + "language_setting_description": "เลือกภาษาที่ต้องการ", + "last_seen": "เห็นล่าสุด", "leave": "ทิ้ง", "let_others_respond": "ให้คนอื่นตอบ", "level": "ระดับ", - "library": "คลัง", - "library_options": "", + "library": "คลังภาพ", + "library_options": "ตัวเลือกคลังภาพ", "light": "สว่าง", - "link_options": "", - "link_to_oauth": "", - "linked_oauth_account": "", + "link_options": "ตัวเลือกลิงก์", + "link_to_oauth": "ลิงก์ไปยัง OAuth", + "linked_oauth_account": "ลิงก์บัญชีผู้ใช้ OAuth", "list": "รายการ", "loading": "กำลังโหลด", - "loading_search_results_failed": "", + "loading_search_results_failed": "โหลดผลการค้นหาล้มเหลว", "log_out": "ออกจากระบบ", - "log_out_all_devices": "", - "login_has_been_disabled": "", - "look": "", - "loop_videos": "", + "log_out_all_devices": "ให้ทุกอุปกรณ์ออกจากระบบทั้งหมด", + "login_has_been_disabled": "ปิดการใช้งานการเข้าสู่ระบบแล้ว", + "look": "ดู", + "loop_videos": "วนวิดีโอ", "loop_videos_description": "เปิดเพื่อให้วิดีโอวนลูปในที่ดูรายละเอียด", - "make": "", - "manage_shared_links": "บริหารลิงก์", - "manage_sharing_with_partners": "", - "manage_the_app_settings": "", - "manage_your_account": "", - "manage_your_api_keys": "", - "manage_your_devices": "", - "manage_your_oauth_connection": "", + "make": "สร้าง", + "manage_shared_links": "จัดการลิงก์ที่แชร์", + "manage_sharing_with_partners": "จัดการการแชร์กับคู่หู", + "manage_the_app_settings": "จัดการการตั้งค่าแอพ", + "manage_your_account": "จัดการบัญชีของคุณ", + "manage_your_api_keys": "จัดการกุญแจ API ของคุณ", + "manage_your_devices": "จัดการอุปกรณ์ของคุณ", + "manage_your_oauth_connection": "จัดการการเชื่อมต่อ OAuth ของคุณ", "map": "แผนที่", "map_marker_with_image": "", - "map_settings": "ตั้งค่าแผนที่", - "media_type": "", + "map_settings": "การตั้งค่าแผนที่", + "media_type": "ชนิดสื่อ", "memories": "ความทรงจำ", - "memories_setting_description": "", + "memories_setting_description": "จัดการสิ่งที่คุณเห็นในความทรงจําของคุณ", "menu": "เมนู", - "merge": "", - "merge_people": "", - "merge_people_successfully": "", + "merge": "รวม", + "merge_people": "รวมผู้คน", + "merge_people_successfully": "รวมผู้คนเรียบร้อยแล้ว", "minimize": "ย่อลง", "minute": "นาที", "missing": "ขาดหาย", "model": "โมเดล", "month": "เดือน", "more": "เพิ่มเติม", - "moved_to_trash": "", - "my_albums": "", + "moved_to_trash": "ทิ้งลงถังขยะแล้ว", + "my_albums": "อัลบั้มของฉัน", "name": "ชื่อ", - "name_or_nickname": "", + "name_or_nickname": "ชื่อหรือชื่อเล่น", "never": "ไม่เคย", - "new_api_key": "", + "new_api_key": "กุญแจ API ใหม่", "new_password": "รหัสผ่านใหม่", - "new_person": "", - "new_user_created": "", - "newest_first": "", + "new_person": "คนใหม่", + "new_user_created": "สร้างผู้ใช้ใหม่แล้ว", + "newest_first": "ใหม่สุดก่อน", "next": "ต่อไป", "next_memory": "", "no": "ไม่", "no_albums_message": "", - "no_archived_assets_message": "", - "no_assets_message": "", + "no_archived_assets_message": "จัดเก็บรูปภาพและวีดิโอถาวรเพื่อซ่อนจากมุมมองคุณ", + "no_assets_message": "กดเพื่อใส่ภาพคุณภาพแรก", "no_exif_info_available": "", "no_explore_results_message": "", - "no_favorites_message": "", - "no_libraries_message": "", - "no_name": "", - "no_places": "", - "no_results": "", - "no_shared_albums_message": "", - "not_in_any_album": "", + "no_favorites_message": "เพิ่มรายการโปรดเพื่อค้นหาภาพและวิดีโอที่ดีที่สุดของคุณอย่างรวดเร็ว", + "no_libraries_message": "สร้างคลังภาพภายนอกเพื่อดูภาพถ่ายและวิดีโอต่าง ๆ ของคุณ", + "no_name": "ไม่มีชื่อ", + "no_places": "ไม่มีสถานที่", + "no_results": "ไม่มีผลลัพธ์", + "no_shared_albums_message": "สร้างอัลบั้มเพื่อแชร์รูปภาพและวิดีโอกับคนในเครือข่ายของคุณ", + "not_in_any_album": "ไม่อยู่ในอัลบั้มใด ๆ", "notes": "หมายเหตุ", - "notification_toggle_setting_description": "", + "notification_toggle_setting_description": "เปิด/ปิด การแจ้งเตือนอีเมล", "notifications": "การแจ้งเตือน", - "notifications_setting_description": "", + "notifications_setting_description": "จัดการการแจ้งเตือน", "oauth": "OAuth", "offline": "ออฟไลน์", "ok": "โอเค", - "oldest_first": "", + "oldest_first": "เก่าสุดก่อน", "online": "ออนไลน์", - "only_favorites": "", + "only_favorites": "รายการโปรดเท่านั้น", "only_refreshes_modified_files": "", - "open_the_search_filters": "", + "open_the_search_filters": "เปิดตัวกรองการค้นหา", "options": "ตัวเลือก", - "organize_your_library": "", + "organize_your_library": "จัดเรียงคลังภาพของคุณ", "other": "อื่น ๆ", "other_devices": "", "other_variables": "", "owned": "เป็นเจ้าของ", "owner": "เจ้าของ", - "partner_sharing": "", - "partners": "", + "partner_sharing": "การแชร์แบบคู่หู", + "partners": "คู่หู", "password": "รหัสผ่าน", "password_does_not_match": "", "password_required": "", @@ -696,66 +696,66 @@ "paused": "หยุด", "pending": "กำลังรอ", "people": "ผู้คน", - "people_sidebar_description": "", + "people_sidebar_description": "แสดงลิงก์ไปยังผู้คนในแถบด้านข้าง", "perform_library_tasks": "", - "permanent_deletion_warning": "", - "permanent_deletion_warning_setting_description": "", - "permanently_delete": "", - "permanently_deleted_asset": "", + "permanent_deletion_warning": "แจ้งเตือนการลบถาวร", + "permanent_deletion_warning_setting_description": "เตือนเมื่อจะลบสื่อถาวร", + "permanently_delete": "ลบถาวร", + "permanently_deleted_asset": "ลบสื่อถาวรแล้ว", "photos": "รูปภาพ", - "photos_from_previous_years": "", - "pick_a_location": "", + "photos_from_previous_years": "ภาพถ่ายจากปีก่อน", + "pick_a_location": "เลือกตําแหน่ง", "place": "สถานที่", "places": "สถานที่", "play": "เล่น", - "play_memories": "", - "play_motion_photo": "", - "play_or_pause_video": "", + "play_memories": "เล่นความทรงจํา", + "play_motion_photo": "เล่นภาพวัตถุเคลื่อนไหว", + "play_or_pause_video": "เล่นหรือหยุดวิดีโอ", "point": "", "port": "พอร์ต", "preset": "", "preview": "ตัวอย่าง", "previous": "ก่อนหน้า", - "previous_memory": "", - "previous_or_next_photo": "", + "previous_memory": "ความทรงจําก่อนหน้า", + "previous_or_next_photo": "ภาพก่อนหน้าหรือภาพถัดไป", "primary": "หลัก", - "profile_picture_set": "", - "public_share": "", + "profile_picture_set": "ตั้งภาพโปรไฟล์แล้ว", + "public_share": "แชร์แบบสาธารณะ", "range": "", "raw": "", - "reaction_options": "", - "read_changelog": "", + "reaction_options": "ตัวเลือก reaction", + "read_changelog": "อ่านบันทึกการเปลี่ยนแปลง", "recent": "ล่าสุด", - "recent_searches": "", + "recent_searches": "การค้นหาล่าสุด", "refresh": "รีเฟรช", - "refreshed": "ถูกรีเฟรช", - "refreshes_every_file": "", - "remove": "เอาออก", + "refreshed": "รีเฟรช", + "refreshes_every_file": "รีเฟรชทุกไฟล์", + "remove": "ลบ", "remove_deleted_assets": "", "remove_from_album": "ลบออกจากอัลบั้ม", - "remove_from_favorites": "", - "remove_from_shared_link": "", + "remove_from_favorites": "เอาออกจากรายการโปรด", + "remove_from_shared_link": "ลบออกจากลิงก์ที่แชร์", "repair": "ซ่อม", "repair_no_results_message": "", "replace_with_upload": "", - "require_password": "", + "require_password": "ต้องการรหัสผ่าน", "reset": "รีเซ็ต", - "reset_password": "", - "reset_people_visibility": "", + "reset_password": "ตั้งค่ารหัสผ่านใหม่", + "reset_people_visibility": "ปรับการมองเห็นใหม่", "reset_settings_to_default": "", - "restore": "กู้คืน", - "restore_user": "", - "retry_upload": "", + "restore": "เรียกคืน", + "restore_user": "เรียกคืนผู้ใช้", + "retry_upload": "ลองอัพโหลดใหม่", "review_duplicates": "", "role": "บทบาท", "save": "บันทึก", - "saved_profile": "", - "saved_settings": "", + "saved_profile": "โพรไฟล์ที่บันทึกไว้", + "saved_settings": "การตั้งค่าที่บันทึกไว้", "say_something": "พูดอะไรสักอย่าง", - "scan_all_libraries": "", + "scan_all_libraries": "สแกนคลังภาพทั้งหมด", "scan_all_library_files": "", "scan_new_library_files": "", - "scan_settings": "", + "scan_settings": "ตั้งค่าการสแกน", "search": "ค้นหา", "search_albums": "", "search_by_context": "", @@ -764,7 +764,7 @@ "search_city": "", "search_country": "", "search_for_existing_person": "", - "search_people": "", + "search_people": "ค้นหาผู้คน", "search_places": "", "search_state": "", "search_timezone": "", @@ -776,11 +776,11 @@ "select_all": "", "select_avatar_color": "", "select_face": "", - "select_featured_photo": "", - "select_library_owner": "", + "select_featured_photo": "เลือกภาพเด่น", + "select_library_owner": "เลือกเจ้าของคลังภาพ", "select_new_face": "", "select_photos": "เลือกรูปภาพ", - "selected": "ถูกเลือก", + "selected": "เลือก", "send_message": "", "server": "เซิร์ฟเวอร์", "server_stats": "", @@ -791,76 +791,76 @@ "set_profile_picture": "", "set_slideshow_to_fullscreen": "", "settings": "ตั้งค่า", - "settings_saved": "", + "settings_saved": "บันทึกการตั้งค่าแล้ว", "share": "แชร์", "shared": "แชร์", - "shared_by": "", - "shared_by_you": "", + "shared_by": "แชร์โดย", + "shared_by_you": "แชร์โดยคุณ", "shared_links": "ลิงก์ที่แชร์", "sharing": "การแชร์", "sharing_sidebar_description": "", - "show_album_options": "", - "show_file_location": "", - "show_gallery": "", - "show_hidden_people": "", - "show_in_timeline": "", - "show_in_timeline_setting_description": "", - "show_keyboard_shortcuts": "", + "show_album_options": "แสดงตัวเลือกอัลบั้ม", + "show_file_location": "แสดงตําแหน่งของไฟล์", + "show_gallery": "แสดงคลังภาพ", + "show_hidden_people": "แสดงคนที่ซ่อนไว้", + "show_in_timeline": "แสดงในไทม์ไลน์", + "show_in_timeline_setting_description": "แสดงรูปภาพและวิดีโอของผู้ใช้นี้ในไทม์ไลน์ของคุณ", + "show_keyboard_shortcuts": "แสดงปุ่มลัดแป้นพิมพ์", "show_metadata": "แสดง metadata", - "show_or_hide_info": "", - "show_password": "", - "show_person_options": "", - "show_progress_bar": "", - "show_search_options": "", + "show_or_hide_info": "แสดงหรือซ่อนข้อมูล", + "show_password": "แสดงรหัสผ่าน", + "show_person_options": "แสดงตัวเลือกของตัวบุคคล", + "show_progress_bar": "แสดงความคืบหน้า แถบ", + "show_search_options": "แสดงตัวเลือกการค้นหา", "shuffle": "สับเปลี่ยน", - "sign_up": "", + "sign_up": "ลงทะเบียน", "size": "ขนาด", - "skip_to_content": "", + "skip_to_content": "ข้ามไปยังเนื้อหา", "slideshow": "สไลด์", - "slideshow_settings": "", - "sort_albums_by": "", + "slideshow_settings": "ตั้งค่าสไลด์", + "sort_albums_by": "เรียงอัลบั้มโดย...", "stack": "ซ้อน", "stack_selected_photos": "", "stacktrace": "", - "start_date": "", + "start_date": "วันที่เริ่ม", "state": "รัฐ", "status": "สถานะ", - "stop_motion_photo": "", + "stop_motion_photo": "ภาพวัตถุเคลื่อนไหว", "stop_photo_sharing": "หยุดแชร์รูปภาพ?", - "storage": "พื้นที่จัดเก็บ", - "storage_label": "", + "storage": "ที่จัดเก็บ", + "storage_label": "ฉลากจัดเก็บ", "submit": "ส่ง", "suggestions": "ข้อเสนอแนะ", - "sunrise_on_the_beach": "", - "swap_merge_direction": "", + "sunrise_on_the_beach": "พระอาทิตย์ขึ้นบนชายหาด", + "swap_merge_direction": "สลับด้านรวม", "sync": "ซิงค์", "template": "แม่แบบ", "theme": "ธีม", - "theme_selection": "", - "theme_selection_description": "", - "time_based_memories": "", + "theme_selection": "การเลือกธีม", + "theme_selection_description": "ตั้งค่าธีมให้สว่างหรือมืดโดยอัตโนมัติ อิงจากค่าของเบราว์เซอร์ของคุณ", + "time_based_memories": "ความทรงจําตามเวลา", "timezone": "เขตเวลา", - "toggle_settings": "", - "toggle_theme": "", + "toggle_settings": "สลับการตั้งค่า", + "toggle_theme": "สลับธีม", "toggle_visibility": "", - "total_usage": "", + "total_usage": "การใช้งานรวม", "trash": "ขยะ", - "trash_all": "", - "trash_no_results_message": "", + "trash_all": "ทิ้งทั้งหมด", + "trash_no_results_message": "รูปและวีดีโอที่ถูกทิ้งจะมาโผล่ที่นี่", "type": "ประเภท", "unarchive": "นำออกจากที่เก็บถาวร", "unarchived": "", "unfavorite": "นำออกจากรายการโปรด", - "unhide_person": "", + "unhide_person": "ยกเลิกซ่อนบุคคล", "unknown": "ไม่ทราบ", "unknown_album": "", - "unknown_year": "", + "unknown_year": "ไม่ทราบปี", "unlink_oauth": "", "unlinked_oauth_account": "", - "unselect_all": "", + "unselect_all": "ยกเลิกการเลือกทั้งหมด", "unstack": "หยุดซ้อน", - "up_next": "", - "updated_password": "", + "up_next": "ต่อไป", + "updated_password": "รหัสผ่านเปลี่ยนแล้ว", "upload": "อัพโหลด", "upload_concurrency": "อัพโหลดพร้อมกัน", "url": "URL", @@ -873,7 +873,7 @@ "utilities": "", "validate": "ตรวจสอบ", "variables": "ตัวแปร", - "version": "เวอร์ชัน", + "version": "รุ่น", "video": "วิดีโอ", "video_hover_setting": "เล่นวิดีโอตัวอย่างเมื่อจ่อ", "video_hover_setting_description": "เล่นวิดีโอตัวอย่างเมื่อเมาส์จ่อข้างบน เมื่อปิดใช้งาน วิดีโอตัวอย่างยังสามารถเล่นได้โดยกดปุ่มเล่น", @@ -890,6 +890,6 @@ "welcome_to_immich": "ยินดีต้อนรับสู่ immich", "year": "ปี", "yes": "ใช่", - "you_dont_have_any_shared_links": "คุณไม่มีลิงก์ที่ใช้ร่วมกัน", + "you_dont_have_any_shared_links": "คุณไม่ได้มีลิงก์ที่แชร์", "zoom_image": "ซูมรูปภาพ" } diff --git a/i18n/tr.json b/i18n/tr.json index b2f0559ce89a3..93f7beddedb86 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -34,6 +34,11 @@ "authentication_settings_disable_all": "Tüm giriş yöntemlerini devre dışı bırakmak istediğinize emin misiniz? Giriş yapma fonksiyonu tamamen devre dışı bırakılacak.", "authentication_settings_reenable": "Yeniden aktif etmek için Sunucu Komutu'nu kullanın.", "background_task_job": "Arka Plan Görevleri", + "backup_database": "Yedek Veritabanı", + "backup_database_enable_description": "Veritabanı Yedeklerinş Etkinleştir", + "backup_keep_last_amount": "Önceki yedeklemeden saklanacak miktar", + "backup_settings": "Yedekleme Ayarları", + "backup_settings_description": "Veritabanı Yedekleme Ayarlarını Yönet", "check_all": "Hepsini Kontrol Et", "cleared_jobs": "{job} için işler temizlendi", "config_set_by_file": "Ayarlar şuanda config dosyası tarafından ayarlanmıştır", @@ -43,6 +48,9 @@ "confirm_reprocess_all_faces": "Tüm yüzleri tekrardan işlemek istediğinize emin misiniz? Bu işlem isimlendirilmiş insanları da silecek.", "confirm_user_password_reset": "{user} adlı kullanıcının şifresini sıfırlamak istediğinize emin misiniz?", "create_job": "Görev oluştur", + "cron_expression": "Cron İfadesi", + "cron_expression_description": "Cron formatını kullanarak tarama aralığını belirle. Daha fazla bilgi için örneğin Crontab Guru’ya bakın", + "cron_expression_presets": "Cron İfadesi Önayarları", "crontab_guru": "", "disable_login": "Girişi devre dışı bırak", "duplicate_detection_job_description": "Benzer fotoğrafları bulmak için makine öğrenmesini çalıştır. Bu işlem Akıllı Arama'ya bağlıdır", @@ -607,26 +615,42 @@ "cant_change_activity": "Etkinliği {etkinleştiremiyor, seçemiyor, doğru {devre dışı bırakamıyor} diğer durumda {etkinleştiremiyor}}", "cant_change_asset_favorite": "Varlığın favori durumunu değiştiremiyor", "cant_change_metadata_assets_count": "{count} varlığın metadatası (meta verisi) değiştirilemiyor", + "cant_get_faces": "Yüzler alınamadı", + "cant_get_number_of_comments": "Yorumların sayısı alınamadı", "cant_search_people": "Kişiler aranamıyor", "cant_search_places": "Mekanlar aranamıyor", "cleared_jobs": "İşler temizlendi: {job}", - "exclusion_pattern_already_exists": "", - "failed_job_command": "", + "error_adding_assets_to_album": "Albüme varlık ekleme hatası", + "error_adding_users_to_album": "Albüme kullanıcı ekleme hatası", + "error_deleting_shared_user": "Paylaşılan kullanıcı silme hatası", + "error_downloading": "{filename} indirme hatası", + "error_hiding_buy_button": "Satın alma butonu gizleme hatası", + "error_removing_assets_from_album": "Varlığı albümden silme hatası, daha fazla detay için konsolu kontrol et", + "error_selecting_all_assets": "Bütün varlıkları seçme hatası", + "exclusion_pattern_already_exists": "Bu dışlama modeli halihazırda mevcut.", + "failed_job_command": "{command} komutu iş: {job} için tamamlanamadı", "failed_to_create_album": "Albüm oluşturulamadı", "failed_to_create_shared_link": "Paylaşılan bağlantı oluşturulamadı", "failed_to_edit_shared_link": "Paylaşılan bağlantı düzenlenemedi", + "failed_to_get_people": "Kişiler alınamadı", + "failed_to_load_asset": "Varlık yüklenemedi", + "failed_to_load_assets": "Varlıklar yüklenemedi", + "failed_to_load_people": "Kişiler yüklenemedi", "failed_to_remove_product_key": "Ürün anahtarı kaldırılamadı", - "import_path_already_exists": "", + "failed_to_stack_assets": "Varlıklar yığınlanamadı", + "failed_to_unstack_assets": "Varıkların yığını kaldırılamadı", + "import_path_already_exists": "Bu içe aktarma yolu halihazırda mevcut.", "incorrect_email_or_password": "Yanlış e-posta veya şifre", - "paths_validation_failed": "", + "paths_validation_failed": "{paths, plural, one {# Yol} other {# Yollar}} doğrulanamadı", "profile_picture_transparent_pixels": "Profil resimleri şeffaf piksele sahip olamaz. Lütfen resme yakınlaştırın ve/veya resmi hareket ettirin.", - "quota_higher_than_disk_size": "", - "repair_unable_to_check_items": "", + "quota_higher_than_disk_size": "Disk boyutundan daha yüksek bir kota belirlediniz", + "repair_unable_to_check_items": "{count, select, one {Öğe} other {Öğeler}} kontrol edilemedi", "unable_to_add_album_users": "Kullanıcılar albüme eklenemiyor", + "unable_to_add_assets_to_shared_link": "Varlıklar paylaşılan bağlantıya eklenemiyor", "unable_to_add_comment": "Yorum eklenemiyor", - "unable_to_add_exclusion_pattern": "", - "unable_to_add_import_path": "", - "unable_to_add_partners": "", + "unable_to_add_exclusion_pattern": "Hariç tutma modeli eklenemiyor", + "unable_to_add_import_path": "İçe aktarma yolu eklenemiyor", + "unable_to_add_partners": "Ortaklar eklenemiyor", "unable_to_change_album_user_role": "", "unable_to_change_date": "Tarih değiştirilemiyor", "unable_to_change_location": "", diff --git a/i18n/uk.json b/i18n/uk.json index 8e3d05ceb613e..65d2850877033 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -34,6 +34,11 @@ "authentication_settings_disable_all": "Ви впевнені, що хочете вимкнути всі методи входу? Вхід буде повністю вимкнений.", "authentication_settings_reenable": "Для повторного ввімкнення використовуйте Команду сервера.", "background_task_job": "Фонові Завдання", + "backup_database": "Резервна копія бази даних", + "backup_database_enable_description": "Увімкнути резервне копіювання бази даних", + "backup_keep_last_amount": "Кількість резервних копій для зберігання", + "backup_settings": "Налаштування резервного копіювання", + "backup_settings_description": "Керування налаштуваннями резервного копіювання бази даних", "check_all": "Перевірити все", "cleared_jobs": "Очищені завдання для: {job}", "config_set_by_file": "Налаштовано за допомогою конфіг-файлу", @@ -43,6 +48,9 @@ "confirm_reprocess_all_faces": "Ви впевнені, що хочете повторно визначити всі обличчя? Це також призведе до видалення імен з усіх облич.", "confirm_user_password_reset": "Ви впевнені, що хочете скинути пароль користувача {user}?", "create_job": "Створити завдання", + "cron_expression": "Cron вираз", + "cron_expression_description": "Встановіть інтервал сканування, використовуючи формат cron. Для отримання додаткової інформації зверніться до напр. Crontab Guru", + "cron_expression_presets": "Попередні налаштування cron виразів", "crontab_guru": "", "disable_login": "Вимкнути вхід", "disabled": "", @@ -1389,7 +1397,7 @@ "warning": "Попередження", "week": "Тиждень", "welcome": "Ласкаво просимо", - "welcome_to_immich": "Ласкаво просимо до immich", + "welcome_to_immich": "Ласкаво просимо до Immich", "year": "Рік", "years_ago": "{years, plural, one {# рік} few {# роки} many {# років} other {# років}} тому", "yes": "Так", diff --git a/i18n/zh_Hant.json b/i18n/zh_Hant.json index 8b1e1e5e15aeb..0adf6dc2c609a 100644 --- a/i18n/zh_Hant.json +++ b/i18n/zh_Hant.json @@ -1,7 +1,7 @@ { "about": "關於", - "account": "賬號", - "account_settings": "賬號設定", + "account": "帳號", + "account_settings": "帳號設定", "acknowledge": "收到", "action": "操作", "actions": "操作", @@ -34,6 +34,11 @@ "authentication_settings_disable_all": "確定要停用所有登入方式嗎?這樣會完全無法登入。", "authentication_settings_reenable": "如需重新啟用,請使用 伺服器指令。", "background_task_job": "背景任務", + "backup_database": "備份資料庫", + "backup_database_enable_description": "啟用資料庫備份", + "backup_keep_last_amount": "保留先前備份的數量", + "backup_settings": "備份設定", + "backup_settings_description": "管理資料庫備份設定", "check_all": "全選", "cleared_jobs": "已為「{job}」清除作業", "config_set_by_file": "目前的設定已透過配置文檔調整", @@ -43,6 +48,9 @@ "confirm_reprocess_all_faces": "確定要重新處理所有臉孔嗎?這會清除已命名的人物。", "confirm_user_password_reset": "您確定要重設 {user} 的密碼嗎?", "create_job": "建立作業", + "cron_expression": "Cron 運算式", + "cron_expression_description": "以 Cron 格式設定掃描時段。詳細資訊請參閱 Crontab Guru", + "cron_expression_presets": "現成的 Cron 運算式", "crontab_guru": "", "disable_login": "停用登入", "disabled": "已禁用", diff --git a/i18n/zh_SIMPLIFIED.json b/i18n/zh_SIMPLIFIED.json index c2e1a873500e7..bb062d9792e08 100644 --- a/i18n/zh_SIMPLIFIED.json +++ b/i18n/zh_SIMPLIFIED.json @@ -28,12 +28,17 @@ "added_to_favorites_count": "添加{count, number}项到收藏", "admin": { "add_exclusion_pattern_description": "添加排除规则。支持使用 *、** 和 ? 通配符。比如要忽略任何名为 “Raw” 的文件夹中的所有文件,请使用 “**/Raw/**”;要忽略所有以 “.tif” 结尾的文件,请使用 “**/*.tif”;要忽略绝对路径,请使用 “/path/to/ignore/**”。", - "asset_offline_description": "此外部库项目已无法从磁盘中找到,并已移至回收站。如果文件已在库中移动,请检查时间线中是否有新的对应项目。要恢复此项目,请确保 Immich 可以访问以下文件路径并执行扫描库任务。", + "asset_offline_description": "磁盘上已找不到此外部库项目,已将其移至回收站。如果文件已在库中移动,请检查时间线中是否有对应项目。要恢复此项目,请确保 Immich 可以访问以下文件路径并执行“扫描库”任务。", "authentication_settings": "认证设置", "authentication_settings_description": "管理密码、OAuth 和其它认证设置", "authentication_settings_disable_all": "确定要禁用所有的登录方式?该操作将完全禁止登录。", "authentication_settings_reenable": "如需再次启用,使用 服务器指令。", "background_task_job": "后台任务", + "backup_database": "备份数据库", + "backup_database_enable_description": "启用数据库备份", + "backup_keep_last_amount": "要保留的先前备份数量", + "backup_settings": "备份设置", + "backup_settings_description": "管理数据库备份设置", "check_all": "检查全部", "cleared_jobs": "已清理任务:{job}", "config_set_by_file": "当前配置已通过配置文件设置", @@ -41,8 +46,11 @@ "confirm_delete_library_assets": "确定要删除该图库吗?这将删除所有包含在Immich中的{count, plural, one {#个项目} other {#个项目}},且无法撤销。但文件仍将保留在磁盘中。", "confirm_email_below": "输入“{email}”来确认", "confirm_reprocess_all_faces": "确定要对全部照片重新进行面部识别吗?这将同时清除所有已命名人物。", - "confirm_user_password_reset": "确定要重置用户{user}的密码吗?", + "confirm_user_password_reset": "确定要重置用户“{user}”的密码吗?", "create_job": "创建任务", + "cron_expression": "Cron表达式", + "cron_expression_description": "使用 cron 格式设置扫描间隔。更多详细信息请参阅 Crontab Guru", + "cron_expression_presets": "Cron 表达式预设", "crontab_guru": "Crontab Guru", "disable_login": "禁用登录", "disabled": "已禁用", @@ -51,10 +59,10 @@ "external_library_created_at": "外部图库(创建于{date})", "external_library_management": "外部图库管理", "face_detection": "人脸检测", - "face_detection_description": "使用机器学习检测项目中的人脸(视频只检测其缩略图中的人脸)。选择“刷新”项将会(重新)处理所有项目。选择“重置”还会清除所有当前面部数据。选择“缺失”项将尚未处理的项目进行排队处理。人脸检测完成后,检测到的人脸将排队进行面部识别,将它们分组到现有的或新的人物中。", - "facial_recognition_job_description": "将检测到的人脸按照人物分组。这一步将在人脸检测完成后执行。选择“重置”项将会(重新)分组所有面孔。选择“缺失”项将尚未分配的人脸置于队列中。", + "face_detection_description": "使用机器学习检测项目中的人脸(视频只检测其缩略图中的人脸)。选择“刷新”将会(重新)处理所有项目。选择“重置”还会清除所有当前面部数据。选择“缺失”将尚未处理的项目进行排队处理。人脸检测完成后,检测到的人脸将排队进行面部识别,将它们分组到现有的或新的人物中。", + "facial_recognition_job_description": "将检测到的人脸按照人物分组。这一步将在人脸检测完成后执行。选择“重置”将会(重新)分组所有面孔。选择“缺失”将尚未分配的人脸置于队列中。", "failed_job_command": "{command}命令执行失败的任务:{job}", - "force_delete_user_warning": "警告:这将立即移除用户以及所有项目。该操作无法撤销且文件无法恢复。", + "force_delete_user_warning": "警告:这将立即移除用户以及其所有项目。该操作无法撤销且文件无法恢复。", "forcing_refresh_library_files": "强制刷新所有图库文件", "image_format": "格式", "image_format_description": "WebP 文件比 JPEG 文件小,但编码速度较慢。", @@ -164,7 +172,7 @@ "note_cannot_be_changed_later": "注意:此项一旦设定,以后无法更改!", "note_unlimited_quota": "提示:输入0表示无限制", "notification_email_from_address": "发件人地址", - "notification_email_from_address_description": "发件人邮箱地址,例如:“张三 <12345@qq.com>”", + "notification_email_from_address_description": "发件人邮箱,例如:“张三 <12345@qq.com>”", "notification_email_host_description": "服务器地址(例如:smtp.qq.com)", "notification_email_ignore_certificate_errors": "忽略证书错误", "notification_email_ignore_certificate_errors_description": "忽略TLS证书验证错误(不建议)", @@ -196,7 +204,7 @@ "oauth_scope": "范围", "oauth_settings": "OAuth", "oauth_settings_description": "管理OAuth登录设置", - "oauth_settings_more_details": "关于本功能的更多详细信息,请查看相关文档。", + "oauth_settings_more_details": "关于此功能的更多详细信息,请查看相关文档。", "oauth_signing_algorithm": "签名算法", "oauth_storage_label_claim": "存储标签声明", "oauth_storage_label_claim_description": "自动将用户的存储标签设置为此项的值。", @@ -265,7 +273,7 @@ "transcoding_acceleration_api": "加速器API", "transcoding_acceleration_api_description": "这个API将与您的设备交互,以加速转码过程。此设置为“尽力而为”——如果转码失败,将回到软件转码。VP9是否工作取决于您的硬件配置。", "transcoding_acceleration_nvenc": "NVENC(需要 NVIDIA GPU)", - "transcoding_acceleration_qsv": "快速同步(需要Intel 7代及以上的 CPU)", + "transcoding_acceleration_qsv": "Quick Sync(需要Intel 7代及以上的 CPU)", "transcoding_acceleration_rkmpp": "RKMPP(仅适用于 Rockchip SOCs)", "transcoding_acceleration_vaapi": "VAAPI", "transcoding_accepted_audio_codecs": "支持的音频编解码器", @@ -326,8 +334,8 @@ "trash_number_of_days_description": "回收站中项目永久删除的天数", "trash_settings": "回收站设置", "trash_settings_description": "管理回收站设置", - "untracked_files": "未被追踪的文件", - "untracked_files_description": "这些文件未被系统追踪。 这可能是移动失败、上传中断或因bug而落下", + "untracked_files": "未被扫描的文件", + "untracked_files_description": "这些文件未被系统扫描。 这可能是移动失败、上传中断或因bug而落下", "user_cleanup_job": "清理用户", "user_delete_delay": "{user}的账户及项目将在{delay, plural, one {#天} other {#天}}后自动永久删除。", "user_delete_delay_settings": "延期删除", @@ -341,7 +349,7 @@ "user_restore_scheduled_removal": "恢复用户 - 计划于{date, date, long}删除", "user_settings": "用户设置", "user_settings_description": "管理用户设置", - "user_successfully_removed": "用户{email}已被成功删除。", + "user_successfully_removed": "用户“{email}”已被成功删除。", "version_check_enabled_description": "启用版本检测", "version_check_implications": "版本检查功能依赖于与 github.com 的定期通信", "version_check_settings": "版本检查", @@ -363,16 +371,16 @@ "album_delete_confirmation_description": "如果该相册是共享的,其他用户将无法再访问它。", "album_info_updated": "相册信息已更新", "album_leave": "退出相册?", - "album_leave_confirmation": "确定要退出相册{album}吗?", + "album_leave_confirmation": "确定要退出相册“{album}”吗?", "album_name": "相册名称", "album_options": "相册设置", "album_remove_user": "移除用户?", - "album_remove_user_confirmation": "你确定要移除{user}吗?", + "album_remove_user_confirmation": "你确定要移除“{user}”吗?", "album_share_no_users": "看起来您已与所有用户共享了此相册,或者您根本没有任何用户可共享。", "album_updated": "相册已更新", "album_updated_setting_description": "当共享相册有新项目时接收邮件通知", - "album_user_left": "离开{album}", - "album_user_removed": "已移除{user}", + "album_user_left": "离开“{album}”", + "album_user_removed": "已移除“{user}”", "album_with_link_access": "拥有此链接的任何人均可查看本相册中的照片和人物。", "albums": "相册", "albums_count": "{count, plural, one {{count, number} 个相册} other {{count, number} 个相册}}", @@ -386,7 +394,7 @@ "allow_public_user_to_upload": "允许所有用户上传", "anti_clockwise": "逆时针", "api_key": "API 密钥", - "api_key_description": "该应用密钥只会展示一次。请确保在关闭窗口前复制下来。", + "api_key_description": "该应用密钥只会显示一次。请确保在关闭窗口前复制下来。", "api_key_empty": "API Key的名称不可以为空", "api_keys": "API 密钥", "app_settings": "应用设置", @@ -402,7 +410,7 @@ "asset_added_to_album": "已添加至相册", "asset_adding_to_album": "正在添加至相册...", "asset_description_updated": "项目描述已更新", - "asset_filename_is_offline": "项目{filename}已离线", + "asset_filename_is_offline": "项目“{filename}”已离线", "asset_has_unassigned_faces": "项目中有未分配的人脸", "asset_hashing": "哈希校验中...", "asset_offline": "项目离线", @@ -558,7 +566,7 @@ "download_settings": "下载", "download_settings_description": "管理项目下载相关设置", "downloading": "下载中", - "downloading_asset_filename": "下载项目{filename}", + "downloading_asset_filename": "下载项目“{filename}”", "drop_files_to_upload": "拖放文件以上传", "duplicates": "重复项", "duplicates_description": "审查每组疑似重复项并标记哪些是重复的(如果有的话)", @@ -603,7 +611,7 @@ "end_date": "结束日期", "error": "错误", "error_loading_image": "加载图片时出错", - "error_title": "错误 - 出了点问题", + "error_title": "错误 - 好像出了问题", "errors": { "cannot_navigate_next_asset": "无法导航到下一个项目", "cannot_navigate_previous_asset": "无法导航到上一个项目", @@ -761,7 +769,7 @@ "features": "功能", "features_setting_description": "管理App功能", "file_name": "文件名", - "file_name_or_extension": "文件名或扩展名", + "file_name_or_extension": "文件名", "filename": "文件名", "files": "", "filetype": "文件类型", @@ -821,7 +829,7 @@ "interval": { "day_at_onepm": "每天下午1点", "hours": "每 {hours, plural, one {小时} other {{hours, number} 小时}}", - "night_at_midnight": "每晚24点", + "night_at_midnight": "每晚0点", "night_at_twoam": "每晚凌晨2点" }, "invite_people": "邀请人员", @@ -1155,7 +1163,7 @@ "search": "搜索", "search_albums": "搜索相册", "search_by_context": "搜索内容", - "search_by_filename": "通过文件名或扩展名搜索", + "search_by_filename": "通过文件名搜索", "search_by_filename_example": "如 IMG_1234.JPG 或 PNG", "search_camera_make": "搜索相机品牌...", "search_camera_model": "搜索相机型号...", diff --git a/machine-learning/poetry.lock b/machine-learning/poetry.lock index fa5cb2d9c211a..de4d03c4f40b1 100644 --- a/machine-learning/poetry.lock +++ b/machine-learning/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "aiocache" @@ -747,19 +747,19 @@ files = [ test = ["pytest (>=6)"] [[package]] -name = "fastapi-slim" -version = "0.115.2" +name = "fastapi" +version = "0.115.4" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi_slim-0.115.2-py3-none-any.whl", hash = "sha256:7dd65a58e9b2a1558ebd479d35e621417d4572188d902a3e5564739914ddc81d"}, - {file = "fastapi_slim-0.115.2.tar.gz", hash = "sha256:83a1ce1e385029cad305b49dcc03622d2be6a905b2ab65d2e7096285605cca8d"}, + {file = "fastapi-0.115.4-py3-none-any.whl", hash = "sha256:0b504a063ffb3cf96a5e27dc1bc32c80ca743a2528574f9cdc77daa2d31b4742"}, + {file = "fastapi-0.115.4.tar.gz", hash = "sha256:db653475586b091cb8b2fec2ac54a680ac6a158e07406e1abae31679e8826349"}, ] [package.dependencies] pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.37.2,<0.41.0" +starlette = ">=0.40.0,<0.42.0" typing-extensions = ">=4.8.0" [package.extras] @@ -946,13 +946,13 @@ tqdm = ["tqdm"] [[package]] name = "ftfy" -version = "6.3.0" +version = "6.3.1" description = "Fixes mojibake and other problems with Unicode, after the fact" optional = false python-versions = ">=3.9" files = [ - {file = "ftfy-6.3.0-py3-none-any.whl", hash = "sha256:17aca296801f44142e3ff2c16f93fbf6a87609ebb3704a9a41dd5d4903396caf"}, - {file = "ftfy-6.3.0.tar.gz", hash = "sha256:1c7d6418e72b25a7760feb150acf574b86924dbb2e95b32c0b3abbd1ba3d7ad6"}, + {file = "ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083"}, + {file = "ftfy-6.3.1.tar.gz", hash = "sha256:9b3c3d90f84fb267fe64d375a07b7f8912d817cf86009ae134aa03e1819506ec"}, ] [package.dependencies] @@ -1315,13 +1315,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "huggingface-hub" -version = "0.25.2" +version = "0.26.2" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" files = [ - {file = "huggingface_hub-0.25.2-py3-none-any.whl", hash = "sha256:1897caf88ce7f97fe0110603d8f66ac264e3ba6accdf30cd66cc0fed5282ad25"}, - {file = "huggingface_hub-0.25.2.tar.gz", hash = "sha256:a1014ea111a5f40ccd23f7f7ba8ac46e20fa3b658ced1f86a00c75c06ec6423c"}, + {file = "huggingface_hub-0.26.2-py3-none-any.whl", hash = "sha256:98c2a5a8e786c7b2cb6fdeb2740893cba4d53e312572ed3d8afafda65b128c46"}, + {file = "huggingface_hub-0.26.2.tar.gz", hash = "sha256:b100d853465d965733964d123939ba287da60a547087783ddff8a323f340332b"}, ] [package.dependencies] @@ -1334,16 +1334,16 @@ tqdm = ">=4.42.1" typing-extensions = ">=3.7.4.3" [package.extras] -all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.5.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.5.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] cli = ["InquirerPy (==0.3.4)"] -dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.5.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.5.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] hf-transfer = ["hf-transfer (>=0.1.4)"] -inference = ["aiohttp", "minijinja (>=1.0)"] -quality = ["mypy (==1.5.1)", "ruff (>=0.5.0)"] +inference = ["aiohttp"] +quality = ["libcst (==1.4.0)", "mypy (==1.5.1)", "ruff (>=0.5.0)"] tensorflow = ["graphviz", "pydot", "tensorflow"] tensorflow-testing = ["keras (<3.0)", "tensorflow"] -testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] +testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] torch = ["safetensors[torch]", "torch"] typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] @@ -1609,13 +1609,13 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"] [[package]] name = "locust" -version = "2.32.0" +version = "2.32.1" description = "Developer-friendly load testing framework" optional = false python-versions = ">=3.9" files = [ - {file = "locust-2.32.0-py3-none-any.whl", hash = "sha256:e004514332b8631ca91382d11d224baee4ced040c5f5c8b2233800ebcbc73c0e"}, - {file = "locust-2.32.0.tar.gz", hash = "sha256:d8f7f5d9d4e801b2e7b0ee3f31109333673da744ccedf85e7da0151f2d263dd9"}, + {file = "locust-2.32.1-py3-none-any.whl", hash = "sha256:3fb5548b4f2b6477fa5229ee55ac3dddbae56e86c3430bf2ba3fee358eb7e7bb"}, + {file = "locust-2.32.1.tar.gz", hash = "sha256:8c3b1094dbf20860fd2f6e26b68f0c6064dc28054f4462664389d102fce1448b"}, ] [package.dependencies] @@ -1876,43 +1876,43 @@ files = [ [[package]] name = "mypy" -version = "1.12.1" +version = "1.13.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3d7d4371829184e22fda4015278fbfdef0327a4b955a483012bd2d423a788801"}, - {file = "mypy-1.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f59f1dfbf497d473201356966e353ef09d4daec48caeacc0254db8ef633a28a5"}, - {file = "mypy-1.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b947097fae68004b8328c55161ac9db7d3566abfef72d9d41b47a021c2fba6b1"}, - {file = "mypy-1.12.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96af62050971c5241afb4701c15189ea9507db89ad07794a4ee7b4e092dc0627"}, - {file = "mypy-1.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:d90da248f4c2dba6c44ddcfea94bb361e491962f05f41990ff24dbd09969ce20"}, - {file = "mypy-1.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1230048fec1380faf240be6385e709c8570604d2d27ec6ca7e573e3bc09c3735"}, - {file = "mypy-1.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:02dcfe270c6ea13338210908f8cadc8d31af0f04cee8ca996438fe6a97b4ec66"}, - {file = "mypy-1.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a437c9102a6a252d9e3a63edc191a3aed5f2fcb786d614722ee3f4472e33f6"}, - {file = "mypy-1.12.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:186e0c8346efc027ee1f9acf5ca734425fc4f7dc2b60144f0fbe27cc19dc7931"}, - {file = "mypy-1.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:673ba1140a478b50e6d265c03391702fa11a5c5aff3f54d69a62a48da32cb811"}, - {file = "mypy-1.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9fb83a7be97c498176fb7486cafbb81decccaef1ac339d837c377b0ce3743a7f"}, - {file = "mypy-1.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:389e307e333879c571029d5b93932cf838b811d3f5395ed1ad05086b52148fb0"}, - {file = "mypy-1.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94b2048a95a21f7a9ebc9fbd075a4fcd310410d078aa0228dbbad7f71335e042"}, - {file = "mypy-1.12.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ee5932370ccf7ebf83f79d1c157a5929d7ea36313027b0d70a488493dc1b179"}, - {file = "mypy-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:19bf51f87a295e7ab2894f1d8167622b063492d754e69c3c2fed6563268cb42a"}, - {file = "mypy-1.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d34167d43613ffb1d6c6cdc0cc043bb106cac0aa5d6a4171f77ab92a3c758bcc"}, - {file = "mypy-1.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:427878aa54f2e2c5d8db31fa9010c599ed9f994b3b49e64ae9cd9990c40bd635"}, - {file = "mypy-1.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5fcde63ea2c9f69d6be859a1e6dd35955e87fa81de95bc240143cf00de1f7f81"}, - {file = "mypy-1.12.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d54d840f6c052929f4a3d2aab2066af0f45a020b085fe0e40d4583db52aab4e4"}, - {file = "mypy-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:20db6eb1ca3d1de8ece00033b12f793f1ea9da767334b7e8c626a4872090cf02"}, - {file = "mypy-1.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b16fe09f9c741d85a2e3b14a5257a27a4f4886c171d562bc5a5e90d8591906b8"}, - {file = "mypy-1.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0dcc1e843d58f444fce19da4cce5bd35c282d4bde232acdeca8279523087088a"}, - {file = "mypy-1.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e10ba7de5c616e44ad21005fa13450cd0de7caaa303a626147d45307492e4f2d"}, - {file = "mypy-1.12.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e6fe449223fa59fbee351db32283838a8fee8059e0028e9e6494a03802b4004"}, - {file = "mypy-1.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:dc6e2a2195a290a7fd5bac3e60b586d77fc88e986eba7feced8b778c373f9afe"}, - {file = "mypy-1.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:de5b2a8988b4e1269a98beaf0e7cc71b510d050dce80c343b53b4955fff45f19"}, - {file = "mypy-1.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843826966f1d65925e8b50d2b483065c51fc16dc5d72647e0236aae51dc8d77e"}, - {file = "mypy-1.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fe20f89da41a95e14c34b1ddb09c80262edcc295ad891f22cc4b60013e8f78d"}, - {file = "mypy-1.12.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8135ffec02121a75f75dc97c81af7c14aa4ae0dda277132cfcd6abcd21551bfd"}, - {file = "mypy-1.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:a7b76fa83260824300cc4834a3ab93180db19876bce59af921467fd03e692810"}, - {file = "mypy-1.12.1-py3-none-any.whl", hash = "sha256:ce561a09e3bb9863ab77edf29ae3a50e65685ad74bba1431278185b7e5d5486e"}, - {file = "mypy-1.12.1.tar.gz", hash = "sha256:f5b3936f7a6d0e8280c9bdef94c7ce4847f5cdfc258fbb2c29a8c1711e8bb96d"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, ] [package.dependencies] @@ -1922,6 +1922,7 @@ typing-extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] reports = ["lxml"] @@ -2091,27 +2092,27 @@ sympy = "*" [[package]] name = "onnxruntime-gpu" -version = "1.18.1" +version = "1.19.2" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" optional = false python-versions = "*" files = [ - {file = "onnxruntime_gpu-1.18.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e9a52f5d43a84fe29e135da6bf10daa18836c81bed9060a5924efd6afc0d259"}, - {file = "onnxruntime_gpu-1.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:e7c1c665e8a11a5cf15369948b04288dc0a6812ad2e6beaff93a3d157c864d9a"}, - {file = "onnxruntime_gpu-1.18.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1334f802cb1e4e2eb6ceebc4ef71ba44f3ef444d34216baafb940368a7a5d2f5"}, - {file = "onnxruntime_gpu-1.18.1-cp311-cp311-win_amd64.whl", hash = "sha256:0ffcc711e89b80c935d5172544f8a605b11525fc1e6f0e78ee79e2c28956e2d9"}, - {file = "onnxruntime_gpu-1.18.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbb1a6c986b2392eebaebc43e198a1614e3f7d2c191725002dbfa0dceb24454b"}, - {file = "onnxruntime_gpu-1.18.1-cp312-cp312-win_amd64.whl", hash = "sha256:bee352929e6eec2ff4e11e323a025ed8bd5eac24795005bc502ac740971fa7bd"}, - {file = "onnxruntime_gpu-1.18.1-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:76d307a849a863d0457869febe4b2fd2fc07c7f26385c7339d17066312fa6be0"}, - {file = "onnxruntime_gpu-1.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:b7498d6c64a03558308ce6d7d14dab306ea90d1204b563890c4d2d26c1b520f0"}, - {file = "onnxruntime_gpu-1.18.1-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a1d8113cb4b8a51b195fae91cfeb6849728462a4b46aaf51b6764c44e54f81f"}, - {file = "onnxruntime_gpu-1.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:fc1d2544a39f5db64c5b8a0c24d0b934d7d64682e6d70763eb2cc726b1fd6c3f"}, + {file = "onnxruntime_gpu-1.19.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a49740e079e7c5215830d30cde3df792e903df007aa0b0fd7aa797937061b27a"}, + {file = "onnxruntime_gpu-1.19.2-cp310-cp310-win_amd64.whl", hash = "sha256:b895920bb5e4241299f68874e0becdc2635ea0142939c11e7ff5ae5b28993613"}, + {file = "onnxruntime_gpu-1.19.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:562fc7c755393eaad9751e56149339dd201ffbfdb3ef5f43ff21d0619ba9045f"}, + {file = "onnxruntime_gpu-1.19.2-cp311-cp311-win_amd64.whl", hash = "sha256:522f7495918176cb8c1a3c78bde7152d984f7096acc786c73a27643af8af87c9"}, + {file = "onnxruntime_gpu-1.19.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:554a02a3fac0119707eb87327908afd21c4e6f0fa5bf9a034398f098adc316c5"}, + {file = "onnxruntime_gpu-1.19.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c6165a405027e3c0f11d189ae7013b5d66919b3381f9bfb3405c0c0cf07968"}, + {file = "onnxruntime_gpu-1.19.2-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4a8562e1e6f1912870c60bfaf8233c82b86e5b93ae39f211b650ac0f2015430"}, + {file = "onnxruntime_gpu-1.19.2-cp38-cp38-win_amd64.whl", hash = "sha256:55505c99e18688a7c68fdc811ed6e7a315aa36f543b33920c77d03a627d2c3f5"}, + {file = "onnxruntime_gpu-1.19.2-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9e369f01f55ea726ae5d28f18236426e52e97c433f0b7682054e61c478a06c9"}, + {file = "onnxruntime_gpu-1.19.2-cp39-cp39-win_amd64.whl", hash = "sha256:c8b8128174b0470537e9f4983aeecc002a435d13914970c2af2f41d244ef2781"}, ] [package.dependencies] coloredlogs = "*" flatbuffers = "*" -numpy = ">=1.21.6,<2.0" +numpy = ">=1.21.6" packaging = "*" protobuf = "*" sympy = "*" @@ -2169,68 +2170,69 @@ numpy = [ [[package]] name = "orjson" -version = "3.10.7" +version = "3.10.10" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.10.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:74f4544f5a6405b90da8ea724d15ac9c36da4d72a738c64685003337401f5c12"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34a566f22c28222b08875b18b0dfbf8a947e69df21a9ed5c51a6bf91cfb944ac"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf6ba8ebc8ef5792e2337fb0419f8009729335bb400ece005606336b7fd7bab7"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac7cf6222b29fbda9e3a472b41e6a5538b48f2c8f99261eecd60aafbdb60690c"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de817e2f5fc75a9e7dd350c4b0f54617b280e26d1631811a43e7e968fa71e3e9"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:348bdd16b32556cf8d7257b17cf2bdb7ab7976af4af41ebe79f9796c218f7e91"}, - {file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:479fd0844ddc3ca77e0fd99644c7fe2de8e8be1efcd57705b5c92e5186e8a250"}, - {file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fdf5197a21dd660cf19dfd2a3ce79574588f8f5e2dbf21bda9ee2d2b46924d84"}, - {file = "orjson-3.10.7-cp310-none-win32.whl", hash = "sha256:d374d36726746c81a49f3ff8daa2898dccab6596864ebe43d50733275c629175"}, - {file = "orjson-3.10.7-cp310-none-win_amd64.whl", hash = "sha256:cb61938aec8b0ffb6eef484d480188a1777e67b05d58e41b435c74b9d84e0b9c"}, - {file = "orjson-3.10.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7db8539039698ddfb9a524b4dd19508256107568cdad24f3682d5773e60504a2"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:480f455222cb7a1dea35c57a67578848537d2602b46c464472c995297117fa09"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8a9c9b168b3a19e37fe2778c0003359f07822c90fdff8f98d9d2a91b3144d8e0"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8de062de550f63185e4c1c54151bdddfc5625e37daf0aa1e75d2a1293e3b7d9a"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b0dd04483499d1de9c8f6203f8975caf17a6000b9c0c54630cef02e44ee624e"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b58d3795dafa334fc8fd46f7c5dc013e6ad06fd5b9a4cc98cb1456e7d3558bd6"}, - {file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33cfb96c24034a878d83d1a9415799a73dc77480e6c40417e5dda0710d559ee6"}, - {file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e724cebe1fadc2b23c6f7415bad5ee6239e00a69f30ee423f319c6af70e2a5c0"}, - {file = "orjson-3.10.7-cp311-none-win32.whl", hash = "sha256:82763b46053727a7168d29c772ed5c870fdae2f61aa8a25994c7984a19b1021f"}, - {file = "orjson-3.10.7-cp311-none-win_amd64.whl", hash = "sha256:eb8d384a24778abf29afb8e41d68fdd9a156cf6e5390c04cc07bbc24b89e98b5"}, - {file = "orjson-3.10.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44a96f2d4c3af51bfac6bc4ef7b182aa33f2f054fd7f34cc0ee9a320d051d41f"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ac14cd57df0572453543f8f2575e2d01ae9e790c21f57627803f5e79b0d3c3"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bdbb61dcc365dd9be94e8f7df91975edc9364d6a78c8f7adb69c1cdff318ec93"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b48b3db6bb6e0a08fa8c83b47bc169623f801e5cc4f24442ab2b6617da3b5313"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23820a1563a1d386414fef15c249040042b8e5d07b40ab3fe3efbfbbcbcb8864"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0c6a008e91d10a2564edbb6ee5069a9e66df3fbe11c9a005cb411f441fd2c09"}, - {file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d352ee8ac1926d6193f602cbe36b1643bbd1bbcb25e3c1a657a4390f3000c9a5"}, - {file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d2d9f990623f15c0ae7ac608103c33dfe1486d2ed974ac3f40b693bad1a22a7b"}, - {file = "orjson-3.10.7-cp312-none-win32.whl", hash = "sha256:7c4c17f8157bd520cdb7195f75ddbd31671997cbe10aee559c2d613592e7d7eb"}, - {file = "orjson-3.10.7-cp312-none-win_amd64.whl", hash = "sha256:1d9c0e733e02ada3ed6098a10a8ee0052dd55774de3d9110d29868d24b17faa1"}, - {file = "orjson-3.10.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:77d325ed866876c0fa6492598ec01fe30e803272a6e8b10e992288b009cbe149"}, - {file = "orjson-3.10.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ea2c232deedcb605e853ae1db2cc94f7390ac776743b699b50b071b02bea6fe"}, - {file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3dcfbede6737fdbef3ce9c37af3fb6142e8e1ebc10336daa05872bfb1d87839c"}, - {file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11748c135f281203f4ee695b7f80bb1358a82a63905f9f0b794769483ea854ad"}, - {file = "orjson-3.10.7-cp313-none-win32.whl", hash = "sha256:a7e19150d215c7a13f39eb787d84db274298d3f83d85463e61d277bbd7f401d2"}, - {file = "orjson-3.10.7-cp313-none-win_amd64.whl", hash = "sha256:eef44224729e9525d5261cc8d28d6b11cafc90e6bd0be2157bde69a52ec83024"}, - {file = "orjson-3.10.7-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6ea2b2258eff652c82652d5e0f02bd5e0463a6a52abb78e49ac288827aaa1469"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:430ee4d85841e1483d487e7b81401785a5dfd69db5de01314538f31f8fbf7ee1"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4b6146e439af4c2472c56f8540d799a67a81226e11992008cb47e1267a9b3225"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:084e537806b458911137f76097e53ce7bf5806dda33ddf6aaa66a028f8d43a23"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4829cf2195838e3f93b70fd3b4292156fc5e097aac3739859ac0dcc722b27ac0"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1193b2416cbad1a769f868b1749535d5da47626ac29445803dae7cc64b3f5c98"}, - {file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4e6c3da13e5a57e4b3dca2de059f243ebec705857522f188f0180ae88badd354"}, - {file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c31008598424dfbe52ce8c5b47e0752dca918a4fdc4a2a32004efd9fab41d866"}, - {file = "orjson-3.10.7-cp38-none-win32.whl", hash = "sha256:7122a99831f9e7fe977dc45784d3b2edc821c172d545e6420c375e5a935f5a1c"}, - {file = "orjson-3.10.7-cp38-none-win_amd64.whl", hash = "sha256:a763bc0e58504cc803739e7df040685816145a6f3c8a589787084b54ebc9f16e"}, - {file = "orjson-3.10.7-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e76be12658a6fa376fcd331b1ea4e58f5a06fd0220653450f0d415b8fd0fbe20"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed350d6978d28b92939bfeb1a0570c523f6170efc3f0a0ef1f1df287cd4f4960"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:144888c76f8520e39bfa121b31fd637e18d4cc2f115727865fdf9fa325b10412"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09b2d92fd95ad2402188cf51573acde57eb269eddabaa60f69ea0d733e789fe9"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b24a579123fa884f3a3caadaed7b75eb5715ee2b17ab5c66ac97d29b18fe57f"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591bcfe7512353bd609875ab38050efe3d55e18934e2f18950c108334b4ff"}, - {file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f4db56635b58cd1a200b0a23744ff44206ee6aa428185e2b6c4a65b3197abdcd"}, - {file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0fa5886854673222618638c6df7718ea7fe2f3f2384c452c9ccedc70b4a510a5"}, - {file = "orjson-3.10.7-cp39-none-win32.whl", hash = "sha256:8272527d08450ab16eb405f47e0f4ef0e5ff5981c3d82afe0efd25dcbef2bcd2"}, - {file = "orjson-3.10.7-cp39-none-win_amd64.whl", hash = "sha256:974683d4618c0c7dbf4f69c95a979734bf183d0658611760017f6e70a145af58"}, - {file = "orjson-3.10.7.tar.gz", hash = "sha256:75ef0640403f945f3a1f9f6400686560dbfb0fb5b16589ad62cd477043c4eee3"}, + {file = "orjson-3.10.10-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b788a579b113acf1c57e0a68e558be71d5d09aa67f62ca1f68e01117e550a998"}, + {file = "orjson-3.10.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:804b18e2b88022c8905bb79bd2cbe59c0cd014b9328f43da8d3b28441995cda4"}, + {file = "orjson-3.10.10-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9972572a1d042ec9ee421b6da69f7cc823da5962237563fa548ab17f152f0b9b"}, + {file = "orjson-3.10.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc6993ab1c2ae7dd0711161e303f1db69062955ac2668181bfdf2dd410e65258"}, + {file = "orjson-3.10.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d78e4cacced5781b01d9bc0f0cd8b70b906a0e109825cb41c1b03f9c41e4ce86"}, + {file = "orjson-3.10.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6eb2598df518281ba0cbc30d24c5b06124ccf7e19169e883c14e0831217a0bc"}, + {file = "orjson-3.10.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23776265c5215ec532de6238a52707048401a568f0fa0d938008e92a147fe2c7"}, + {file = "orjson-3.10.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8cc2a654c08755cef90b468ff17c102e2def0edd62898b2486767204a7f5cc9c"}, + {file = "orjson-3.10.10-cp310-none-win32.whl", hash = "sha256:081b3fc6a86d72efeb67c13d0ea7c030017bd95f9868b1e329a376edc456153b"}, + {file = "orjson-3.10.10-cp310-none-win_amd64.whl", hash = "sha256:ff38c5fb749347768a603be1fb8a31856458af839f31f064c5aa74aca5be9efe"}, + {file = "orjson-3.10.10-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:879e99486c0fbb256266c7c6a67ff84f46035e4f8749ac6317cc83dacd7f993a"}, + {file = "orjson-3.10.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:019481fa9ea5ff13b5d5d95e6fd5ab25ded0810c80b150c2c7b1cc8660b662a7"}, + {file = "orjson-3.10.10-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0dd57eff09894938b4c86d4b871a479260f9e156fa7f12f8cad4b39ea8028bb5"}, + {file = "orjson-3.10.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbde6d70cd95ab4d11ea8ac5e738e30764e510fc54d777336eec09bb93b8576c"}, + {file = "orjson-3.10.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2625cb37b8fb42e2147404e5ff7ef08712099197a9cd38895006d7053e69d6"}, + {file = "orjson-3.10.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbf3c20c6a7db69df58672a0d5815647ecf78c8e62a4d9bd284e8621c1fe5ccb"}, + {file = "orjson-3.10.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:75c38f5647e02d423807d252ce4528bf6a95bd776af999cb1fb48867ed01d1f6"}, + {file = "orjson-3.10.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:23458d31fa50ec18e0ec4b0b4343730928296b11111df5f547c75913714116b2"}, + {file = "orjson-3.10.10-cp311-none-win32.whl", hash = "sha256:2787cd9dedc591c989f3facd7e3e86508eafdc9536a26ec277699c0aa63c685b"}, + {file = "orjson-3.10.10-cp311-none-win_amd64.whl", hash = "sha256:6514449d2c202a75183f807bc755167713297c69f1db57a89a1ef4a0170ee269"}, + {file = "orjson-3.10.10-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8564f48f3620861f5ef1e080ce7cd122ee89d7d6dacf25fcae675ff63b4d6e05"}, + {file = "orjson-3.10.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5bf161a32b479034098c5b81f2608f09167ad2fa1c06abd4e527ea6bf4837a9"}, + {file = "orjson-3.10.10-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68b65c93617bcafa7f04b74ae8bc2cc214bd5cb45168a953256ff83015c6747d"}, + {file = "orjson-3.10.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8e28406f97fc2ea0c6150f4c1b6e8261453318930b334abc419214c82314f85"}, + {file = "orjson-3.10.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4d0d9fe174cc7a5bdce2e6c378bcdb4c49b2bf522a8f996aa586020e1b96cee"}, + {file = "orjson-3.10.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3be81c42f1242cbed03cbb3973501fcaa2675a0af638f8be494eaf37143d999"}, + {file = "orjson-3.10.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:65f9886d3bae65be026219c0a5f32dbbe91a9e6272f56d092ab22561ad0ea33b"}, + {file = "orjson-3.10.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:730ed5350147db7beb23ddaf072f490329e90a1d059711d364b49fe352ec987b"}, + {file = "orjson-3.10.10-cp312-none-win32.whl", hash = "sha256:a8f4bf5f1c85bea2170800020d53a8877812892697f9c2de73d576c9307a8a5f"}, + {file = "orjson-3.10.10-cp312-none-win_amd64.whl", hash = "sha256:384cd13579a1b4cd689d218e329f459eb9ddc504fa48c5a83ef4889db7fd7a4f"}, + {file = "orjson-3.10.10-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44bffae68c291f94ff5a9b4149fe9d1bdd4cd0ff0fb575bcea8351d48db629a1"}, + {file = "orjson-3.10.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e27b4c6437315df3024f0835887127dac2a0a3ff643500ec27088d2588fa5ae1"}, + {file = "orjson-3.10.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca84df16d6b49325a4084fd8b2fe2229cb415e15c46c529f868c3387bb1339d"}, + {file = "orjson-3.10.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c14ce70e8f39bd71f9f80423801b5d10bf93d1dceffdecd04df0f64d2c69bc01"}, + {file = "orjson-3.10.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:24ac62336da9bda1bd93c0491eff0613003b48d3cb5d01470842e7b52a40d5b4"}, + {file = "orjson-3.10.10-cp313-none-win32.whl", hash = "sha256:eb0a42831372ec2b05acc9ee45af77bcaccbd91257345f93780a8e654efc75db"}, + {file = "orjson-3.10.10-cp313-none-win_amd64.whl", hash = "sha256:f0c4f37f8bf3f1075c6cc8dd8a9f843689a4b618628f8812d0a71e6968b95ffd"}, + {file = "orjson-3.10.10-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:829700cc18503efc0cf502d630f612884258020d98a317679cd2054af0259568"}, + {file = "orjson-3.10.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0ceb5e0e8c4f010ac787d29ae6299846935044686509e2f0f06ed441c1ca949"}, + {file = "orjson-3.10.10-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0c25908eb86968613216f3db4d3003f1c45d78eb9046b71056ca327ff92bdbd4"}, + {file = "orjson-3.10.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:218cb0bc03340144b6328a9ff78f0932e642199ac184dd74b01ad691f42f93ff"}, + {file = "orjson-3.10.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2277ec2cea3775640dc81ab5195bb5b2ada2fe0ea6eee4677474edc75ea6785"}, + {file = "orjson-3.10.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:848ea3b55ab5ccc9d7bbd420d69432628b691fba3ca8ae3148c35156cbd282aa"}, + {file = "orjson-3.10.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e3e67b537ac0c835b25b5f7d40d83816abd2d3f4c0b0866ee981a045287a54f3"}, + {file = "orjson-3.10.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:7948cfb909353fce2135dcdbe4521a5e7e1159484e0bb024c1722f272488f2b8"}, + {file = "orjson-3.10.10-cp38-none-win32.whl", hash = "sha256:78bee66a988f1a333dc0b6257503d63553b1957889c17b2c4ed72385cd1b96ae"}, + {file = "orjson-3.10.10-cp38-none-win_amd64.whl", hash = "sha256:f1d647ca8d62afeb774340a343c7fc023efacfd3a39f70c798991063f0c681dd"}, + {file = "orjson-3.10.10-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5a059afddbaa6dd733b5a2d76a90dbc8af790b993b1b5cb97a1176ca713b5df8"}, + {file = "orjson-3.10.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f9b5c59f7e2a1a410f971c5ebc68f1995822837cd10905ee255f96074537ee6"}, + {file = "orjson-3.10.10-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d5ef198bafdef4aa9d49a4165ba53ffdc0a9e1c7b6f76178572ab33118afea25"}, + {file = "orjson-3.10.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf29ce0bb5d3320824ec3d1508652421000ba466abd63bdd52c64bcce9eb1fa"}, + {file = "orjson-3.10.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dddd5516bcc93e723d029c1633ae79c4417477b4f57dad9bfeeb6bc0315e654a"}, + {file = "orjson-3.10.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12f2003695b10817f0fa8b8fca982ed7f5761dcb0d93cff4f2f9f6709903fd7"}, + {file = "orjson-3.10.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:672f9874a8a8fb9bb1b771331d31ba27f57702c8106cdbadad8bda5d10bc1019"}, + {file = "orjson-3.10.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1dcbb0ca5fafb2b378b2c74419480ab2486326974826bbf6588f4dc62137570a"}, + {file = "orjson-3.10.10-cp39-none-win32.whl", hash = "sha256:d9bbd3a4b92256875cb058c3381b782649b9a3c68a4aa9a2fff020c2f9cfc1be"}, + {file = "orjson-3.10.10-cp39-none-win_amd64.whl", hash = "sha256:766f21487a53aee8524b97ca9582d5c6541b03ab6210fbaf10142ae2f3ced2aa"}, + {file = "orjson-3.10.10.tar.gz", hash = "sha256:37949383c4df7b4337ce82ee35b6d7471e55195efa7dcb45ab8226ceadb0fe3b"}, ] [[package]] @@ -2747,13 +2749,13 @@ cli = ["click (>=5.0)"] [[package]] name = "python-multipart" -version = "0.0.12" +version = "0.0.17" description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.8" files = [ - {file = "python_multipart-0.0.12-py3-none-any.whl", hash = "sha256:43dcf96cf65888a9cd3423544dd0d75ac10f7aa0c3c28a175bbcd00c9ce1aebf"}, - {file = "python_multipart-0.0.12.tar.gz", hash = "sha256:045e1f98d719c1ce085ed7f7e1ef9d8ccc8c02ba02b5566d5f7521410ced58cb"}, + {file = "python_multipart-0.0.17-py3-none-any.whl", hash = "sha256:15dc4f487e0a9476cc1201261188ee0940165cffc94429b6fc565c4d3045cb5d"}, + {file = "python_multipart-0.0.17.tar.gz", hash = "sha256:41330d831cae6e2f22902704ead2826ea038d0419530eadff3ea80175aec5538"}, ] [[package]] @@ -2984,13 +2986,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.9.2" +version = "13.9.3" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" files = [ - {file = "rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1"}, - {file = "rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c"}, + {file = "rich-13.9.3-py3-none-any.whl", hash = "sha256:9836f5096eb2172c9e77df411c1b009bace4193d6a481d534fea75ebba758283"}, + {file = "rich-13.9.3.tar.gz", hash = "sha256:bc1e01b899537598cf02579d2b9f4a415104d3fc439313a7a2c165d76557a08e"}, ] [package.dependencies] @@ -3003,29 +3005,29 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "ruff" -version = "0.7.0" +version = "0.7.1" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.7.0-py3-none-linux_armv6l.whl", hash = "sha256:0cdf20c2b6ff98e37df47b2b0bd3a34aaa155f59a11182c1303cce79be715628"}, - {file = "ruff-0.7.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:496494d350c7fdeb36ca4ef1c9f21d80d182423718782222c29b3e72b3512737"}, - {file = "ruff-0.7.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:214b88498684e20b6b2b8852c01d50f0651f3cc6118dfa113b4def9f14faaf06"}, - {file = "ruff-0.7.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630fce3fefe9844e91ea5bbf7ceadab4f9981f42b704fae011bb8efcaf5d84be"}, - {file = "ruff-0.7.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:211d877674e9373d4bb0f1c80f97a0201c61bcd1e9d045b6e9726adc42c156aa"}, - {file = "ruff-0.7.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:194d6c46c98c73949a106425ed40a576f52291c12bc21399eb8f13a0f7073495"}, - {file = "ruff-0.7.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:82c2579b82b9973a110fab281860403b397c08c403de92de19568f32f7178598"}, - {file = "ruff-0.7.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9af971fe85dcd5eaed8f585ddbc6bdbe8c217fb8fcf510ea6bca5bdfff56040e"}, - {file = "ruff-0.7.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b641c7f16939b7d24b7bfc0be4102c56562a18281f84f635604e8a6989948914"}, - {file = "ruff-0.7.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d71672336e46b34e0c90a790afeac8a31954fd42872c1f6adaea1dff76fd44f9"}, - {file = "ruff-0.7.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ab7d98c7eed355166f367597e513a6c82408df4181a937628dbec79abb2a1fe4"}, - {file = "ruff-0.7.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1eb54986f770f49edb14f71d33312d79e00e629a57387382200b1ef12d6a4ef9"}, - {file = "ruff-0.7.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:dc452ba6f2bb9cf8726a84aa877061a2462afe9ae0ea1d411c53d226661c601d"}, - {file = "ruff-0.7.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4b406c2dce5be9bad59f2de26139a86017a517e6bcd2688da515481c05a2cb11"}, - {file = "ruff-0.7.0-py3-none-win32.whl", hash = "sha256:f6c968509f767776f524a8430426539587d5ec5c662f6addb6aa25bc2e8195ec"}, - {file = "ruff-0.7.0-py3-none-win_amd64.whl", hash = "sha256:ff4aabfbaaba880e85d394603b9e75d32b0693152e16fa659a3064a85df7fce2"}, - {file = "ruff-0.7.0-py3-none-win_arm64.whl", hash = "sha256:10842f69c245e78d6adec7e1db0a7d9ddc2fff0621d730e61657b64fa36f207e"}, - {file = "ruff-0.7.0.tar.gz", hash = "sha256:47a86360cf62d9cd53ebfb0b5eb0e882193fc191c6d717e8bef4462bc3b9ea2b"}, + {file = "ruff-0.7.1-py3-none-linux_armv6l.whl", hash = "sha256:cb1bc5ed9403daa7da05475d615739cc0212e861b7306f314379d958592aaa89"}, + {file = "ruff-0.7.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27c1c52a8d199a257ff1e5582d078eab7145129aa02721815ca8fa4f9612dc35"}, + {file = "ruff-0.7.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:588a34e1ef2ea55b4ddfec26bbe76bc866e92523d8c6cdec5e8aceefeff02d99"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94fc32f9cdf72dc75c451e5f072758b118ab8100727168a3df58502b43a599ca"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:985818742b833bffa543a84d1cc11b5e6871de1b4e0ac3060a59a2bae3969250"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32f1e8a192e261366c702c5fb2ece9f68d26625f198a25c408861c16dc2dea9c"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:699085bf05819588551b11751eff33e9ca58b1b86a6843e1b082a7de40da1565"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344cc2b0814047dc8c3a8ff2cd1f3d808bb23c6658db830d25147339d9bf9ea7"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4316bbf69d5a859cc937890c7ac7a6551252b6a01b1d2c97e8fc96e45a7c8b4a"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79d3af9dca4c56043e738a4d6dd1e9444b6d6c10598ac52d146e331eb155a8ad"}, + {file = "ruff-0.7.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5c121b46abde94a505175524e51891f829414e093cd8326d6e741ecfc0a9112"}, + {file = "ruff-0.7.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8422104078324ea250886954e48f1373a8fe7de59283d747c3a7eca050b4e378"}, + {file = "ruff-0.7.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:56aad830af8a9db644e80098fe4984a948e2b6fc2e73891538f43bbe478461b8"}, + {file = "ruff-0.7.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:658304f02f68d3a83c998ad8bf91f9b4f53e93e5412b8f2388359d55869727fd"}, + {file = "ruff-0.7.1-py3-none-win32.whl", hash = "sha256:b517a2011333eb7ce2d402652ecaa0ac1a30c114fbbd55c6b8ee466a7f600ee9"}, + {file = "ruff-0.7.1-py3-none-win_amd64.whl", hash = "sha256:f38c41fcde1728736b4eb2b18850f6d1e3eedd9678c914dede554a70d5241307"}, + {file = "ruff-0.7.1-py3-none-win_arm64.whl", hash = "sha256:19aa200ec824c0f36d0c9114c8ec0087082021732979a359d6f3c390a6ff2a37"}, + {file = "ruff-0.7.1.tar.gz", hash = "sha256:9d8a41d4aa2dad1575adb98a82870cf5db5f76b2938cf2206c22c940034a36f4"}, ] [[package]] @@ -3204,13 +3206,13 @@ files = [ [[package]] name = "starlette" -version = "0.37.2" +version = "0.41.2" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" files = [ - {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, - {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, + {file = "starlette-0.41.2-py3-none-any.whl", hash = "sha256:fbc189474b4731cf30fcef52f18a8d070e3f3b46c6a04c97579e85e6ffca942d"}, + {file = "starlette-0.41.2.tar.gz", hash = "sha256:9834fd799d1a87fd346deb76158668cfa0b0d56f85caefe8268e2d97c3468b62"}, ] [package.dependencies] @@ -3776,4 +3778,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<4.0" -content-hash = "f4594d26ee661fb239c7b5750a4c79e5e049480182928af816ccf5e34e8b641f" +content-hash = "b690d5fbd141da3947f4f1dc029aba1b95e7faafd723166f2c4bdc47a66c095e" diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml index 87019909a58d0..8029dcd25043a 100644 --- a/machine-learning/pyproject.toml +++ b/machine-learning/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "machine-learning" -version = "1.118.2" +version = "1.120.2" description = "" authors = ["Hau Tran "] readme = "README.md" @@ -11,14 +11,13 @@ python = ">=3.10,<4.0" insightface = ">=0.7.3,<1.0" opencv-python-headless = ">=4.7.0.72,<5.0" pillow = ">=9.5.0,<11.0" -fastapi-slim = ">=0.95.2,<1.0" +fastapi = ">=0.95.2,<1.0" uvicorn = {extras = ["standard"], version = ">=0.22.0,<1.0"} pydantic = "^2.0.0" pydantic-settings = "^2.5.2" aiocache = ">=0.12.1,<1.0" rich = ">=13.4.2" ftfy = ">=6.1.1" -setuptools = "^70.0.0" python-multipart = ">=0.0.6,<1.0" orjson = ">=3.9.5" gunicorn = ">=21.1.0" diff --git a/mobile/.fvmrc b/mobile/.fvmrc index ee6eaac06fefc..739573148100d 100644 --- a/mobile/.fvmrc +++ b/mobile/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.24.3" + "flutter": "3.24.4" } diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle index 52750232cceba..506ee9d1a4b0a 100644 --- a/mobile/android/app/build.gradle +++ b/mobile/android/app/build.gradle @@ -2,7 +2,7 @@ plugins { id "com.android.application" id "kotlin-android" id "dev.flutter.flutter-gradle-plugin" - id "kotlin-kapt" + id 'com.google.devtools.ksp' } def localProperties = new Properties() @@ -31,12 +31,13 @@ android { compileSdkVersion 34 compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + coreLibraryDesugaringEnabled true } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } sourceSets { @@ -74,6 +75,7 @@ android { signingConfig signingConfigs.release } } + namespace 'app.alextran.immich' } flutter { @@ -81,11 +83,11 @@ flutter { } dependencies { - def kotlin_version = '1.9.24' - def kotlin_coroutines_version = '1.8.1' - def work_version = '2.9.0' - def concurrent_version = '1.1.0' - def guava_version = '33.2.0-android' + def kotlin_version = '2.0.20' + def kotlin_coroutines_version = '1.9.0' + def work_version = '2.9.1' + def concurrent_version = '1.2.0' + def guava_version = '33.3.1-android' def glide_version = '4.16.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" @@ -94,7 +96,8 @@ dependencies { implementation "androidx.concurrent:concurrent-futures:$concurrent_version" implementation "com.google.guava:guava:$guava_version" implementation "com.github.bumptech.glide:glide:$glide_version" - kapt "com.github.bumptech.glide:compiler:$glide_version" + ksp "com.github.bumptech.glide:ksp:$glide_version" + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.2' } // This is uncommented in F-Droid build script diff --git a/mobile/android/app/proguard-rules.pro b/mobile/android/app/proguard-rules.pro new file mode 100644 index 0000000000000..ea6dd795b58c9 --- /dev/null +++ b/mobile/android/app/proguard-rules.pro @@ -0,0 +1,32 @@ +##---------------Begin: proguard configuration for Gson ---------- +# Gson uses generic type information stored in a class file when working with fields. Proguard +# removes such information by default, so configure it to keep all of it. +-keepattributes Signature + +# For using GSON @Expose annotation +-keepattributes *Annotation* + +# Gson specific classes +-dontwarn sun.misc.** +#-keep class com.google.gson.stream.** { *; } + +# Application classes that will be serialized/deserialized over Gson +-keep class com.google.gson.examples.android.model.** { ; } + +# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory, +# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) +-keep class * extends com.google.gson.TypeAdapter +-keep class * implements com.google.gson.TypeAdapterFactory +-keep class * implements com.google.gson.JsonSerializer +-keep class * implements com.google.gson.JsonDeserializer + +# Prevent R8 from leaving Data object members always null +-keepclassmembers,allowobfuscation class * { + @com.google.gson.annotations.SerializedName ; +} + +# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher. +-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken +-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken + +##---------------End: proguard configuration for Gson ---------- \ No newline at end of file diff --git a/mobile/android/app/src/debug/AndroidManifest.xml b/mobile/android/app/src/debug/AndroidManifest.xml index e33c470b4d2a0..ac7c0c7e53034 100644 --- a/mobile/android/app/src/debug/AndroidManifest.xml +++ b/mobile/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - + - \ No newline at end of file + diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index 17c2830b48e26..c85ce136844bc 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - diff --git a/mobile/android/app/src/profile/AndroidManifest.xml b/mobile/android/app/src/profile/AndroidManifest.xml index e33c470b4d2a0..ac7c0c7e53034 100644 --- a/mobile/android/app/src/profile/AndroidManifest.xml +++ b/mobile/android/app/src/profile/AndroidManifest.xml @@ -1,6 +1,6 @@ - + - \ No newline at end of file + diff --git a/mobile/android/build.gradle b/mobile/android/build.gradle index 87cc79281dd37..7a39a8d3cccf9 100644 --- a/mobile/android/build.gradle +++ b/mobile/android/build.gradle @@ -1,5 +1,5 @@ allprojects { - ext.kotlin_version = '1.9.24' + ext.kotlin_version = '2.0.20' repositories { google() diff --git a/mobile/android/fastlane/Fastfile b/mobile/android/fastlane/Fastfile index 22ad63244d3ed..59deb9a3be9fe 100644 --- a/mobile/android/fastlane/Fastfile +++ b/mobile/android/fastlane/Fastfile @@ -35,8 +35,8 @@ platform :android do task: 'bundle', build_type: 'Release', properties: { - "android.injected.version.code" => 163, - "android.injected.version.name" => "1.118.2", + "android.injected.version.code" => 167, + "android.injected.version.name" => "1.120.2", } ) upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab') diff --git a/mobile/android/gradle.properties b/mobile/android/gradle.properties index 4d3226abc21bb..78c37cc2a3bb2 100644 --- a/mobile/android/gradle.properties +++ b/mobile/android/gradle.properties @@ -1,3 +1,5 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4096M android.useAndroidX=true -android.enableJetifier=true \ No newline at end of file +android.enableJetifier=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false \ No newline at end of file diff --git a/mobile/android/gradle/wrapper/gradle-wrapper.properties b/mobile/android/gradle/wrapper/gradle-wrapper.properties index 6357330c9e8fd..dedd5d1e69e69 100644 --- a/mobile/android/gradle/wrapper/gradle-wrapper.properties +++ b/mobile/android/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip +networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip -distributionSha256Sum=fe696c020f241a5f69c30f763c5a7f38eec54b490db19cd2b0962dda420d7d12 \ No newline at end of file diff --git a/mobile/android/settings.gradle b/mobile/android/settings.gradle index e809a0abaa38f..74f8904a10960 100644 --- a/mobile/android/settings.gradle +++ b/mobile/android/settings.gradle @@ -18,9 +18,9 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "7.4.2" apply false - id "org.jetbrains.kotlin.android" version "1.9.0" apply false - id "org.jetbrains.kotlin.kapt" version "1.9.0" apply false + id "com.android.application" version '8.7.2' apply false + id "org.jetbrains.kotlin.android" version "2.0.20" apply false + id 'com.google.devtools.ksp' version '2.0.20-1.0.24' apply false } include ":app" diff --git a/mobile/assets/i18n/cs-CZ.json b/mobile/assets/i18n/cs-CZ.json index 6a2c70a2a91a3..6e3462b26f7f3 100644 --- a/mobile/assets/i18n/cs-CZ.json +++ b/mobile/assets/i18n/cs-CZ.json @@ -44,9 +44,9 @@ "app_bar_signout_dialog_content": "Určitě se chcete odhlásit?", "app_bar_signout_dialog_ok": "Ano", "app_bar_signout_dialog_title": "Odhlásit se", - "archived": "Archivované", + "archived": "Archiv", "archive_page_no_archived_assets": "Nebyla nalezena žádná archivovaná média", - "archive_page_title": "Archív ({})", + "archive_page_title": "Archiv ({})", "asset_action_delete_err_read_only": "Nelze odstranit položky pouze pro čtení, přeskakuji", "asset_action_share_err_offline": "Nelze načíst offline položky, přeskakuji", "asset_list_group_by_sub_title": "Seskupit podle", @@ -172,7 +172,7 @@ "control_bottom_app_bar_add_to_album": "Přidat do alba", "control_bottom_app_bar_album_info": "{} položek", "control_bottom_app_bar_album_info_shared": "{} položky – sdílené", - "control_bottom_app_bar_archive": "Archív", + "control_bottom_app_bar_archive": "Archivovat", "control_bottom_app_bar_create_new_album": "Vytvořit nové album", "control_bottom_app_bar_delete": "Smazat", "control_bottom_app_bar_delete_from_immich": "Smazat ze serveru Immich", @@ -285,7 +285,7 @@ "invalid_date_format": "Chybný formát data", "library": "Knihovna", "library_page_albums": "Alba", - "library_page_archive": "Archív", + "library_page_archive": "Archivovat", "library_page_device_albums": "Alba v zařízení", "library_page_favorites": "Oblíbené", "library_page_new_album": "Nové album", diff --git a/mobile/assets/i18n/de-DE.json b/mobile/assets/i18n/de-DE.json index b3452889fdecb..d84487973982f 100644 --- a/mobile/assets/i18n/de-DE.json +++ b/mobile/assets/i18n/de-DE.json @@ -414,7 +414,7 @@ "recently_added_page_title": "Zuletzt hinzugefügt", "save_to_gallery": "In Galerie speichern", "scaffold_body_error_occurred": "Ein Fehler ist aufgetreten", - "search_albums": "Suche Alben", + "search_albums": "nach Album suchen", "search_bar_hint": "Durchsuche deine Fotos", "search_filter_apply": "Filter anwenden", "search_filter_camera": "Kamera", diff --git a/mobile/assets/i18n/el-GR.json b/mobile/assets/i18n/el-GR.json index 5d8d077fab29f..dac5667cc1277 100644 --- a/mobile/assets/i18n/el-GR.json +++ b/mobile/assets/i18n/el-GR.json @@ -1,19 +1,19 @@ { - "action_common_back": "Back", + "action_common_back": "Πίσω", "action_common_cancel": "Ακύρωση", - "action_common_clear": "Clear", - "action_common_confirm": "Confirm", - "action_common_save": "Save", - "action_common_select": "Select", + "action_common_clear": "Εκκαθάριση", + "action_common_confirm": "Επιβεβαίωση", + "action_common_save": "Αποθήκευση", + "action_common_select": "Επιλογή", "action_common_update": "Ενημέρωση", - "add_a_name": "Add a name", + "add_a_name": "Πρόσθεση ονόματος", "add_to_album_bottom_sheet_added": "Προστέθηκε στο {album}", "add_to_album_bottom_sheet_already_exists": "Ήδη στο {album}", "advanced_settings_log_level_title": "Επίπεδο καταγραφής: {}", "advanced_settings_prefer_remote_subtitle": "Μερικές συσκευές αργούν πολύ να φορτώσουν μικρογραφίες από αρχεία στη συσκευή. Ενεργοποιήστε αυτήν τη ρύθμιση για να φορτώνονται αντί αυτού απομακρυσμένες εικόνες.", "advanced_settings_prefer_remote_title": "Προτίμηση απομακρυσμένων εικόνων.", - "advanced_settings_proxy_headers_subtitle": "Define proxy headers Immich should send with each network request", - "advanced_settings_proxy_headers_title": "Proxy Headers", + "advanced_settings_proxy_headers_subtitle": "Καθορισμός κεφαλίδων διακομιστή μεσολάβησης που το Immich πρέπει να στέλνει με κάθε αίτημα δικτύου", + "advanced_settings_proxy_headers_title": "Κεφαλίδες διακομιστή μεσολάβησης", "advanced_settings_self_signed_ssl_subtitle": "Παρακάμπτει τον έλεγχο πιστοποιητικού SSL του διακομιστή. Απαραίτητο για αυτο-υπογεγραμμένα πιστοποιητικά.", "advanced_settings_self_signed_ssl_title": "Να επιτρέπονται αυτο-υπογεγραμμένα πιστοποιητικά SSL", "advanced_settings_tile_subtitle": "Ρυθμίσεις προχωρημένου χρήστη", @@ -22,13 +22,13 @@ "advanced_settings_troubleshooting_title": "Αντιμετώπιση προβλημάτων", "album_info_card_backup_album_excluded": "ΕΞΑΙΡΟΥΜΕΝΟ", "album_info_card_backup_album_included": "ΣΥΜΠΕΡΙΛΑΜΒΑΝΟΜΕΝΟ", - "albums": "Albums", + "albums": "Άλμπουμ", "album_thumbnail_card_item": "1 αντικείμενο", "album_thumbnail_card_items": "{} αντικείμενα", "album_thumbnail_card_shared": "· Κοινόχρηστο", "album_thumbnail_owned": "Δικό μου", "album_thumbnail_shared_by": "Κοινοποιημένο από {}", - "album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?", + "album_viewer_appbar_delete_confirm": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το άλμπουμ από τον λογαριασμό σας;", "album_viewer_appbar_share_delete": "Διαγραφή άλμπουμ", "album_viewer_appbar_share_err_delete": "Αποτυχία διαγραφής άλμπουμ", "album_viewer_appbar_share_err_leave": "Αποτυχία αποχώρησης από άλμπουμ", @@ -38,34 +38,34 @@ "album_viewer_appbar_share_remove": "Αφαίρεση από άλμπουμ", "album_viewer_appbar_share_to": "Κοινοποίηση σε", "album_viewer_page_share_add_users": "Προσθήκη χρηστών", - "all": "All", + "all": "Όλα", "all_people_page_title": "Άτομα", "all_videos_page_title": "Βίντεο", "app_bar_signout_dialog_content": "Είστε βέβαιοι ότι θέλετε να αποσυνδεθείτε;", "app_bar_signout_dialog_ok": "Ναι", "app_bar_signout_dialog_title": "Αποσύνδεση", - "archived": "Archived", + "archived": "Αρχειοθετήθηκε", "archive_page_no_archived_assets": "Δε βρέθηκαν αρχειοθετημένα στοιχεία", "archive_page_title": "Αρχειοθέτηση ({})", - "asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping", - "asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping", - "asset_list_group_by_sub_title": "Group by", + "asset_action_delete_err_read_only": "Δεν είναι δυνατή η διαγραφή στοιχείων μόνο για ανάγνωση, παραλείπεται", + "asset_action_share_err_offline": "Δεν είναι δυνατή η ανάκτηση στοιχείων εκτός σύνδεσης, παραλείπεται", + "asset_list_group_by_sub_title": "Ομαδοποίηση κατά", "asset_list_layout_settings_dynamic_layout_title": "Δυναμική διάταξη", "asset_list_layout_settings_group_automatically": "Αυτόματα", "asset_list_layout_settings_group_by": "Ομαδοποίηση στοιχείων ανά", "asset_list_layout_settings_group_by_month": "Μήνας", "asset_list_layout_settings_group_by_month_day": "Μήνας + ημέρα", - "asset_list_layout_sub_title": "Layout", + "asset_list_layout_sub_title": "Διάταξη", "asset_list_settings_subtitle": "Ρυθμίσεις διάταξης πλέγματος φωτογραφιών", "asset_list_settings_title": "Πλέγμα φωτογραφιών", - "asset_restored_successfully": "Asset restored successfully", - "assets_deleted_permanently": "{} asset(s) deleted permanently", - "assets_deleted_permanently_from_server": "{} asset(s) deleted permanently from the Immich server", - "assets_removed_permanently_from_device": "{} asset(s) removed permanently from your device", - "assets_restored_successfully": "{} asset(s) restored successfully", - "assets_trashed": "{} asset(s) trashed", - "assets_trashed_from_server": "{} asset(s) trashed from the Immich server", - "asset_viewer_settings_title": "Asset Viewer", + "asset_restored_successfully": "Το στοιχείο αποκαταστάθηκε με επιτυχία", + "assets_deleted_permanently": "{} στοιχείο(α) διαγράφηκαν οριστικά", + "assets_deleted_permanently_from_server": "{} στοιχείο(α) διαγράφηκαν οριστικά από τον διακομιστή Immich", + "assets_removed_permanently_from_device": "{} στοιχεία καταργήθηκαν οριστικά από τη συσκευή σας", + "assets_restored_successfully": "{} στοιχεία αποκαταστάθηκαν με επιτυχία", + "assets_trashed": "{} στοιχεία μεταφέρθηκαν στον κάδο απορριμμάτων", + "assets_trashed_from_server": "{} στοιχεία μεταφέρθηκαν στον κάδο απορριμμάτων από τον διακομιστή Immich", + "asset_viewer_settings_title": "Προβολή Στοιχείων", "backup_album_selection_page_albums_device": "Άλμπουμ στη συσκευή ({})", "backup_album_selection_page_albums_tap": "Πάτημα για συμπερίληψη, διπλό πάτημα για εξαίρεση", "backup_album_selection_page_assets_scatter": "Τα στοιχεία μπορεί να διασκορπιστούν σε πολλά άλμπουμ. Έτσι, τα άλμπουμ μπορούν να περιληφθούν ή να εξαιρεθούν κατά τη διαδικασία δημιουργίας αντιγράφων ασφαλείας.", @@ -130,7 +130,7 @@ "backup_manual_in_progress": "Μεταφόρτωση σε εξέλιξη. Δοκιμάστε αργότερα", "backup_manual_success": "Επιτυχία", "backup_manual_title": "Κατάσταση μεταφόρτωσης", - "backup_options_page_title": "Backup options", + "backup_options_page_title": "Επιλογές αντιγράφων ασφαλείας", "cache_settings_album_thumbnails": "Μικρογραφίες σελίδας βιβλιοθήκης ({} στοιχεία)", "cache_settings_clear_cache_button": "Εκκαθάριση προσωρινής μνήμης", "cache_settings_clear_cache_button_title": "Καθαρίζει τη προσωρινή μνήμη της εφαρμογής. Αυτό θα επηρεάσει σημαντικά την απόδοση της εφαρμογής μέχρι να αναδημιουργηθεί η προσωρινή μνήμη.", @@ -154,21 +154,21 @@ "change_password_form_new_password": "Νέος Κωδικός", "change_password_form_password_mismatch": "Οι κωδικοί δεν ταιριάζουν", "change_password_form_reenter_new_password": "Επανεισαγωγή Νέου Κωδικού", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Enter Password", - "client_cert_import": "Import", - "client_cert_import_success_msg": "Client certificate is imported", - "client_cert_invalid_msg": "Invalid certificate file or wrong password", - "client_cert_remove": "Remove", - "client_cert_remove_msg": "Client certificate is removed", - "client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate Import/Remove is available only before login", - "client_cert_title": "SSL Client Certificate", + "client_cert_dialog_msg_confirm": "ΟΚ", + "client_cert_enter_password": "Εισαγάγετε κωδικό πρόσβασης", + "client_cert_import": "Εισαγωγή", + "client_cert_import_success_msg": "Το πιστοποιητικό πελάτη εισάγεται", + "client_cert_invalid_msg": "Μη έγκυρο αρχείο πιστοποιητικού ή λάθος κωδικός πρόσβασης", + "client_cert_remove": "Αφαίρεση", + "client_cert_remove_msg": "Το πιστοποιητικό πελάτη καταργήθηκε", + "client_cert_subtitle": "Υποστηρίζει μόνο τη μορφή PKCS12 (.p12, .pfx). Η Εισαγωγή/Αφαίρεση πιστοποιητικού είναι διαθέσιμη μόνο πριν από τη σύνδεση", + "client_cert_title": "Πιστοποιητικό πελάτη SSL", "common_add_to_album": "Προσθήκη στο άλμπουμ", "common_change_password": "Αλλαγή Κωδικού", "common_create_new_album": "Δημιουργία νέου άλμπουμ", "common_server_error": "Ελέγξτε τη σύνδεσή σας, βεβαιωθείτε ότι ο διακομιστής είναι προσβάσιμος και ότι οι εκδόσεις της εφαρμογής/διακομιστή είναι συμβατές.", "common_shared": "Κοινόχρηστο", - "contextual_search": "Sunrise on the beach", + "contextual_search": "Ανατολή στην παραλία", "control_bottom_app_bar_add_to_album": "Προσθήκη στο άλμπουμ", "control_bottom_app_bar_album_info": "{} αντικείμενα", "control_bottom_app_bar_album_info_shared": "{} αντικείμενα · Κοινόχρηστα", @@ -177,8 +177,8 @@ "control_bottom_app_bar_delete": "Διαγραφή", "control_bottom_app_bar_delete_from_immich": "Διαγραφή από το Immich", "control_bottom_app_bar_delete_from_local": "Διαγραφή από τη συσκευή", - "control_bottom_app_bar_download": "Download", - "control_bottom_app_bar_edit": "Edit", + "control_bottom_app_bar_download": "Λήψη", + "control_bottom_app_bar_edit": "Επεξεργασία", "control_bottom_app_bar_edit_location": "Επεξεργασία Τοποθεσίας", "control_bottom_app_bar_edit_time": "Επεξεργασία Ημερομηνίας & Ώρας", "control_bottom_app_bar_favorite": "Προσθήκη στα αγαπημένα", @@ -189,14 +189,14 @@ "control_bottom_app_bar_unarchive": "Αναίρεση αρχειοθέτησης", "control_bottom_app_bar_unfavorite": "Κατάργηση από τα αγαπημένα", "control_bottom_app_bar_upload": "Μεταφόρτωση", - "create_album": "Create album", + "create_album": "Δημιουργία άλμπουμ", "create_album_page_untitled": "Χωρίς τίτλο", - "create_new": "CREATE NEW", + "create_new": "ΔΗΜΙΟΥΡΓΙΑ ΝΕΟΥ", "create_shared_album_page_create": "Δημιουργία", "create_shared_album_page_share": "Κοινοποίηση", "create_shared_album_page_share_add_assets": "ΠΡΟΣΘΗΚΗ ΣΤΟΙΧΕΙΩΝ", "create_shared_album_page_share_select_photos": "Επιλέξτε Φωτογραφίες", - "crop": "Crop", + "crop": "Αποκοπή", "curated_location_page_title": "Τοποθεσίες", "curated_object_page_title": "Πράγματα", "daily_title_text_date": "Ε, MMM dd", @@ -208,174 +208,174 @@ "delete_dialog_alert_remote": "Αυτά τα αντικείμενα θα διαγραφούν οριστικά από τον διακομιστή Immich", "delete_dialog_cancel": "Ακύρωση", "delete_dialog_ok": "Διαγραφή", - "delete_dialog_ok_force": "Delete Anyway", + "delete_dialog_ok_force": "Διαγραφή όπως και να έχει", "delete_dialog_title": "Οριστική Διαγραφή", - "delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only", - "delete_local_dialog_ok_force": "Delete Anyway", + "delete_local_dialog_ok_backed_up_only": "Διαγραφή μόνο των αντιγράφων ασφαλείας", + "delete_local_dialog_ok_force": "Διαγραφή όπως και να έχει", "delete_shared_link_dialog_content": "Σίγουρα θέλετε να διαγράψετε αυτόν τον κοινοποιημένο σύνδεσμο;", "delete_shared_link_dialog_title": "Διαγραφή Κοινοποιημένου Συνδέσμου", "description_input_hint_text": "Προσθήκη περιγραφής...", "description_input_submit_error": "Σφάλμα κατά την ενημέρωση της περιγραφής, ελέγξτε το αρχείο καταγραφής για περισσότερες λεπτομέρειες", - "download_canceled": "Download canceled", - "download_complete": "Download complete", - "download_enqueue": "Download enqueued", - "download_error": "Download Error", - "download_failed": "Download failed", - "download_filename": "file: {}", - "download_finished": "Download finished", - "downloading": "Downloading...", - "downloading_media": "Downloading media", - "download_notfound": "Download not found", - "download_paused": "Download paused", - "download_started": "Download started", - "download_sucess": "Download success", - "download_sucess_android": "The media has been downloaded to DCIM/Immich", - "download_waiting_to_retry": "Waiting to retry", + "download_canceled": "Η λήψη ακυρώθηκε", + "download_complete": "Η λήψη ολοκληρώθηκε", + "download_enqueue": "Η λήψη τέθηκε σε ουρά", + "download_error": "Σφάλμα λήψης", + "download_failed": "Η λήψη απέτυχε", + "download_filename": "αρχείο: {}", + "download_finished": "Η λήψη ολοκληρώθηκε", + "downloading": "Λήψη...", + "downloading_media": "Λήψη πολυμέσων", + "download_notfound": "Το αρχείο δεν βρέθηκε", + "download_paused": "Η λήψη διακόπηκε", + "download_started": "Η λήψη ξεκίνησε", + "download_sucess": "Επιτυχία λήψης", + "download_sucess_android": "Το μέσο έχει ληφθεί στο DCIM/Immich", + "download_waiting_to_retry": "Αναμονή για επανάληψη", "edit_date_time_dialog_date_time": "Ημερομηνία και Ώρα", "edit_date_time_dialog_timezone": "Ζώνη ώρας", - "edit_image_title": "Edit", + "edit_image_title": "Επεξεργασία", "edit_location_dialog_title": "Τοποθεσία", - "error_saving_image": "Error: {}", + "error_saving_image": "Σφάλμα: {}", "exif_bottom_sheet_description": "Προσθήκη Περιγραφής...", "exif_bottom_sheet_details": "ΛΕΠΤΟΜΕΡΕΙΕΣ", "exif_bottom_sheet_location": "ΤΟΠΟΘΕΣΙΑ", "exif_bottom_sheet_location_add": "Προσθήκη τοποθεσίας", - "exif_bottom_sheet_people": "PEOPLE", - "exif_bottom_sheet_person_add_person": "Add name", + "exif_bottom_sheet_people": "ΑΝΘΡΩΠΟΙ", + "exif_bottom_sheet_person_add_person": "Προσθήκη ονόματος", "experimental_settings_new_asset_list_subtitle": "Σε εξέλιξη", "experimental_settings_new_asset_list_title": "Ενεργοποίηση πειραματικού πλέγματος φωτογραφιών", "experimental_settings_subtitle": "Χρησιμοποιείτε με δική σας ευθύνη!", "experimental_settings_title": "Πειραματικό", - "favorites": "Favorites", + "favorites": "Αγαπημένα", "favorites_page_no_favorites": "Δεν βρέθηκαν αγαπημένα στοιχεία", "favorites_page_title": "Αγαπημένα", - "filename_search": "File name or extension", - "filter": "Filter", - "haptic_feedback_switch": "Enable haptic feedback", - "haptic_feedback_title": "Haptic Feedback", - "header_settings_add_header_tip": "Add Header", - "header_settings_field_validator_msg": "Value cannot be empty", - "header_settings_header_name_input": "Header name", - "header_settings_header_value_input": "Header value", - "header_settings_page_title": "Proxy Headers", - "headers_settings_tile_subtitle": "Define proxy headers the app should send with each network request", - "headers_settings_tile_title": "Custom proxy headers", + "filename_search": "Όνομα αρχείου ή επέκταση", + "filter": "Φίλτρο", + "haptic_feedback_switch": "Ενεργοποίηση απτικής ανάδρασης", + "haptic_feedback_title": "Απτική Ανάδραση", + "header_settings_add_header_tip": "Προσθήκη Κεφαλίδας", + "header_settings_field_validator_msg": "Η τιμή δεν μπορεί να είναι κενή", + "header_settings_header_name_input": "Όνομα κεφαλίδας", + "header_settings_header_value_input": "Τιμή κεφαλίδας", + "header_settings_page_title": "Κεφαλίδες διακομιστή μεσολάβησης", + "headers_settings_tile_subtitle": "Καθορίστε τις κεφαλίδες διακομιστή μεσολάβησης που θα πρέπει να στέλνει η εφαρμογή με κάθε αίτημα δικτύου", + "headers_settings_tile_title": "Προσαρμοσμένες κεφαλίδες διακομιστή μεσολάβησης", "home_page_add_to_album_conflicts": "Προστέθηκαν {added} στοιχεία στο άλμπουμ {album}. {failed} στοιχεία υπάρχουν ήδη στο άλμπουμ.", "home_page_add_to_album_err_local": "Δεν είναι ακόμη δυνατή η προσθήκη τοπικών στοιχείων σε άλμπουμ, παράβλεψη", "home_page_add_to_album_success": "Προστέθηκαν {added} στοιχεία στο άλμπουμ {album}.", - "home_page_album_err_partner": "Can not add partner assets to an album yet, skipping", - "home_page_archive_err_local": "Can not archive local assets yet, skipping", - "home_page_archive_err_partner": "Can not archive partner assets, skipping", - "home_page_building_timeline": "Building the timeline", - "home_page_delete_err_partner": "Can not delete partner assets, skipping", - "home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping", - "home_page_favorite_err_local": "Can not favorite local assets yet, skipping", - "home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping", - "home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).", - "home_page_share_err_local": "Can not share local assets via link, skipping", - "home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping", - "ignore_icloud_photos": "Ignore iCloud photos", - "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server", - "image_saved_successfully": "Image saved", - "image_viewer_page_state_provider_download_error": "Download Error", - "image_viewer_page_state_provider_download_started": "Download Started", - "image_viewer_page_state_provider_download_success": "Download Success", - "image_viewer_page_state_provider_share_error": "Share Error", - "invalid_date": "Invalid date", - "invalid_date_format": "Invalid date format", - "library": "Library", - "library_page_albums": "Albums", - "library_page_archive": "Archive", - "library_page_device_albums": "Albums on Device", - "library_page_favorites": "Favorites", - "library_page_new_album": "New album", - "library_page_sharing": "Sharing", - "library_page_sort_asset_count": "Number of assets", - "library_page_sort_created": "Created date", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_most_oldest_photo": "Oldest photo", - "library_page_sort_most_recent_photo": "Most recent photo", - "library_page_sort_title": "Album title", - "location_picker_choose_on_map": "Choose on map", - "location_picker_latitude": "Latitude", - "location_picker_latitude_error": "Enter a valid latitude", - "location_picker_latitude_hint": "Enter your latitude here", - "location_picker_longitude": "Longitude", - "location_picker_longitude_error": "Enter a valid longitude", - "location_picker_longitude_hint": "Enter your longitude here", + "home_page_album_err_partner": "Δεν είναι δυνατή η προσθήκη στοιχείων συντρόφου σε ένα άλμπουμ ακόμα, παραλείπεται", + "home_page_archive_err_local": "Δεν είναι δυνατή η αρχειοθέτηση τοπικών στοιχείων ακόμα, παραλείπεται", + "home_page_archive_err_partner": "Δεν είναι δυνατή η αρχειοθέτηση στοιχείων συντρόφου, παραλείπεται", + "home_page_building_timeline": "Χτίζεται το χρονοδιάγραμμα", + "home_page_delete_err_partner": "Δεν είναι δυνατή η διαγραφή στοιχείων συντρόφου, παραλείπεται", + "home_page_delete_remote_err_local": "Τοπικά στοιχεία στη διαγραφή απομακρυσμένης επιλογής, παραλείπεται", + "home_page_favorite_err_local": "Δεν μπορώ ακόμα να αγαπήσω τα τοπικά στοιχεία, παραλείπεται", + "home_page_favorite_err_partner": "Δεν είναι ακόμα δυνατή η πρόσθεση στοιχείων συντρόφου στα αγαπημένα, παραλείπεται", + "home_page_first_time_notice": "Εάν αυτή είναι η πρώτη φορά που χρησιμοποιείτε την εφαρμογή, βεβαιωθείτε ότι έχετε επιλέξει ένα άλμπουμ αντίγραφου ασφαλείας, ώστε το χρονοδιάγραμμα να μπορεί να συμπληρώσει φωτογραφίες και βίντεο στα άλμπουμ.", + "home_page_share_err_local": "Δεν είναι δυνατή η κοινή χρήση τοπικών στοιχείων μέσω συνδέσμου, παραλείπεται", + "home_page_upload_err_limit": "Μπορείτε να ανεβάσετε μόνο 30 στοιχεία κάθε φορά, παραλείπεται", + "ignore_icloud_photos": "Αγνοήστε τις φωτογραφίες iCloud", + "ignore_icloud_photos_description": "Οι φωτογραφίες που είναι αποθηκευμένες στο iCloud δεν θα μεταφορτωθούν στον διακομιστή Immich", + "image_saved_successfully": "Η εικόνα αποθηκεύτηκε", + "image_viewer_page_state_provider_download_error": "Σφάλμα Λήψης", + "image_viewer_page_state_provider_download_started": "Ξεκίνησε Λήψη", + "image_viewer_page_state_provider_download_success": "Επιτυχία Λήψης", + "image_viewer_page_state_provider_share_error": "Σφάλμα Κοινής Χρήσης", + "invalid_date": "Μη έγκυρη ημερομηνία", + "invalid_date_format": "Μη έγκυρη μορφή ημερομηνίας", + "library": "Βιβλιοθήκη", + "library_page_albums": "Άλμπουμ", + "library_page_archive": "Αρχείο", + "library_page_device_albums": "Άλμπουμ στη Συσκευή", + "library_page_favorites": "Αγαπημένα", + "library_page_new_album": "Νέο άλμπουμ", + "library_page_sharing": "Κοινή Χρήση", + "library_page_sort_asset_count": "Αριθμός στοιχείων", + "library_page_sort_created": "Ημερομηνία δημιουργίας", + "library_page_sort_last_modified": "Τελευταία τροποποίηση", + "library_page_sort_most_oldest_photo": "Πιο παλιά φωτογραφία", + "library_page_sort_most_recent_photo": "Πιο πρόσφατη φωτογραφία", + "library_page_sort_title": "Τίτλος άλμπουμ", + "location_picker_choose_on_map": "Επιλέξτε στο χάρτη", + "location_picker_latitude": "Γεωγραφικό πλάτος", + "location_picker_latitude_error": "Εισαγάγετε ένα έγκυρο γεωγραφικό πλάτος", + "location_picker_latitude_hint": "Εισαγάγετε το γεωγραφικό πλάτος σας εδώ", + "location_picker_longitude": "Γεωγραφικό μήκος", + "location_picker_longitude_error": "Εισαγάγετε ένα έγκυρο γεωγραφικό μήκος", + "location_picker_longitude_hint": "Εισαγάγετε εδώ το γεωγραφικό σας μήκος", "login_disabled": "Η σύνδεση έχει απενεργοποιηθεί", - "login_form_api_exception": "API exception. Please check the server URL and try again.", - "login_form_back_button_text": "Back", - "login_form_button_text": "Login", - "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port/api", - "login_form_endpoint_url": "Server Endpoint URL", - "login_form_err_http": "Please specify http:// or https://", - "login_form_err_invalid_email": "Invalid Email", - "login_form_err_invalid_url": "Invalid URL", - "login_form_err_leading_whitespace": "Leading whitespace", - "login_form_err_trailing_whitespace": "Trailing whitespace", - "login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL", - "login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server", - "login_form_failed_login": "Error logging you in, check server URL, email and password", - "login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.", + "login_form_api_exception": "Εξαίρεση API. Ελέγξτε τη διεύθυνση URL του διακομιστή και δοκιμάστε ξανά.", + "login_form_back_button_text": "Πίσω", + "login_form_button_text": "Σύνδεση", + "login_form_email_hint": "to-email-sou@email.com", + "login_form_endpoint_hint": "http://ip-tou-server-sou:porta/api", + "login_form_endpoint_url": "URL τελικού σημείου διακομιστή", + "login_form_err_http": "Προσδιορίστε http:// ή https://", + "login_form_err_invalid_email": "Μη έγκυρο email", + "login_form_err_invalid_url": "Μη έγκυρη διεύθυνση URL", + "login_form_err_leading_whitespace": "Κενό διάστημα στην αρχή", + "login_form_err_trailing_whitespace": "Κενό διάστημα στο τέλος", + "login_form_failed_get_oauth_server_config": "Σφάλμα καταγραφής χρησιμοποιώντας το OAuth, ελέγξτε τη διεύθυνση URL του διακομιστή", + "login_form_failed_get_oauth_server_disable": "Η δυνατότητα OAuth δεν είναι διαθέσιμη σε αυτόν τον διακομιστή", + "login_form_failed_login": "Σφάλμα κατά τη σύνδεσή σας, ελέγξτε τη διεύθυνση URL του διακομιστή, το email και τον κωδικό πρόσβασης", + "login_form_handshake_exception": "Υπήρξε σφάλμα χειραψίας με τον διακομιστή. Ενεργοποιήστε την υποστήριξη αυτο-υπογεγραμμένου πιστοποιητικού στις ρυθμίσεις εάν χρησιμοποιείτε αυτο-υπογεγραμμένο πιστοποιητικό.", "login_form_label_email": "Email", - "login_form_label_password": "Password", - "login_form_next_button": "Next", - "login_form_password_hint": "password", - "login_form_save_login": "Stay logged in", - "login_form_server_empty": "Enter a server URL.", - "login_form_server_error": "Could not connect to server.", - "login_password_changed_error": "There was an error updating your password", - "login_password_changed_success": "Password updated successfully", - "map_assets_in_bound": "{} photo", - "map_assets_in_bounds": "{} photos", - "map_cannot_get_user_location": "Cannot get user's location", - "map_location_dialog_cancel": "Cancel", - "map_location_dialog_yes": "Yes", - "map_location_picker_page_use_location": "Use this location", - "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", - "map_location_service_disabled_title": "Location Service disabled", - "map_no_assets_in_bounds": "No photos in this area", - "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", - "map_no_location_permission_title": "Location Permission denied", - "map_settings_dark_mode": "Dark mode", - "map_settings_date_range_option_all": "All", - "map_settings_date_range_option_day": "Past 24 hours", - "map_settings_date_range_option_days": "Past {} days", - "map_settings_date_range_option_year": "Past year", - "map_settings_date_range_option_years": "Past {} years", - "map_settings_dialog_cancel": "Cancel", - "map_settings_dialog_save": "Save", - "map_settings_dialog_title": "Map Settings", - "map_settings_include_show_archived": "Include Archived", - "map_settings_include_show_partners": "Include Partners", - "map_settings_only_relative_range": "Date range", - "map_settings_only_show_favorites": "Show Favorite Only", - "map_settings_theme_settings": "Map Theme", - "map_zoom_to_see_photos": "Zoom out to see photos", - "memories_all_caught_up": "All caught up", - "memories_check_back_tomorrow": "Check back tomorrow for more memories", - "memories_start_over": "Start Over", - "memories_swipe_to_close": "Swipe up to close", - "memories_year_ago": "A year ago", - "memories_years_ago": "{} years ago", - "monthly_title_text_date_format": "MMMM y", - "motion_photos_page_title": "Motion Photos", - "multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping", - "multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping", - "my_albums": "My albums", - "no_assets_to_show": "No assets to show", - "no_name": "No name", - "notification_permission_dialog_cancel": "Cancel", - "notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.", - "notification_permission_dialog_settings": "Settings", - "notification_permission_list_tile_content": "Grant permission to enable notifications.", - "notification_permission_list_tile_enable_button": "Enable Notifications", - "notification_permission_list_tile_title": "Notification Permission", - "on_this_device": "On this device", - "partner_list_user_photos": "{user}'s photos", - "partner_list_view_all": "View all", + "login_form_label_password": "Κωδικός Πρόσβασης", + "login_form_next_button": "Επόμενος", + "login_form_password_hint": "κωδικός πρόσβασης", + "login_form_save_login": "Μείνετε συνδεδεμένοι", + "login_form_server_empty": "Εισαγάγετε μια διεύθυνση URL διακομιστή.", + "login_form_server_error": "Δεν ήταν δυνατή η σύνδεση με τον διακομιστή.", + "login_password_changed_error": "Παρουσιάστηκε σφάλμα κατά την ενημέρωση του κωδικού πρόσβασής σας", + "login_password_changed_success": "Ο κωδικός πρόσβασης ενημερώθηκε με επιτυχία", + "map_assets_in_bound": "{} φωτογραφία", + "map_assets_in_bounds": "{} φωτογραφίες", + "map_cannot_get_user_location": "Δεν είναι δυνατή η λήψη της τοποθεσίας του χρήστη", + "map_location_dialog_cancel": "Ακύρωση", + "map_location_dialog_yes": "Ναι", + "map_location_picker_page_use_location": "Χρησιμοποιήστε αυτήν την τοποθεσία", + "map_location_service_disabled_content": "Η υπηρεσία τοποθεσίας πρέπει να είναι ενεργοποιημένη για την εμφάνιση στοιχείων από την τρέχουσα τοποθεσία σας. Θέλετε να το ενεργοποιήσετε τώρα;", + "map_location_service_disabled_title": "Η υπηρεσία τοποθεσίας απενεργοποιήθηκε", + "map_no_assets_in_bounds": "Δεν υπάρχουν φωτογραφίες σε αυτήν την περιοχή", + "map_no_location_permission_content": "Απαιτείται άδεια τοποθεσίας για την εμφάνιση στοιχείων από την τρέχουσα τοποθεσία σας. Θέλετε να το επιτρέψετε τώρα;", + "map_no_location_permission_title": "Η άδεια τοποθεσίας απορρίφθηκε", + "map_settings_dark_mode": "Σκοτεινή λειτουργία", + "map_settings_date_range_option_all": "Όλοι", + "map_settings_date_range_option_day": "Προηγούμενες 24 ώρες", + "map_settings_date_range_option_days": "Προηγούμενες {} ημέρες", + "map_settings_date_range_option_year": "Προηγούμενο έτος", + "map_settings_date_range_option_years": "Προηγούμενα {} έτη", + "map_settings_dialog_cancel": "Ακύρωση", + "map_settings_dialog_save": "Αποθήκευση", + "map_settings_dialog_title": "Ρυθμίσεις Χάρτη", + "map_settings_include_show_archived": "Συμπεριλάβετε Αρχειοθετημένα", + "map_settings_include_show_partners": "Συμπεριλάβετε Συντρόφους", + "map_settings_only_relative_range": "Εύρος ημερομηνιών", + "map_settings_only_show_favorites": "Εμφάνιση μόνο αγαπημένων", + "map_settings_theme_settings": "Θέμα χάρτη", + "map_zoom_to_see_photos": "Σμικρύνετε για να δείτε φωτογραφίες", + "memories_all_caught_up": "Συγχρονισμένα", + "memories_check_back_tomorrow": "Ελέγξτε ξανά αύριο για περισσότερες αναμνήσεις", + "memories_start_over": "Ξεκινήστε από την αρχή", + "memories_swipe_to_close": "Σύρετε προς τα πάνω για να κλείσετε", + "memories_year_ago": "Πριν ένα χρόνο", + "memories_years_ago": "Πριν από {} χρόνια", + "monthly_title_text_date_format": "ΜΜΜΜ y", + "motion_photos_page_title": "Κινούμενες Φωτογραφίες", + "multiselect_grid_edit_date_time_err_read_only": "Δεν είναι δυνατή η επεξεργασία της ημερομηνίας των στοιχείων μόνο για ανάγνωση, παραλείπεται", + "multiselect_grid_edit_gps_err_read_only": "Δεν είναι δυνατή η επεξεργασία της τοποθεσίας των στοιχείων μόνο για ανάγνωση, παραλείπεται", + "my_albums": "Τα άλμπουμ μου", + "no_assets_to_show": "Δεν υπάρχουν στοιχεία προς εμφάνιση", + "no_name": "Κανένα όνομα", + "notification_permission_dialog_cancel": "Ακύρωση", + "notification_permission_dialog_content": "Για να ενεργοποιήσετε τις ειδοποιήσεις, μεταβείτε στις Ρυθμίσεις και επιλέξτε να επιτρέπεται.", + "notification_permission_dialog_settings": "Ρυθμίσεις", + "notification_permission_list_tile_content": "Παραχωρήστε άδεια για ενεργοποίηση ειδοποιήσεων.", + "notification_permission_list_tile_enable_button": "Ενεργοποίηση Ειδοποιήσεων", + "notification_permission_list_tile_title": "Άδεια Ειδοποίησης", + "on_this_device": "Σε αυτή τη συσκευή", + "partner_list_user_photos": "Φωτογραφίες του/της {user}", + "partner_list_view_all": "Προβολή όλων", "partner_page_add_partner": "Προσθήκη συντρόφου", "partner_page_empty_message": "Οι φωτογραφίες σας δεν διαμοιράζονται ακόμα με κανέναν.", "partner_page_no_more_users": "Δεν υπάρχουν άλλοι χρήστες για προσθήκη", @@ -385,241 +385,241 @@ "partner_page_stop_sharing_content": "Ο/Η {} δεν θα μπορεί πλέον να δει τις φωτογραφίες σας.", "partner_page_stop_sharing_title": "Θέλετε να σταματήσετε να μοιράζεστε τις φωτογραφίες σας;", "partner_page_title": "Σύντροφος", - "partners": "Partners", - "people": "People", + "partners": "Σύντροφοι", + "people": "Ανθρωποι", "permission_onboarding_back": "Πίσω", - "permission_onboarding_continue_anyway": "Continue anyway", - "permission_onboarding_get_started": "Get started", - "permission_onboarding_go_to_settings": "Go to settings", - "permission_onboarding_grant_permission": "Grant permission", - "permission_onboarding_log_out": "Log out", - "permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.", - "permission_onboarding_permission_granted": "Permission granted! You are all set.", - "permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.", - "permission_onboarding_request": "Immich requires permission to view your photos and videos.", - "places": "Places", - "preferences_settings_title": "Preferences", - "profile_drawer_app_logs": "Logs", - "profile_drawer_client_out_of_date_major": "Mobile App is out of date. Please update to the latest major version.", - "profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.", - "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date", - "profile_drawer_documentation": "Documentation", + "permission_onboarding_continue_anyway": "Συνέχεια", + "permission_onboarding_get_started": "Ξεκινήστε", + "permission_onboarding_go_to_settings": "Μεταβείτε στις ρυθμίσεις", + "permission_onboarding_grant_permission": "Χορήγηση άδειας", + "permission_onboarding_log_out": "Αποσυνδεθείτε", + "permission_onboarding_permission_denied": "Η άδεια απορρίφθηκε. Για να χρησιμοποιήσετε το Immich, παραχωρήστε δικαιώματα φωτογραφίας και βίντεο στις Ρυθμίσεις.", + "permission_onboarding_permission_granted": "Δόθηκε άδεια! Είστε έτοιμοι.", + "permission_onboarding_permission_limited": "Περιορισμένη άδεια. Για να επιτρέψετε στο Immich να δημιουργεί αντίγραφα ασφαλείας και να διαχειρίζεται ολόκληρη τη συλλογή σας, παραχωρήστε άδειες φωτογραφιών και βίντεο στις Ρυθμίσεις.", + "permission_onboarding_request": "Το Immich απαιτεί άδεια πρόσβασεις στις φωτογραφίες και τα βίντεό σας.", + "places": "Μέρη", + "preferences_settings_title": "Προτιμήσεις", + "profile_drawer_app_logs": "Καταγραφές", + "profile_drawer_client_out_of_date_major": "Παρακαλώ ενημερώστε την εφαρμογή στην πιο πρόσφατη κύρια έκδοση.", + "profile_drawer_client_out_of_date_minor": "Παρακαλώ ενημερώστε την εφαρμογή στην πιο πρόσφατη δευτερεύουσα έκδοση.", + "profile_drawer_client_server_up_to_date": "Ο πελάτης και ο διακομιστής είναι ενημερωμένοι", + "profile_drawer_documentation": "Απόδειξη με έγγραφα", "profile_drawer_github": "GitHub", - "profile_drawer_server_out_of_date_major": "Server is out of date. Please update to the latest major version.", - "profile_drawer_server_out_of_date_minor": "Server is out of date. Please update to the latest minor version.", - "profile_drawer_settings": "Settings", - "profile_drawer_sign_out": "Sign Out", - "profile_drawer_trash": "Trash", - "recently_added": "Recently added", - "recently_added_page_title": "Recently Added", - "save_to_gallery": "Save to gallery", - "scaffold_body_error_occurred": "Error occurred", - "search_albums": "Search albums", - "search_bar_hint": "Search your photos", - "search_filter_apply": "Apply filter", - "search_filter_camera": "Camera", - "search_filter_camera_make": "Make", - "search_filter_camera_model": "Model", - "search_filter_camera_title": "Select camera type", - "search_filter_date": "Date", - "search_filter_date_interval": "{start} to {end}", - "search_filter_date_title": "Select a date range", - "search_filter_display_option_archive": "Archive", - "search_filter_display_option_favorite": "Favorite", - "search_filter_display_option_not_in_album": "Not in album", - "search_filter_display_options": "Display Options", - "search_filter_display_options_title": "Display options", - "search_filter_location": "Location", - "search_filter_location_city": "City", - "search_filter_location_country": "Country", - "search_filter_location_state": "State", - "search_filter_location_title": "Select location", - "search_filter_media_type": "Media Type", - "search_filter_media_type_all": "All", - "search_filter_media_type_image": "Image", - "search_filter_media_type_title": "Select media type", - "search_filter_media_type_video": "Video", - "search_filter_people": "People", - "search_filter_people_title": "Select people", - "search_page_categories": "Categories", - "search_page_favorites": "Favorites", - "search_page_motion_photos": "Motion Photos", - "search_page_no_objects": "No Objects Info Available", - "search_page_no_places": "No Places Info Available", + "profile_drawer_server_out_of_date_major": "Παρακαλώ ενημερώστε τον διακομιστή στην πιο πρόσφατη κύρια έκδοση.", + "profile_drawer_server_out_of_date_minor": "Παρακαλώ ενημερώστε τον διακομιστή στην πιο πρόσφατη δευτερεύουσα έκδοση.", + "profile_drawer_settings": "Ρυθμίσεις", + "profile_drawer_sign_out": "Αποσύνδεση", + "profile_drawer_trash": "Σκουπίδια", + "recently_added": "Προστέθηκαν πρόσφατα", + "recently_added_page_title": "Προστέθηκαν Πρόσφατα", + "save_to_gallery": "Αποθήκευση στη συλλογή", + "scaffold_body_error_occurred": "Παρουσιάστηκε σφάλμα", + "search_albums": "Αναζήτηση άλμπουμ", + "search_bar_hint": "Αναζητήστε τις φωτογραφίες σας", + "search_filter_apply": "Εφαρμογή φίλτρου", + "search_filter_camera": "Κάμερα", + "search_filter_camera_make": "Μάρκα", + "search_filter_camera_model": "Μοντέλο", + "search_filter_camera_title": "Επιλέξτε τύπο κάμερας", + "search_filter_date": "Ημερομηνία", + "search_filter_date_interval": "{start} έως {end}", + "search_filter_date_title": "Επιλέξτε εύρος ημερομηνιών", + "search_filter_display_option_archive": "Αρχείο", + "search_filter_display_option_favorite": "Αγαπημένο", + "search_filter_display_option_not_in_album": "Όχι στο άλμπουμ", + "search_filter_display_options": "Επιλογές εμφάνισης", + "search_filter_display_options_title": "Επιλογές εμφάνισης", + "search_filter_location": "Τοποθεσία", + "search_filter_location_city": "Πόλη", + "search_filter_location_country": "Χώρα", + "search_filter_location_state": "Πολιτεία", + "search_filter_location_title": "Επιλέξτε τοποθεσία", + "search_filter_media_type": "Τύπος Μέσου", + "search_filter_media_type_all": "Όλα", + "search_filter_media_type_image": "Εικόνα", + "search_filter_media_type_title": "Επιλέξτε τύπο μέσου", + "search_filter_media_type_video": "Βίντεο", + "search_filter_people": "Ανθρωποι", + "search_filter_people_title": "Επιλέξτε άτομα", + "search_page_categories": "Κατηγορίες", + "search_page_favorites": "Αγαπημένα", + "search_page_motion_photos": "Κινούμενες Φωτογραφίες", + "search_page_no_objects": "Μη διαθέσιμες πληροφορίες αντικειμένων", + "search_page_no_places": "Μη διαθέσιμες πληροφορίες για μέρη", "search_page_people": "Άτομα", - "search_page_person_add_name_dialog_cancel": "Cancel", - "search_page_person_add_name_dialog_hint": "Name", - "search_page_person_add_name_dialog_save": "Save", - "search_page_person_add_name_dialog_title": "Add a name", - "search_page_person_add_name_subtitle": "Find them fast by name with search", - "search_page_person_add_name_title": "Add a name", - "search_page_person_edit_name": "Edit name", - "search_page_places": "Places", - "search_page_recently_added": "Recently added", - "search_page_screenshots": "Screenshots", - "search_page_selfies": "Selfies", - "search_page_things": "Things", - "search_page_videos": "Videos", - "search_page_view_all_button": "View all", - "search_page_your_activity": "Your activity", - "search_page_your_map": "Your Map", - "search_result_page_new_search_hint": "New Search", - "search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ", - "search_suggestion_list_smart_search_hint_2": "m:your-search-term", - "select_additional_user_for_sharing_page_suggestions": "Suggestions", - "select_user_for_sharing_page_err_album": "Failed to create album", - "select_user_for_sharing_page_share_suggestions": "Suggestions", - "server_info_box_app_version": "App Version", + "search_page_person_add_name_dialog_cancel": "Ακύρωση", + "search_page_person_add_name_dialog_hint": "Όνομα", + "search_page_person_add_name_dialog_save": "Αποθήκευση", + "search_page_person_add_name_dialog_title": "Προσθέστε όνομα", + "search_page_person_add_name_subtitle": "Βρείτε τα γρήγορα ονομαστικά με αναζήτηση", + "search_page_person_add_name_title": "Προσθέστε ένα όνομα", + "search_page_person_edit_name": "Επεξεργασία ονόματος", + "search_page_places": "Μέρη", + "search_page_recently_added": "Προστέθηκε πρόσφατα", + "search_page_screenshots": "Στιγμιότυπα οθόνης", + "search_page_selfies": "Σέλφι", + "search_page_things": "Πράγματα", + "search_page_videos": "Βίντεο", + "search_page_view_all_button": "Προβολή όλων", + "search_page_your_activity": "Η δραστηριότητά σας", + "search_page_your_map": "Ο χάρτης σας", + "search_result_page_new_search_hint": "Νέα Αναζήτηση", + "search_suggestion_list_smart_search_hint_1": "Η έξυπνη αναζήτηση είναι ενεργοποιημένη από προεπιλογή, για αναζήτηση μεταδεδομένων χρησιμοποιήστε το συντακτικό", + "search_suggestion_list_smart_search_hint_2": "m:όρος-αναζήτησης", + "select_additional_user_for_sharing_page_suggestions": "Προτάσεις", + "select_user_for_sharing_page_err_album": "Αποτυχία δημιουργίας άλπουμ", + "select_user_for_sharing_page_share_suggestions": "Προτάσεις", + "server_info_box_app_version": "Έκδοση εφαρμογής", "server_info_box_latest_release": "Τελευταία Έκδοση", - "server_info_box_server_url": "Server URL", - "server_info_box_server_version": "Server Version", - "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", - "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", - "setting_image_viewer_original_title": "Load original image", - "setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.", - "setting_image_viewer_preview_title": "Load preview image", - "setting_image_viewer_title": "Images", - "setting_languages_apply": "Apply", - "setting_languages_title": "Languages", - "setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}", - "setting_notifications_notify_hours": "{} hours", - "setting_notifications_notify_immediately": "immediately", - "setting_notifications_notify_minutes": "{} minutes", - "setting_notifications_notify_never": "never", - "setting_notifications_notify_seconds": "{} seconds", - "setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset", - "setting_notifications_single_progress_title": "Show background backup detail progress", - "setting_notifications_subtitle": "Adjust your notification preferences", - "setting_notifications_title": "Notifications", - "setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)", - "setting_notifications_total_progress_title": "Show background backup total progress", - "setting_pages_app_bar_settings": "Settings", - "settings_require_restart": "Please restart Immich to apply this setting", - "setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.", - "setting_video_viewer_looping_title": "Looping", - "setting_video_viewer_title": "Videos", - "share_add": "Add", - "share_add_photos": "Add photos", - "share_add_title": "Add a title", - "share_assets_selected": "{} selected", - "share_create_album": "Create album", + "server_info_box_server_url": "URL διακομιστή", + "server_info_box_server_version": "Έκδοση Διακομιστή", + "setting_image_viewer_help": "Το πρόγραμμα προβολής λεπτομερειών φορτώνει πρώτα τη μικρογραφία, στη συνέχεια φορτώνει την προεπισκόπηση μεσαίου μεγέθους (αν είναι ενεργοποιημένη), τέλος φορτώνει το πρωτότυπο (αν είναι ενεργοποιημένο).", + "setting_image_viewer_original_subtitle": "Ενεργοποιήστε τη φόρτωση της πρωτότυπης εικόνας πλήρους ανάλυσης (μεγάλη!). Απενεργοποιήστε για να μειώσετε τη χρήση δεδομένων (τόσο στο δίκτυο όσο και στην κρυφή μνήμη της συσκευής).", + "setting_image_viewer_original_title": "Φόρτωση πρωτότυπης εικόνας", + "setting_image_viewer_preview_subtitle": "Ενεργοποιήστε τη φόρτωση μιας εικόνας μέσης ανάλυσης. Απενεργοποιήστε είτε για να φορτώνεται απευθείας το πρωτότυπο είτε για να χρησιμοποιείται μόνο η μικρογραφία.", + "setting_image_viewer_preview_title": "Φόρτωση εικόνας προεπισκόπησης", + "setting_image_viewer_title": "Εικόνες", + "setting_languages_apply": "Εφαρμόζω", + "setting_languages_title": "Γλώσσες", + "setting_notifications_notify_failures_grace_period": "Ειδοποίηση αποτυχιών δημιουργίας αντιγράφων ασφαλείας στο παρασκήνιο: {}", + "setting_notifications_notify_hours": "{} ώρες", + "setting_notifications_notify_immediately": "αμέσως", + "setting_notifications_notify_minutes": "{} λεπτά", + "setting_notifications_notify_never": "ποτέ", + "setting_notifications_notify_seconds": "{} δευτερόλεπτα", + "setting_notifications_single_progress_subtitle": "Λεπτομερείς πληροφορίες προόδου μεταφόρτωσης ανά στοιχείο", + "setting_notifications_single_progress_title": "Εμφάνιση προόδου λεπτομερειών δημιουργίας αντιγράφων ασφαλείας παρασκηνίου", + "setting_notifications_subtitle": "Προσαρμόστε τις προτιμήσεις ειδοποίησης", + "setting_notifications_title": "Ειδοποιήσεις", + "setting_notifications_total_progress_subtitle": "Συνολική πρόοδος μεταφόρτωσης (ολοκληρώθηκε/σύνολο στοιχείων)", + "setting_notifications_total_progress_title": "Εμφάνιση συνολικής προόδου δημιουργίας αντιγράφων ασφαλείας παρασκηνίου", + "setting_pages_app_bar_settings": "Ρυθμίσεις", + "settings_require_restart": "Επανεκκινήστε το Immich για να εφαρμόσετε αυτήν τη ρύθμιση", + "setting_video_viewer_looping_subtitle": "Ενεργοποιήστε για το αυτόματη συνεχής επανάληψη βίντεο στο πρόγραμμα προβολής λεπτομερειών.", + "setting_video_viewer_looping_title": "Συνεχής Επανάληψη", + "setting_video_viewer_title": "Βίντεο", + "share_add": "Πρόσθεση", + "share_add_photos": "Προσθήκη φωτογραφιών", + "share_add_title": "Προσθέστε έναν ίτλο", + "share_assets_selected": "{} επιλεγμένα", + "share_create_album": "Δημιουργία άλμπουμ", "shared_album_activities_input_disable": "Το σχόλιο είναι απενεργοποιημένο", - "shared_album_activities_input_hint": "Say something", - "shared_album_activity_remove_content": "Do you want to delete this activity?", - "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activities_input_hint": "Πες κάτι", + "shared_album_activity_remove_content": "Θέλετε να διαγράψετε αυτήν τη δραστηριότητα;", + "shared_album_activity_remove_title": "Διαγραφή Δραστηριότητας", "shared_album_activity_setting_subtitle": "Επέτρεψε σε άλλους να απαντάνε", - "shared_album_activity_setting_title": "Comments & likes", - "shared_album_section_people_action_error": "Error leaving/removing from album", - "shared_album_section_people_action_leave": "Remove user from album", - "shared_album_section_people_action_remove_user": "Remove user from album", - "shared_album_section_people_owner_label": "Owner", - "shared_album_section_people_title": "PEOPLE", - "share_dialog_preparing": "Preparing...", - "shared_link_app_bar_title": "Shared Links", - "shared_link_clipboard_copied_massage": "Copied to clipboard", - "shared_link_clipboard_text": "Link: {}\nPassword: {}", - "shared_link_create_app_bar_title": "Create link to share", - "shared_link_create_error": "Error while creating shared link", - "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", - "shared_link_edit_allow_download": "Allow public user to download", - "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", - "shared_link_edit_change_expiry": "Change expiration time", - "shared_link_edit_description": "Description", - "shared_link_edit_description_hint": "Enter the share description", + "shared_album_activity_setting_title": "Σχόλια & likes", + "shared_album_section_people_action_error": "Σφάλμα αποχώρησης/κατάργησης από το άλμπουμ", + "shared_album_section_people_action_leave": "Αποχώρηση χρήστη από το άλμπουμ", + "shared_album_section_people_action_remove_user": "Κατάργηση χρήστη από το άλμπουμ", + "shared_album_section_people_owner_label": "Ιδιοκτήτης", + "shared_album_section_people_title": "ΑΝΘΡΩΠΟΙ", + "share_dialog_preparing": "Προετοιμασία...", + "shared_link_app_bar_title": "Κοινόχρηστοι Σύνδεσμοι", + "shared_link_clipboard_copied_massage": "Αντιγράφηκε στο πρόχειρο", + "shared_link_clipboard_text": "Σύνδεσμος: {}\nΚωδικός πρόσβασης: {}", + "shared_link_create_app_bar_title": "Δημιουργία συνδέσμου για κοινή χρήση", + "shared_link_create_error": "Σφάλμα κατά τη δημιουργία κοινόχρηστου συνδέσμου", + "shared_link_create_info": "Να επιτρέπεται σε οποιονδήποτε έχει τον σύνδεσμο να δει τις επιλεγμένες φωτογραφίες", + "shared_link_create_submit_button": "Δημιουργία συνδέσμου", + "shared_link_edit_allow_download": "Να επιτρέπεται η λήψη απο δημόσιο χρήστη", + "shared_link_edit_allow_upload": "Να επιτρέπεται η μεταφόρτωση απο δημόσιο χρήστη", + "shared_link_edit_app_bar_title": "Επεξεργασία συνδέσμου", + "shared_link_edit_change_expiry": "Αλλαγή χρόνου λήξης", + "shared_link_edit_description": "Περιγραφή", + "shared_link_edit_description_hint": "Εισαγάγετε την περιγραφή της κοινής χρήσης", "shared_link_edit_expire_after": "Λήξη μετά από", - "shared_link_edit_expire_after_option_day": "1 day", - "shared_link_edit_expire_after_option_days": "{} days", - "shared_link_edit_expire_after_option_hour": "1 hour", - "shared_link_edit_expire_after_option_hours": "{} hours", - "shared_link_edit_expire_after_option_minute": "1 minute", - "shared_link_edit_expire_after_option_minutes": "{} minutes", - "shared_link_edit_expire_after_option_months": "{} months", - "shared_link_edit_expire_after_option_never": "Never", - "shared_link_edit_expire_after_option_year": "{} year", - "shared_link_edit_password": "Password", - "shared_link_edit_password_hint": "Enter the share password", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", - "shared_link_empty": "You don't have any shared links", - "shared_link_error_server_url_fetch": "Cannot fetch the server url", - "shared_link_expired": "Expired", - "shared_link_expires_day": "Expires in {} day", - "shared_link_expires_days": "Expires in {} days", - "shared_link_expires_hour": "Expires in {} hour", - "shared_link_expires_hours": "Expires in {} hours", - "shared_link_expires_minute": "Expires in {} minute", - "shared_link_expires_minutes": "Expires in {} minutes", - "shared_link_expires_never": "Expires ∞", - "shared_link_expires_second": "Expires in {} second", - "shared_link_expires_seconds": "Expires in {} seconds", - "shared_link_individual_shared": "Individual shared", - "shared_link_info_chip_download": "Download", + "shared_link_edit_expire_after_option_day": "1 ημέρα", + "shared_link_edit_expire_after_option_days": "{} ημέρες", + "shared_link_edit_expire_after_option_hour": "1 ώρα", + "shared_link_edit_expire_after_option_hours": "{} ώρες", + "shared_link_edit_expire_after_option_minute": "1 λεπτό", + "shared_link_edit_expire_after_option_minutes": "{} λεπτά", + "shared_link_edit_expire_after_option_months": "{} μήνες", + "shared_link_edit_expire_after_option_never": "Ποτέ", + "shared_link_edit_expire_after_option_year": "{} έτος", + "shared_link_edit_password": "Κωδικός πρόσβασης", + "shared_link_edit_password_hint": "Εισαγάγετε τον κωδικό πρόσβασης κοινής χρήσης", + "shared_link_edit_show_meta": "Εμφάνιση μεταδεδομένων", + "shared_link_edit_submit_button": "Ενημέρωση συνδέσμου", + "shared_link_empty": "Δεν έχετε κοινόχρηστους συνδέσμους", + "shared_link_error_server_url_fetch": "Δεν είναι δυνατή η ανάκτηση του URL του διακομιστή", + "shared_link_expired": "Έληξε", + "shared_link_expires_day": "Λήγει σε {} ημέρα", + "shared_link_expires_days": "Λήγει σε {} ημέρες", + "shared_link_expires_hour": "Λήγει σε {} ώρα", + "shared_link_expires_hours": "Λήγει σε {} ώρες", + "shared_link_expires_minute": "Λήγει σε {} λεπτό", + "shared_link_expires_minutes": "Λήγει σε {} λεπτά", + "shared_link_expires_never": "Λήγει ∞", + "shared_link_expires_second": "Λήγει σε {} δευτερόλεπτο", + "shared_link_expires_seconds": "Λήγει σε {} δευτερόλεπτα", + "shared_link_individual_shared": "Μεμονωμένο κοινό", + "shared_link_info_chip_download": "Λήψη", "shared_link_info_chip_metadata": "EXIF", - "shared_link_info_chip_upload": "Upload", - "shared_link_manage_links": "Manage Shared links", - "shared_link_public_album": "Public album", - "shared_links": "Shared links", - "share_done": "Done", - "shared_with_me": "Shared with me", - "share_invite": "Invite to album", - "sharing_page_album": "Shared albums", - "sharing_page_description": "Create shared albums to share photos and videos with people in your network.", - "sharing_page_empty_list": "EMPTY LIST", - "sharing_silver_appbar_create_shared_album": "New shared album", - "sharing_silver_appbar_shared_links": "Shared links", - "sharing_silver_appbar_share_partner": "Share with partner", - "sync": "Sync", - "sync_albums": "Sync albums", - "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", - "sync_upload_album_setting_subtitle": "Create and upload your photos and videos to the selected albums on Immich", - "tab_controller_nav_library": "Library", - "tab_controller_nav_photos": "Photos", - "tab_controller_nav_search": "Search", - "tab_controller_nav_sharing": "Sharing", - "theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles", - "theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})", - "theme_setting_colorful_interface_subtitle": "Apply primary color to background surfaces.", - "theme_setting_colorful_interface_title": "Colorful interface", - "theme_setting_dark_mode_switch": "Dark mode", - "theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer", - "theme_setting_image_viewer_quality_title": "Image viewer quality", - "theme_setting_primary_color_subtitle": "Pick a color for primary actions and accents.", - "theme_setting_primary_color_title": "Primary color", - "theme_setting_system_primary_color_title": "Use system color", - "theme_setting_system_theme_switch": "Automatic (Follow system setting)", - "theme_setting_theme_subtitle": "Choose the app's theme setting", - "theme_setting_theme_title": "Theme", - "theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load", - "theme_setting_three_stage_loading_title": "Enable three-stage loading", - "translated_text_options": "Options", - "trash": "Trash", - "trash_emptied": "Emptied trash", - "trash_page_delete": "Delete", - "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_btn": "Empty trash", - "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", - "trash_page_empty_trash_dialog_ok": "Ok", - "trash_page_info": "Trashed items will be permanently deleted after {} days", - "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Restore", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_select_btn": "Select", - "trash_page_title": "Trash ({})", + "shared_link_info_chip_upload": "Μεταφόρτωση", + "shared_link_manage_links": "Διαχείριση Κοινόχρηστων Συνδέσμων", + "shared_link_public_album": "Δημόσιο άλμπουμ", + "shared_links": "Κοινόχρηστοι σύνδεσμοι", + "share_done": "Τέλος", + "shared_with_me": "Μοιρασμένα μαζί μου", + "share_invite": "Πρόσκληση σε άλμπουμ", + "sharing_page_album": "Κοινόχρηστα άλμπουμ", + "sharing_page_description": "Δημιουργήστε κοινόχρηστα άλμπουμ για να μοιράζεστε φωτογραφίες και βίντεο με άτομα στο δίκτυό σας.", + "sharing_page_empty_list": "ΚΕΝΗ ΛΙΣΤΑ", + "sharing_silver_appbar_create_shared_album": "Νέο κοινόχρηστο άλμπουμ", + "sharing_silver_appbar_shared_links": "Κοινόχρηστοι σύνδεσμοι", + "sharing_silver_appbar_share_partner": "Μοιραστείτε με τον συνεργάτη", + "sync": "Συγχρονισμός", + "sync_albums": "Συγχρονισμός άλμπουμ", + "sync_albums_manual_subtitle": "Συγχρονίστε όλα τα μεταφορτωμένα βίντεο και φωτογραφίες με τα επιλεγμένα εφεδρικά άλμπουμ", + "sync_upload_album_setting_subtitle": "Δημιουργήστε και ανεβάστε τις φωτογραφίες και τα βίντεό σας στα επιλεγμένα άλμπουμ στο Immich", + "tab_controller_nav_library": "Βιβλιοθήκη", + "tab_controller_nav_photos": "Φωτογραφίες", + "tab_controller_nav_search": "Αναζήτηση", + "tab_controller_nav_sharing": "Κοινή Χρήση", + "theme_setting_asset_list_storage_indicator_title": "Εμφάνιση ένδειξης αποθήκευσης σε πλακίδια στοιχείων", + "theme_setting_asset_list_tiles_per_row_title": "Αριθμός στοιχείων ανά σειρά ({})", + "theme_setting_colorful_interface_subtitle": "Εφαρμόστε βασικό χρώμα σε επιφάνειες φόντου.", + "theme_setting_colorful_interface_title": "Πολύχρωμη διεπαφή", + "theme_setting_dark_mode_switch": "Σκοτεινή λειτουργία", + "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_theme_title": "Θέμα", + "theme_setting_three_stage_loading_subtitle": "Η φόρτωση τριών σταδίων μπορεί να αυξήσει την απόδοση φόρτωσης, αλλά προκαλεί σημαντικά υψηλότερο φόρτο δικτύου", + "theme_setting_three_stage_loading_title": "Ενεργοποιήστε τη φόρτωση τριών σταδίων", + "translated_text_options": "Επιλογές", + "trash": "Σκουπίδια", + "trash_emptied": "Αδειάστηκαν τα σκουπίδια", + "trash_page_delete": "Διαγραφή", + "trash_page_delete_all": "Διαγραφή όλων", + "trash_page_empty_trash_btn": "Αδειάστε τα σκουπίδια", + "trash_page_empty_trash_dialog_content": "Θέλετε να αδειάσετε τα περιουσιακά σας στοιχεία στον κάδο απορριμμάτων; Αυτά τα στοιχεία θα καταργηθούν οριστικά από το Immich", + "trash_page_empty_trash_dialog_ok": "Εντάξει", + "trash_page_info": "Τα στοιχεία που έχουν απορριφθεί θα διαγραφούν οριστικά μετά από {} ημέρες", + "trash_page_no_assets": "Δεν υπάρχουν περιουσιακά στοιχεία που έχουν απορριφθεί", + "trash_page_restore": "Επαναφορά", + "trash_page_restore_all": "Επαναφορά Όλων", + "trash_page_select_assets_btn": "Επιλέξτε στοιχεία", + "trash_page_select_btn": "Επιλογή", + "trash_page_title": "Κάδος Απορριμμάτων ({})", "upload_dialog_cancel": "Ακύρωση", "upload_dialog_info": "Θέλετε να αντιγράψετε (κάνετε backup) τα επιλεγμένo(α) στοιχείο(α) στο διακομιστή;", "upload_dialog_ok": "Ανέβασμα", "upload_dialog_title": "Ανέβασμα στοιχείου", - "version_announcement_overlay_ack": "Acknowledge", - "version_announcement_overlay_release_notes": "release notes", - "version_announcement_overlay_text_1": "Hi friend, there is a new release of", - "version_announcement_overlay_text_2": "please take your time to visit the ", - "version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.", - "version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89", - "videos": "Videos", - "viewer_remove_from_stack": "Remove from Stack", - "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack" + "version_announcement_overlay_ack": "Κατάλαβα", + "version_announcement_overlay_release_notes": "σημειώσεις έκδοσης", + "version_announcement_overlay_text_1": "Γειά σας, υπάρχει μια νέα έκδοση του", + "version_announcement_overlay_text_2": "παρακαλώ αφιερώστε χρόνο να επισκεφθείτε το", + "version_announcement_overlay_text_3": " και βεβαιωθείτε ότι το docker-compose και το .env σας είναι ενημερωμένη για την αποφυγή τυχόν εσφαλμένων διαμορφώσεων, ειδικά εάν χρησιμοποιείτε το WatchTower ή οποιονδήποτε μηχανισμό που χειρίζεται την αυτόματη ενημέρωση του διακομιστή σας.", + "version_announcement_overlay_title": "Διαθέσιμη νέα έκδοση διακομιστή \uD83C\uDF89", + "videos": "Βίντεο", + "viewer_remove_from_stack": "Κατάργηση από τη Στοίβα", + "viewer_stack_use_as_main_asset": "Χρήση ως Κύριο Στοιχείο", + "viewer_unstack": "Αποστοίβαξε" } \ No newline at end of file diff --git a/mobile/assets/i18n/es-ES.json b/mobile/assets/i18n/es-ES.json index 88db7f9068eff..5f7f8a12b17df 100644 --- a/mobile/assets/i18n/es-ES.json +++ b/mobile/assets/i18n/es-ES.json @@ -6,7 +6,7 @@ "action_common_save": "Guardar", "action_common_select": "Seleccionar", "action_common_update": "Actualizar", - "add_a_name": "Add a name", + "add_a_name": "Añadir nombre", "add_to_album_bottom_sheet_added": "Agregado a {album}", "add_to_album_bottom_sheet_already_exists": "Ya se encuentra en {album}", "advanced_settings_log_level_title": "Nivel de registro: {}", @@ -22,7 +22,7 @@ "advanced_settings_troubleshooting_title": "Solución de problemas", "album_info_card_backup_album_excluded": "EXCLUIDOS", "album_info_card_backup_album_included": "INCLUIDOS", - "albums": "Albums", + "albums": "Álbumes", "album_thumbnail_card_item": "1 elemento", "album_thumbnail_card_items": "{} elementos", "album_thumbnail_card_shared": "Compartido", @@ -38,13 +38,13 @@ "album_viewer_appbar_share_remove": "Eliminar del álbum ", "album_viewer_appbar_share_to": "Compartir Con", "album_viewer_page_share_add_users": "Agregar usuarios", - "all": "All", + "all": "Todos", "all_people_page_title": "Personas", "all_videos_page_title": "Videos", "app_bar_signout_dialog_content": "¿Estás seguro que quieres cerrar sesión?", "app_bar_signout_dialog_ok": "Sí", "app_bar_signout_dialog_title": "Cerrar sesión", - "archived": "Archived", + "archived": "Archivado", "archive_page_no_archived_assets": "No se encontraron elementos archivados", "archive_page_title": "Archivo ({})", "asset_action_delete_err_read_only": "No se pueden borrar el archivo(s) de solo lectura, omitiendo", @@ -58,13 +58,13 @@ "asset_list_layout_sub_title": "Disposición", "asset_list_settings_subtitle": "Configuraciones del diseño de la cuadrícula de fotos", "asset_list_settings_title": "Cuadrícula de fotos", - "asset_restored_successfully": "Asset restored successfully", + "asset_restored_successfully": "Elementos restaurados exitosamente", "assets_deleted_permanently": "\n{} elementos(s) eliminado(s) permanentemente", "assets_deleted_permanently_from_server": "{} asset(s) deleted permanently from the Immich server", - "assets_removed_permanently_from_device": "{} asset(s) removed permanently from your device", - "assets_restored_successfully": "{} asset(s) restored successfully", + "assets_removed_permanently_from_device": "{} elemento(s) eliminado(s) permanentemente de su dispositivo", + "assets_restored_successfully": "{} elemento(s) restaurado(s) exitosamente", "assets_trashed": "{} elemento(s) eliminado(s)", - "assets_trashed_from_server": "{} asset(s) trashed from the Immich server", + "assets_trashed_from_server": "{} elemento(s) movido a la papelera en Immich", "asset_viewer_settings_title": "Visor de Archivos", "backup_album_selection_page_albums_device": "Álbumes en el dispositivo ({})", "backup_album_selection_page_albums_tap": "Toque para incluir, doble toque para excluir", @@ -189,9 +189,9 @@ "control_bottom_app_bar_unarchive": "Desarchivar", "control_bottom_app_bar_unfavorite": "Retirar favorito", "control_bottom_app_bar_upload": "Subir", - "create_album": "Create album", + "create_album": "Crear álbum", "create_album_page_untitled": "Sin título", - "create_new": "CREATE NEW", + "create_new": "Crear nuevo", "create_shared_album_page_create": "Crear", "create_shared_album_page_share": "Compartir", "create_shared_album_page_share_add_assets": "AGREGAR ELEMENTOS", @@ -216,21 +216,21 @@ "delete_shared_link_dialog_title": "Eliminar enlace compartido", "description_input_hint_text": "Agregar descripción...", "description_input_submit_error": "Error al actualizar la descripción, verifica el registro para obtener más detalles", - "download_canceled": "Download canceled", - "download_complete": "Download complete", - "download_enqueue": "Download enqueued", + "download_canceled": "Descarga cancelada", + "download_complete": "Descarga completada", + "download_enqueue": "Descarga en cola", "download_error": "Error al descargar", - "download_failed": "Download failed", - "download_filename": "file: {}", - "download_finished": "Download finished", - "downloading": "Downloading...", - "downloading_media": "Downloading media", - "download_notfound": "Download not found", - "download_paused": "Download paused", + "download_failed": "Descarga fallida", + "download_filename": "Archivo: {}", + "download_finished": "Descarga completada", + "downloading": "Descargando...", + "downloading_media": "Descargando medios", + "download_notfound": "Descarga no encontrada", + "download_paused": "Descarga en pausa", "download_started": "Descarga iniciada", "download_sucess": "Descarga Exitosa", "download_sucess_android": "Los archivos se han descargado en DCIM/Immich", - "download_waiting_to_retry": "Waiting to retry", + "download_waiting_to_retry": "Esperando para reintentar", "edit_date_time_dialog_date_time": "Fecha y Hora", "edit_date_time_dialog_timezone": "Zona horaria", "edit_image_title": "Editar", @@ -246,11 +246,11 @@ "experimental_settings_new_asset_list_title": "Habilitar cuadrícula fotográfica experimental", "experimental_settings_subtitle": "Úsalo bajo tu responsabilidad", "experimental_settings_title": "Experimental", - "favorites": "Favorites", + "favorites": "Favoritos", "favorites_page_no_favorites": "No se encontraron elementos marcados como favoritos", "favorites_page_title": "Favoritos", "filename_search": "Nombre o extensión", - "filter": "Filter", + "filter": "Filtrar", "haptic_feedback_switch": "Activar respuesta háptica", "haptic_feedback_title": "Respuesta Háptica", "header_settings_add_header_tip": "Añadir cabecera", @@ -274,8 +274,8 @@ "home_page_first_time_notice": "Si esta es la primera vez que usas la app, por favor, asegúrate de elegir un álbum de respaldo para que la línea de tiempo pueda cargar fotos y videos en los álbumes.", "home_page_share_err_local": "No se pueden compartir elementos locales a través de un enlace, omitiendo", "home_page_upload_err_limit": "Solo se pueden subir 30 elementos simultáneamente, omitiendo", - "ignore_icloud_photos": "Ignore iCloud photos", - "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server", + "ignore_icloud_photos": "Ignorar fotos de iCloud", + "ignore_icloud_photos_description": "Las fotos almacenadas en iCloud no se subirán a Immich", "image_saved_successfully": "Imágenes guardas", "image_viewer_page_state_provider_download_error": "Error de descarga", "image_viewer_page_state_provider_download_started": "Descarga Iniciada", @@ -283,7 +283,7 @@ "image_viewer_page_state_provider_share_error": "Error al compartir", "invalid_date": "Fecha incorrecta", "invalid_date_format": "Formato de fecha incorrecto", - "library": "Library", + "library": "Biblioteca", "library_page_albums": "Álbumes", "library_page_archive": "Archivo", "library_page_device_albums": "Álbumes en el dispositivo", @@ -364,7 +364,7 @@ "motion_photos_page_title": "Foto en Movimiento", "multiselect_grid_edit_date_time_err_read_only": "No se puede cambiar la fecha del archivo(s) de solo lectura, omitiendo", "multiselect_grid_edit_gps_err_read_only": "No se puede cambiar la localización de archivos de solo lectura. Saltando.", - "my_albums": "My albums", + "my_albums": "Mis álbumes", "no_assets_to_show": "No hay elementos a mostrar", "no_name": "Sin nombre", "notification_permission_dialog_cancel": "Cancelar", @@ -373,7 +373,7 @@ "notification_permission_list_tile_content": "Concede permiso para habilitar las notificaciones.", "notification_permission_list_tile_enable_button": "Permitir notificaciones", "notification_permission_list_tile_title": "Permisos de Notificacion", - "on_this_device": "On this device", + "on_this_device": "En este dispositivo", "partner_list_user_photos": "Fotos de {user}", "partner_list_view_all": "Ver todas", "partner_page_add_partner": "Agregar compañero", @@ -385,8 +385,8 @@ "partner_page_stop_sharing_content": "{} ya no podrá acceder a tus fotos", "partner_page_stop_sharing_title": "¿Dejar de compartir tus fotos?", "partner_page_title": "Compañero", - "partners": "Partners", - "people": "People", + "partners": "Colaboradores", + "people": "Personas", "permission_onboarding_back": "Volver", "permission_onboarding_continue_anyway": "Continuar de todos modos", "permission_onboarding_get_started": "Empezar", @@ -397,7 +397,7 @@ "permission_onboarding_permission_granted": "¡Permiso concedido! Todo listo.", "permission_onboarding_permission_limited": "Permiso limitado. Para permitir que Immich haga copia de seguridad y gestione toda tu colección de galería, concede permisos de fotos y videos en Configuración.", "permission_onboarding_request": "Immich requiere permiso para ver tus fotos y videos.", - "places": "Places", + "places": "Lugares", "preferences_settings_title": "Preferencias", "profile_drawer_app_logs": "Registros", "profile_drawer_client_out_of_date_major": "La app está desactualizada. Por favor actualiza a la última versión principal.", @@ -410,17 +410,17 @@ "profile_drawer_settings": "Configuración", "profile_drawer_sign_out": "Cerrar Sesión", "profile_drawer_trash": "Papelera", - "recently_added": "Recently added", + "recently_added": "Añadidos recientemente", "recently_added_page_title": "Recién Agregadas", "save_to_gallery": "Guardado en la galería", "scaffold_body_error_occurred": "Ha ocurrido un error", - "search_albums": "Search albums", + "search_albums": "Buscar álbum", "search_bar_hint": "Busca tus fotos", "search_filter_apply": "Aplicar filtros", "search_filter_camera": "Cámara", "search_filter_camera_make": "Marca", "search_filter_camera_model": "Modelo", - "search_filter_camera_title": "Select camera type", + "search_filter_camera_title": "Elige tipo de cámara", "search_filter_date": "Fecha", "search_filter_date_interval": "{start} al {end}", "search_filter_date_title": "Selecciona un intervalo de fechas", @@ -560,9 +560,9 @@ "shared_link_info_chip_upload": "Subir", "shared_link_manage_links": "Administrar enlaces compartidos", "shared_link_public_album": "Álbum público ", - "shared_links": "Shared links", + "shared_links": "Enlaces", "share_done": "Hecho", - "shared_with_me": "Shared with me", + "shared_with_me": "Compartidos conmigo", "share_invite": "Invitar al álbum", "sharing_page_album": "Álbumes compartidos", "sharing_page_description": "Crea álbumes compartidos para compartir fotos y vídeos con las personas de tu red.", @@ -594,7 +594,7 @@ "theme_setting_three_stage_loading_subtitle": "La carga en tres etapas puede aumentar el rendimiento de carga pero provoca un consumo de red significativamente mayor", "theme_setting_three_stage_loading_title": "Activar carga en tres etapas", "translated_text_options": "Opciones", - "trash": "Trash", + "trash": "Papelera", "trash_emptied": "Papelera vaciada", "trash_page_delete": "Eliminar", "trash_page_delete_all": "Eliminar todos", diff --git a/mobile/assets/i18n/fr-FR.json b/mobile/assets/i18n/fr-FR.json index 2293d9ca304bb..948e53540b807 100644 --- a/mobile/assets/i18n/fr-FR.json +++ b/mobile/assets/i18n/fr-FR.json @@ -6,10 +6,10 @@ "action_common_save": "Sauvegarder", "action_common_select": "Sélectionner", "action_common_update": "Mise à jour", - "add_a_name": "Add a name", + "add_a_name": "Ajouter un nom", "add_to_album_bottom_sheet_added": "Ajouté à {album}", "add_to_album_bottom_sheet_already_exists": "Déjà dans {album}", - "advanced_settings_log_level_title": "Log level: {}", + "advanced_settings_log_level_title": "Niveau de log : {}", "advanced_settings_prefer_remote_subtitle": "Certains appareils sont terriblement lents à charger des miniatures à partir de ressources présentes sur l'appareil. Activez ce paramètre pour charger des images distantes à la place.", "advanced_settings_prefer_remote_title": "Préférer les images distantes", "advanced_settings_proxy_headers_subtitle": "Ajoutez des en-têtes personnalisés à chaque requête réseau", @@ -38,15 +38,15 @@ "album_viewer_appbar_share_remove": "Retirer de l'album", "album_viewer_appbar_share_to": "Partager à", "album_viewer_page_share_add_users": "Ajouter des utilisateurs", - "all": "All", + "all": "Tous", "all_people_page_title": "Personnes", "all_videos_page_title": "Vidéos", "app_bar_signout_dialog_content": "Êtes-vous sûr de vouloir vous déconnecter ?", "app_bar_signout_dialog_ok": "Oui", "app_bar_signout_dialog_title": "Se déconnecter", - "archived": "Archived", + "archived": "Archivé", "archive_page_no_archived_assets": "Aucun élément archivé n'a été trouvé", - "archive_page_title": "Archive ({})", + "archive_page_title": "Archives ({})", "asset_action_delete_err_read_only": "Impossible de supprimer le(s) élément(s) en lecture seule.", "asset_action_share_err_offline": "Impossible de récupérer le(s) élément(s) hors ligne.", "asset_list_group_by_sub_title": "Regrouper par", @@ -63,7 +63,7 @@ "assets_deleted_permanently_from_server": "{} élément(s) supprimé(s) définitivement du serveur Immich", "assets_removed_permanently_from_device": "\"{} élément(s) supprimé(s) définitivement de votre appareil", "assets_restored_successfully": "Élément restauré avec succès", - "assets_trashed": "{} élément(s) déplacé(s) vers la corbeill", + "assets_trashed": "{} élément(s) déplacé(s) vers la corbeille", "assets_trashed_from_server": "{} élément(s) déplacé(s) vers la corbeille du serveur Immich", "asset_viewer_settings_title": "Visualisateur d'éléments", "backup_album_selection_page_albums_device": "Albums sur l'appareil ({})", @@ -122,7 +122,7 @@ "backup_controller_page_total_sub": "Toutes les photos et vidéos uniques des albums sélectionnés", "backup_controller_page_turn_off": "Désactiver la sauvegarde", "backup_controller_page_turn_on": "Activer la sauvegarde", - "backup_controller_page_uploading_file_info": "Transfert des informations du fichier", + "backup_controller_page_uploading_file_info": "Transfert du fichier", "backup_err_only_album": "Impossible de retirer le seul album", "backup_info_card_assets": "éléments", "backup_manual_cancelled": "Annulé", @@ -172,7 +172,7 @@ "control_bottom_app_bar_add_to_album": "Ajouter à l'album", "control_bottom_app_bar_album_info": "{} éléments", "control_bottom_app_bar_album_info_shared": "{} éléments - Partagés", - "control_bottom_app_bar_archive": "Archive", + "control_bottom_app_bar_archive": "Archiver", "control_bottom_app_bar_create_new_album": "Créer un nouvel album", "control_bottom_app_bar_delete": "Supprimer", "control_bottom_app_bar_delete_from_immich": "Supprimer de Immich", @@ -189,14 +189,14 @@ "control_bottom_app_bar_unarchive": "Désarchiver", "control_bottom_app_bar_unfavorite": "Enlever des favoris", "control_bottom_app_bar_upload": "Téléverser", - "create_album": "Create album", + "create_album": "Créer l'album", "create_album_page_untitled": "Sans titre", - "create_new": "CREATE NEW", + "create_new": "NOUVEAU", "create_shared_album_page_create": "Créer", "create_shared_album_page_share": "Partager", "create_shared_album_page_share_add_assets": "AJOUTER DES ÉLÉMENTS", "create_shared_album_page_share_select_photos": "Sélectionner les photos", - "crop": "Crop", + "crop": "Recadrer", "curated_location_page_title": "Lieux", "curated_object_page_title": "Objets", "daily_title_text_date": "E, dd MMM", @@ -216,41 +216,41 @@ "delete_shared_link_dialog_title": "Supprimer le lien partagé", "description_input_hint_text": "Ajouter une description…", "description_input_submit_error": "Erreur de mise à jour de la description, vérifier le journal pour plus de détails", - "download_canceled": "Download canceled", - "download_complete": "Download complete", - "download_enqueue": "Download enqueued", - "download_error": "Download Error", - "download_failed": "Download failed", - "download_filename": "file: {}", - "download_finished": "Download finished", - "downloading": "Downloading...", - "downloading_media": "Downloading media", - "download_notfound": "Download not found", - "download_paused": "Download paused", - "download_started": "Download started", - "download_sucess": "Download success", - "download_sucess_android": "The media has been downloaded to DCIM/Immich", - "download_waiting_to_retry": "Waiting to retry", + "download_canceled": "Téléchargement annulé", + "download_complete": "Téléchargement terminé", + "download_enqueue": "Téléchargement en attente", + "download_error": "Erreur de téléchargement", + "download_failed": "Téléchargement échoué", + "download_filename": "fichier : {}", + "download_finished": "Téléchargement terminé", + "downloading": "Téléchargement...", + "downloading_media": "Téléchargement du média", + "download_notfound": "Téléchargement non trouvé", + "download_paused": "Téléchargement en pause", + "download_started": "Téléchargement commencé", + "download_sucess": "Téléchargement réussi", + "download_sucess_android": "Le média a été téléchargé dans DCIM/Immich", + "download_waiting_to_retry": "Téléchargement en attente du prochain essai", "edit_date_time_dialog_date_time": "Date et heure", "edit_date_time_dialog_timezone": "Fuseau horaire", - "edit_image_title": "Edit", + "edit_image_title": "Modifier", "edit_location_dialog_title": "Localisation", - "error_saving_image": "Error: {}", + "error_saving_image": "Erreur : {}", "exif_bottom_sheet_description": "Ajouter une description…", "exif_bottom_sheet_details": "DÉTAILS", "exif_bottom_sheet_location": "LOCALISATION", - "exif_bottom_sheet_location_add": "Add a location", + "exif_bottom_sheet_location_add": "Ajouter un lieu", "exif_bottom_sheet_people": "PERSONNES", "exif_bottom_sheet_person_add_person": "Ajouter un nom", "experimental_settings_new_asset_list_subtitle": "En cours de développement", "experimental_settings_new_asset_list_title": "Activer la grille de photos expérimentale", "experimental_settings_subtitle": "Utilisez à vos dépends !", "experimental_settings_title": "Expérimental", - "favorites": "Favorites", + "favorites": "Favoris", "favorites_page_no_favorites": "Aucun élément favori n'a été trouvé", "favorites_page_title": "Favoris", "filename_search": "Nom de fichier ou extension", - "filter": "Filter", + "filter": "Filtres", "haptic_feedback_switch": "Activer le retour haptique", "haptic_feedback_title": "Retour haptique", "header_settings_add_header_tip": "Ajouter un en-tête", @@ -274,23 +274,23 @@ "home_page_first_time_notice": "Si c'est la première fois que vous utilisez l'application, veillez à choisir un ou plusieurs albums de sauvegarde afin que la chronologie puisse alimenter les photos et les vidéos de cet ou ces albums.", "home_page_share_err_local": "Impossible de partager par lien les médias locaux, cette opération est donc ignorée.", "home_page_upload_err_limit": "Limite de téléchargement de 30 éléments en même temps, demande ignorée", - "ignore_icloud_photos": "Ignore iCloud photos", - "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server", - "image_saved_successfully": "Image saved", + "ignore_icloud_photos": "Ignorer les photos iCloud", + "ignore_icloud_photos_description": "Les photos stockées sur iCloud ne sont pas enregistrées sur Immich", + "image_saved_successfully": "Image enregistré", "image_viewer_page_state_provider_download_error": "Erreur de téléchargement", "image_viewer_page_state_provider_download_started": "Téléchargement démarré", "image_viewer_page_state_provider_download_success": "Téléchargement réussi", "image_viewer_page_state_provider_share_error": "Erreur de partage", "invalid_date": "Date invalide", "invalid_date_format": "Format de date invalide", - "library": "Library", + "library": "Bibliothèque", "library_page_albums": "Albums", "library_page_archive": "Archive", "library_page_device_albums": "Albums sur l'appareil", "library_page_favorites": "Favoris", "library_page_new_album": "Nouvel album", "library_page_sharing": "Partage", - "library_page_sort_asset_count": "Number of assets", + "library_page_sort_asset_count": "Nombre d'éléments", "library_page_sort_created": "Créations les plus récentes", "library_page_sort_last_modified": "Dernière modification", "library_page_sort_most_oldest_photo": "Photo la plus ancienne", @@ -364,7 +364,7 @@ "motion_photos_page_title": "Photos avec mouvement", "multiselect_grid_edit_date_time_err_read_only": "Impossible de modifier la date d'un élément d'actif en lecture seule.", "multiselect_grid_edit_gps_err_read_only": "Impossible de modifier l'emplacement d'un élément en lecture seule.", - "my_albums": "My albums", + "my_albums": "Mes albums", "no_assets_to_show": "Aucuns éléments à afficher", "no_name": "Sans nom", "notification_permission_dialog_cancel": "Annuler", @@ -373,7 +373,7 @@ "notification_permission_list_tile_content": "Accordez la permission d'activer les notifications.", "notification_permission_list_tile_enable_button": "Activer les notifications", "notification_permission_list_tile_title": "Permission de notification", - "on_this_device": "On this device", + "on_this_device": "Sur cet appareil", "partner_list_user_photos": "Photos de {user}", "partner_list_view_all": "Voir tous", "partner_page_add_partner": "Ajouter un partenaire", @@ -385,8 +385,8 @@ "partner_page_stop_sharing_content": "{} ne pourra plus accéder à vos photos.", "partner_page_stop_sharing_title": "Arrêter de partager vos photos ?", "partner_page_title": "Partenaire", - "partners": "Partners", - "people": "People", + "partners": "Partenaires", + "people": "Personnes", "permission_onboarding_back": "Retour", "permission_onboarding_continue_anyway": "Continuer quand même", "permission_onboarding_get_started": "Commencer", @@ -397,7 +397,7 @@ "permission_onboarding_permission_granted": "Permission accordée ! Vous êtes prêts.", "permission_onboarding_permission_limited": "Permission limitée. Pour permettre à Immich de sauvegarder et de gérer l'ensemble de votre bibliothèque, accordez l'autorisation pour les photos et vidéos dans les Paramètres.", "permission_onboarding_request": "Immich demande l'autorisation de visionner vos photos et vidéo", - "places": "Places", + "places": "Lieux", "preferences_settings_title": "Préférences", "profile_drawer_app_logs": "Journaux", "profile_drawer_client_out_of_date_major": "L'application mobile est obsolète. Veuillez effectuer la mise à jour vers la dernière version majeure.", @@ -410,21 +410,21 @@ "profile_drawer_settings": "Paramètres", "profile_drawer_sign_out": "Se déconnecter", "profile_drawer_trash": "Corbeille", - "recently_added": "Recently added", + "recently_added": "Ajouté récemment", "recently_added_page_title": "Récemment ajouté", - "save_to_gallery": "Save to gallery", + "save_to_gallery": "Enregistrer", "scaffold_body_error_occurred": "Une erreur s'est produite", - "search_albums": "Search albums", + "search_albums": "Rechercher des albums", "search_bar_hint": "Rechercher vos photos", "search_filter_apply": "Appliquer le filtre", "search_filter_camera": "Appareil", "search_filter_camera_make": "Fabricant", - "search_filter_camera_model": "Modéle", + "search_filter_camera_model": "Modèle", "search_filter_camera_title": "Sélectionner le type d'appareil", "search_filter_date": "Date", "search_filter_date_interval": "{start} à {end}", "search_filter_date_title": "Sélectionner une période", - "search_filter_display_option_archive": "Archive", + "search_filter_display_option_archive": "Archivé", "search_filter_display_option_favorite": "Favoris", "search_filter_display_option_not_in_album": "Pas dans un album", "search_filter_display_options": "Options d'affichage", @@ -560,9 +560,9 @@ "shared_link_info_chip_upload": "Chargement", "shared_link_manage_links": "Gérer les liens partagés", "shared_link_public_album": "Album public", - "shared_links": "Shared links", + "shared_links": "Liens partagés", "share_done": "Fait", - "shared_with_me": "Shared with me", + "shared_with_me": "Partagé avec moi", "share_invite": "Inviter à l'album", "sharing_page_album": "Albums partagés", "sharing_page_description": "Créez des albums partagés pour partager des photos et des vidéos avec les personnes de votre réseau.", @@ -570,10 +570,10 @@ "sharing_silver_appbar_create_shared_album": "Créer un album partagé", "sharing_silver_appbar_shared_links": "Liens partagés", "sharing_silver_appbar_share_partner": "Partager avec un partenaire", - "sync": "Sync", - "sync_albums": "Sync albums", - "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", - "sync_upload_album_setting_subtitle": "Create and upload your photos and videos to the selected albums on Immich", + "sync": "Synchroniser", + "sync_albums": "Synchroniser dans des albums", + "sync_albums_manual_subtitle": "Synchroniser toutes les vidéos et photos sauvegardées dans les albums sélectionnés", + "sync_upload_album_setting_subtitle": "Créer et sauvegarde vos photos et vidéos dans les albums sélectionnés sur Immich", "tab_controller_nav_library": "Bibliothèque", "tab_controller_nav_photos": "Photos", "tab_controller_nav_search": "Recherche", @@ -594,7 +594,7 @@ "theme_setting_three_stage_loading_subtitle": "Le chargement en trois étapes peut améliorer les performances de chargement, mais entraîne une augmentation significative de la charge du réseau.", "theme_setting_three_stage_loading_title": "Activer le chargement en trois étapes", "translated_text_options": "Options", - "trash": "Trash", + "trash": "Corbeille", "trash_emptied": "Corbeille vidée", "trash_page_delete": "Supprimer", "trash_page_delete_all": "Tout supprimer", @@ -618,7 +618,7 @@ "version_announcement_overlay_text_2": "veuillez prendre le temps de visiter le ", "version_announcement_overlay_text_3": " et assurez-vous que votre configuration docker-compose et .env est à jour pour éviter toute erreur de configuration, en particulier si vous utilisez WatchTower ou tout autre mécanisme qui gère la mise à jour automatique de votre application serveur.", "version_announcement_overlay_title": "Nouvelle version serveur disponible \uD83C\uDF89", - "videos": "Videos", + "videos": "Vidéos", "viewer_remove_from_stack": "Retirer de la pile", "viewer_stack_use_as_main_asset": "Utiliser comme élément principal", "viewer_unstack": "Désempiler" diff --git a/mobile/assets/i18n/he-IL.json b/mobile/assets/i18n/he-IL.json index a7b14d2b74dd9..c0bfb7367b887 100644 --- a/mobile/assets/i18n/he-IL.json +++ b/mobile/assets/i18n/he-IL.json @@ -6,7 +6,7 @@ "action_common_save": "שמור", "action_common_select": "בחר", "action_common_update": "עדכון", - "add_a_name": "Add a name", + "add_a_name": "הוסף שם", "add_to_album_bottom_sheet_added": "נוסף ל {album}", "add_to_album_bottom_sheet_already_exists": "כבר ב {album}", "advanced_settings_log_level_title": "רמת תיעוד אירועים: {}", @@ -22,7 +22,7 @@ "advanced_settings_troubleshooting_title": "פתרון בעיות", "album_info_card_backup_album_excluded": "הוחרגו", "album_info_card_backup_album_included": "נכללו", - "albums": "Albums", + "albums": "אלבומים", "album_thumbnail_card_item": "פריט 1", "album_thumbnail_card_items": "{} פריטים", "album_thumbnail_card_shared": " · משותף", @@ -38,13 +38,13 @@ "album_viewer_appbar_share_remove": "הסרה מאלבום", "album_viewer_appbar_share_to": "שתף עם", "album_viewer_page_share_add_users": "הוסף משתמשים", - "all": "All", + "all": "הכל", "all_people_page_title": "אנשים", "all_videos_page_title": "סרטונים", "app_bar_signout_dialog_content": "האם את/ה בטוח/ה שברצונך להתנתק?", "app_bar_signout_dialog_ok": "כן", "app_bar_signout_dialog_title": "התנתק", - "archived": "Archived", + "archived": "בארכיון", "archive_page_no_archived_assets": "לא נמצאו נכסים בארכיון", "archive_page_title": "ארכיון ({})", "asset_action_delete_err_read_only": "לא ניתן למחוק נכס(ים) לקריאה בלבד, מדלג", @@ -189,9 +189,9 @@ "control_bottom_app_bar_unarchive": "הוצא מארכיון", "control_bottom_app_bar_unfavorite": "הסר ממועדפים", "control_bottom_app_bar_upload": "העלאה", - "create_album": "Create album", + "create_album": "צור אלבום", "create_album_page_untitled": "ללא כותרת", - "create_new": "CREATE NEW", + "create_new": "צור חדש", "create_shared_album_page_create": "יצירה", "create_shared_album_page_share": "שתף", "create_shared_album_page_share_add_assets": "הוסף נכסים", @@ -246,11 +246,11 @@ "experimental_settings_new_asset_list_title": "אפשר רשת תמונות ניסיונית", "experimental_settings_subtitle": "השימוש הוא על אחריותך בלבד!", "experimental_settings_title": "נסיוני", - "favorites": "Favorites", + "favorites": "מועדפים", "favorites_page_no_favorites": "לא נמצאו נכסים מועדפים", "favorites_page_title": "מועדפים", "filename_search": "שם קובץ או סיומת", - "filter": "Filter", + "filter": "סנן", "haptic_feedback_switch": "אפשר משוב ברטט", "haptic_feedback_title": "משוב ברטט", "header_settings_add_header_tip": "הוסף כותרת", @@ -274,8 +274,8 @@ "home_page_first_time_notice": "אם זאת הפעם הראשונה שאת/ה משתמש/ת ביישום, נא להקפיד לבחור אלבומ(ים) לגיבוי כך שציר הזמן יוכל לאכלס תמונות וסרטונים באלבומ(ים)", "home_page_share_err_local": "לא ניתן לשתף נכסים מקומיים על ידי קישור, מדלג", "home_page_upload_err_limit": "ניתן להעלות רק מקסימום של 30 נכסים בכל פעם, מדלג", - "ignore_icloud_photos": "Ignore iCloud photos", - "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server", + "ignore_icloud_photos": "התעלם מתמונות iCloud", + "ignore_icloud_photos_description": "תמונות שמאוחסנות ב-iCloud לא יועלו לשרת ה-Immich", "image_saved_successfully": "תמונה נשמרה", "image_viewer_page_state_provider_download_error": "שגיאת הורדה", "image_viewer_page_state_provider_download_started": "ההורדה החלה", @@ -283,7 +283,7 @@ "image_viewer_page_state_provider_share_error": "שיתוף שגיאה", "invalid_date": "תאריך לא תקין", "invalid_date_format": "פורמט תאריך לא תקין", - "library": "Library", + "library": "ספרייה", "library_page_albums": "אלבומים", "library_page_archive": "ארכיון", "library_page_device_albums": "אלבומים במכשיר", @@ -364,7 +364,7 @@ "motion_photos_page_title": "תמונות עם תנועה", "multiselect_grid_edit_date_time_err_read_only": "לא ניתן לערוך תאריך של נכס(ים) לקריאה בלבד, מדלג", "multiselect_grid_edit_gps_err_read_only": "לא ניתן לערוך מיקום של נכס(ים) לקריאה בלבד, מדלג", - "my_albums": "My albums", + "my_albums": "האלבומים שלי", "no_assets_to_show": "אין נכסים להציג", "no_name": "ללא שם", "notification_permission_dialog_cancel": "ביטול", @@ -373,7 +373,7 @@ "notification_permission_list_tile_content": "הענק הרשאה כדי לאפשר התראות", "notification_permission_list_tile_enable_button": "אפשר התראות", "notification_permission_list_tile_title": "הרשאת התראה", - "on_this_device": "On this device", + "on_this_device": "במכשיר הזה", "partner_list_user_photos": "תמונות של {user}", "partner_list_view_all": "הצג הכל", "partner_page_add_partner": "הוספת שותף", @@ -385,8 +385,8 @@ "partner_page_stop_sharing_content": "{} לא יוכל יותר לגשת לתמונות שלך", "partner_page_stop_sharing_title": "להפסיק לשתף את התמונות שלך?", "partner_page_title": "שותף", - "partners": "Partners", - "people": "People", + "partners": "שותפים", + "people": "אנשים", "permission_onboarding_back": "חזרה", "permission_onboarding_continue_anyway": "המשך בכל זאת", "permission_onboarding_get_started": "להתחיל", @@ -397,7 +397,7 @@ "permission_onboarding_permission_granted": "ההרשאה ניתנה! את/ה מוכנ/ה", "permission_onboarding_permission_limited": "הרשאה מוגבלת. כדי לתת ליישום לגבות ולנהל את כל אוסף הגלריה שלך, הענק הרשאה לתמונות וסרטונים בהגדרות", "permission_onboarding_request": "היישום דורש הרשאה כדי לראות את התמונות והסרטונים שלך", - "places": "Places", + "places": "מקומות", "preferences_settings_title": "העדפות", "profile_drawer_app_logs": "יומן", "profile_drawer_client_out_of_date_major": "האפליקציה לנייד היא מיושנת. נא לעדכן לגרסה הראשית האחרונה", @@ -410,11 +410,11 @@ "profile_drawer_settings": "הגדרות", "profile_drawer_sign_out": "יציאה", "profile_drawer_trash": "אשפה", - "recently_added": "Recently added", + "recently_added": "נוסף לאחרונה", "recently_added_page_title": "נוסף לאחרונה", "save_to_gallery": "שמור לגלריה", "scaffold_body_error_occurred": "אירעה שגיאה", - "search_albums": "Search albums", + "search_albums": "חפש/י אלבומים", "search_bar_hint": "חפש/י בתמונות שלך", "search_filter_apply": "החל סינון", "search_filter_camera": "מצלמה", @@ -560,9 +560,9 @@ "shared_link_info_chip_upload": "העלאה", "shared_link_manage_links": "ניהול קישורים משותפים", "shared_link_public_album": "אלבום ציבורי", - "shared_links": "Shared links", + "shared_links": "קישורים משותפים", "share_done": "סיום", - "shared_with_me": "Shared with me", + "shared_with_me": "משותף איתי", "share_invite": "הזמן לאלבום", "sharing_page_album": "אלבומים משותפים", "sharing_page_description": "צור אלבומים משותפים כדי לשתף תמונות וסרטונים עם אנשים ברשת שלך", @@ -594,7 +594,7 @@ "theme_setting_three_stage_loading_subtitle": "טעינה בשלושה שלבים עשויה לשפר את ביצועי הטעינה אבל גורמת באופן משמעותי לעומס רשת גבוה יותר", "theme_setting_three_stage_loading_title": "אפשר טעינה בשלושה שלבים", "translated_text_options": "אפשרויות", - "trash": "Trash", + "trash": "אשפה", "trash_emptied": "האשפה רוקנה", "trash_page_delete": "מחק", "trash_page_delete_all": "מחק הכל", @@ -618,7 +618,7 @@ "version_announcement_overlay_text_2": "אנא קח/י את הזמן שלך לבקר ב ", "version_announcement_overlay_text_3": " ולוודא שמבנה ה docker-compose וה env. שלך עדכני כדי למנוע תצורות שגויות, במיוחד אם את/ה משתמש/ת ב WatchTower או בכל מנגנון שמטפל בעדכון יישום השרת שלך באופן אוטומטי", "version_announcement_overlay_title": "גרסת שרת חדשה זמינה \uD83C\uDF89", - "videos": "Videos", + "videos": "סרטונים", "viewer_remove_from_stack": "הסר מערימה", "viewer_stack_use_as_main_asset": "השתמש כנכס ראשי", "viewer_unstack": "ביטול ערימה" diff --git a/mobile/assets/i18n/hu-HU.json b/mobile/assets/i18n/hu-HU.json index e28535f9b15f1..a19263e2bcb30 100644 --- a/mobile/assets/i18n/hu-HU.json +++ b/mobile/assets/i18n/hu-HU.json @@ -1,19 +1,19 @@ { "action_common_back": "Vissza", "action_common_cancel": "Mégsem", - "action_common_clear": "Kitöröl", + "action_common_clear": "Alaphelyzetbe állít", "action_common_confirm": "Jóváhagy", - "action_common_save": "Save", - "action_common_select": "Select", + "action_common_save": "Mentés", + "action_common_select": "Kiválaszt", "action_common_update": "Frissít", - "add_a_name": "Add a name", + "add_a_name": "Név hozzáadása", "add_to_album_bottom_sheet_added": "Hozzáadva a(z) \"{album}\" albumhoz", "add_to_album_bottom_sheet_already_exists": "Már benne van a(z) \"{album}\" albumban", "advanced_settings_log_level_title": "Naplózás szintje: {}", - "advanced_settings_prefer_remote_subtitle": "Néhány eszköz fájdalmasan lassan tölti be az eszközön lévő bélyegképeket. Ezzel a beállítással inkább a távoli képeket töltjük be helyette.", + "advanced_settings_prefer_remote_subtitle": "Néhány eszköz fájdalmasan lassan tölti be az eszközön lévő bélyegképeket. Ez a beállítás inkább a távoli képeket tölti be helyettük.", "advanced_settings_prefer_remote_title": "Távoli képek előnyben részesítése", - "advanced_settings_proxy_headers_subtitle": "Define proxy headers Immich should send with each network request", - "advanced_settings_proxy_headers_title": "Proxy Headers", + "advanced_settings_proxy_headers_subtitle": "Add meg azokat a proxy fejléceket, amiket az app elküldjön minden hálózati kérésnél", + "advanced_settings_proxy_headers_title": "Proxy Fejlécek", "advanced_settings_self_signed_ssl_subtitle": "Nem ellenőrzi a szerver SSL tanúsítványát. Önaláírt tanúsítvány esetén szükséges beállítás.", "advanced_settings_self_signed_ssl_title": "Önaláírt SSL tanúsítványok engedélyezése", "advanced_settings_tile_subtitle": "Haladó felhasználói beállítások", @@ -22,7 +22,7 @@ "advanced_settings_troubleshooting_title": "Hibaelhárítás", "album_info_card_backup_album_excluded": "KIHAGYVA", "album_info_card_backup_album_included": "BELEÉRTVE", - "albums": "Albums", + "albums": "Albumok", "album_thumbnail_card_item": "1 elem", "album_thumbnail_card_items": "{} elem", "album_thumbnail_card_shared": "· Megosztott", @@ -30,56 +30,56 @@ "album_thumbnail_shared_by": "Megosztotta: {}", "album_viewer_appbar_delete_confirm": "Biztos, hogy törölni szeretnéd ezt az albumot?", "album_viewer_appbar_share_delete": "Album törlése", - "album_viewer_appbar_share_err_delete": "Nem sikerült törölni az albumot", + "album_viewer_appbar_share_err_delete": "Az album törlése sikertelen", "album_viewer_appbar_share_err_leave": "Nem sikerült kilépni az albumból", "album_viewer_appbar_share_err_remove": "Néhány elemet nem sikerült törölni az albumból", - "album_viewer_appbar_share_err_title": "Nem sikerült átnevezni az albumot", + "album_viewer_appbar_share_err_title": "Az album átnevezése sikertelen", "album_viewer_appbar_share_leave": "Kilépés az albumból", "album_viewer_appbar_share_remove": "Eltávolítás az albumból", "album_viewer_appbar_share_to": "Megosztás Ide", "album_viewer_page_share_add_users": "Felhasználók hozzáadása", - "all": "All", + "all": "Összes", "all_people_page_title": "Emberek", "all_videos_page_title": "Videók", "app_bar_signout_dialog_content": "Biztos, hogy ki szeretnél jelentkezni?", "app_bar_signout_dialog_ok": "Igen", "app_bar_signout_dialog_title": "Kijelentkezés", - "archived": "Archived", + "archived": "Archivált", "archive_page_no_archived_assets": "Nem található archivált elem", "archive_page_title": "Archívum ({})", "asset_action_delete_err_read_only": "Csak-olvasható elem(ek)et nem lehet törölni, így ezeket átugorjuk", - "asset_action_share_err_offline": "Nem sikerült betölteni a kapcsolat nélküli elem(ek)et, így ezeket kihagyjuk", + "asset_action_share_err_offline": "Nem lehet betölteni a kapcsolat nélküli elem(ek)et, így ezeket kihagyjuk", "asset_list_group_by_sub_title": "Csoportosítás", "asset_list_layout_settings_dynamic_layout_title": "Dinamikus elrendezés", - "asset_list_layout_settings_group_automatically": "Automatikus", + "asset_list_layout_settings_group_automatically": "Automatikusan", "asset_list_layout_settings_group_by": "Elemek csoportosítása", - "asset_list_layout_settings_group_by_month": "hónapok szerint", - "asset_list_layout_settings_group_by_month_day": "hónap és nap szerint", + "asset_list_layout_settings_group_by_month": "Hónapok szerint", + "asset_list_layout_settings_group_by_month_day": "Hónapok és napok szerint", "asset_list_layout_sub_title": "Elrendezés", "asset_list_settings_subtitle": "Fotórács elrendezése", "asset_list_settings_title": "Fotórács", - "asset_restored_successfully": "Asset restored successfully", - "assets_deleted_permanently": "{} asset(s) deleted permanently", - "assets_deleted_permanently_from_server": "{} asset(s) deleted permanently from the Immich server", - "assets_removed_permanently_from_device": "{} asset(s) removed permanently from your device", - "assets_restored_successfully": "{} asset(s) restored successfully", - "assets_trashed": "{} asset(s) trashed", - "assets_trashed_from_server": "{} asset(s) trashed from the Immich server", + "asset_restored_successfully": "Elem sikeresen helyreállítva", + "assets_deleted_permanently": "{} elem véglegesen törölve", + "assets_deleted_permanently_from_server": "{} elem véglegesen törölve az Immich szerverről", + "assets_removed_permanently_from_device": "{} elem véglegesen törölve az eszközödről", + "assets_restored_successfully": "{} elem sikeresen helyreállítva", + "assets_trashed": "{} elem lomtárba helyezve", + "assets_trashed_from_server": "{} elem lomtárba helyezve az Immich szerveren", "asset_viewer_settings_title": "Elem Megjelenítő", "backup_album_selection_page_albums_device": "Ezen az eszközön lévő albumok ({})", - "backup_album_selection_page_albums_tap": "Koppincs a hozzáadáshoz, duplán koppincs az eltávolításhoz", + "backup_album_selection_page_albums_tap": "Koppints a hozzáadáshoz, duplán koppints az eltávolításhoz", "backup_album_selection_page_assets_scatter": "Egy elem több albumban is lehet. Ezért a mentéshez albumokat lehet hozzáadni vagy azokat a mentésből kihagyni.", "backup_album_selection_page_select_albums": "Válassz albumokat", "backup_album_selection_page_selection_info": "Összegzés", "backup_album_selection_page_total_assets": "Összes egyedi elem", "backup_all": "Összes", - "backup_background_service_backup_failed_message": "Hiba a mentés közben. Újrapróbálkozás...", - "backup_background_service_connection_failed_message": "Hiba a szerverhez való csatlakozás közben. Újrapróbálkozás...", + "backup_background_service_backup_failed_message": "Az elemek mentése sikertelen. Újrapróbálkozás...", + "backup_background_service_connection_failed_message": "A szerverhez csatlakozás sikertelen. Újrapróbálkozás...", "backup_background_service_current_upload_notification": "Feltöltés {}", - "backup_background_service_default_notification": "Új elemek keresése...", - "backup_background_service_error_title": "Hiba mentés közben", + "backup_background_service_default_notification": "Új elemek ellenőrzése...", + "backup_background_service_error_title": "Hiba a mentés közben", "backup_background_service_in_progress_notification": "Elemek mentése folyamatban…", - "backup_background_service_upload_failure_notification": "Hiba a feltöltés közben {}", + "backup_background_service_upload_failure_notification": "A feltöltés sikertelen {}", "backup_controller_page_albums": "Albumok Mentése", "backup_controller_page_background_app_refresh_disabled_content": "Engedélyezd a háttérben történő frissítést a Beállítások > Általános > Háttérben Frissítés menüpontban.", "backup_controller_page_background_app_refresh_disabled_title": "Háttérben frissítés kikapcsolva", @@ -89,7 +89,7 @@ "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "Akkumulátor optimalizálás", "backup_controller_page_background_charging": "Csak töltés közben", - "backup_controller_page_background_configure_error": "Nem sikerült beállítani a háttér szolgáltatást", + "backup_controller_page_background_configure_error": "A háttérszolgáltatás beállítása sikertelen", "backup_controller_page_background_delay": "Új elemek mentésének késleltetése: {}", "backup_controller_page_background_description": "Kapcsold be a háttérfolyamatot, hogy automatikusan mentsen elemeket az applikáció megnyitása nélkül", "backup_controller_page_background_is_off": "Automatikus mentés a háttérben ki van kapcsolva", @@ -102,7 +102,7 @@ "backup_controller_page_backup_sub": "Mentett fotók és videók", "backup_controller_page_cancel": "Mégsem", "backup_controller_page_created": "Létrehozva: {}", - "backup_controller_page_desc_backup": "Ha engedélyezed az előtérben mentést, akkor az új elemek automatikusan feltöltődnek a szerverre, amikor megyitod az alkalmazást.", + "backup_controller_page_desc_backup": "Ha bekapcsolod az előtérben mentést, akkor az új elemek automatikusan feltöltődnek a szerverre, amikor megyitod az alkalmazást.", "backup_controller_page_excluded": "Kivéve:", "backup_controller_page_failed": "Sikertelen ({})", "backup_controller_page_filename": "Fájlnév: {}[{}]", @@ -153,32 +153,32 @@ "change_password_form_description": "Szia {name}!\n\nMost jelentkezel be először a rendszerbe vagy más okból szükséges a jelszavad meváltoztatása. Kérjük, add meg új jelszavad.", "change_password_form_new_password": "Új Jelszó", "change_password_form_password_mismatch": "A beírt jelszavak nem egyeznek", - "change_password_form_reenter_new_password": "Jelszó (még egyszer)", + "change_password_form_reenter_new_password": "Jelszó (Még Egyszer)", "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Enter Password", - "client_cert_import": "Import", - "client_cert_import_success_msg": "Client certificate is imported", - "client_cert_invalid_msg": "Invalid certificate file or wrong password", - "client_cert_remove": "Remove", - "client_cert_remove_msg": "Client certificate is removed", - "client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate Import/Remove is available only before login", - "client_cert_title": "SSL Client Certificate", + "client_cert_enter_password": "Jelszó Megadása", + "client_cert_import": "Importálás", + "client_cert_import_success_msg": "Kliens tanúsítvány importálva", + "client_cert_invalid_msg": "Érvénytelen tanúsítvány fájl vagy hibás jelszó", + "client_cert_remove": "Eltávolítás", + "client_cert_remove_msg": "Kliens tanúsítvány eltávolítva", + "client_cert_subtitle": "Csak a PKCS12 (.p12, .pfx) formátum támogatott. Tanúsítvány Importálása/Eltávolítása csak a bejelentkezés előtt lehetséges", + "client_cert_title": "SSL Kliens Tanúsítvány", "common_add_to_album": "Albumhoz ad", "common_change_password": "Jelszócsere", "common_create_new_album": "Új album létrehozása", "common_server_error": "Kérjük, ellenőrizd a hálózati kapcsolatot, gondoskodj róla, hogy a szerver elérhető legyen, valamint az alkalmazás és a szerver kompatibilis verziójú legyen.", - "common_shared": "Megosztva", - "contextual_search": "Sunrise on the beach", + "common_shared": "Megosztott", + "contextual_search": "Napfelkelte a tengerparton", "control_bottom_app_bar_add_to_album": "Albumhoz ad", "control_bottom_app_bar_album_info": "{} elem", "control_bottom_app_bar_album_info_shared": "{} elemek · Megosztva", - "control_bottom_app_bar_archive": "Archivál", + "control_bottom_app_bar_archive": "Archiválás", "control_bottom_app_bar_create_new_album": "Új album létrehozása", "control_bottom_app_bar_delete": "Törlés", "control_bottom_app_bar_delete_from_immich": "Törlés az Immich-ből", "control_bottom_app_bar_delete_from_local": "Törlés az eszközről", - "control_bottom_app_bar_download": "Download", - "control_bottom_app_bar_edit": "Edit", + "control_bottom_app_bar_download": "Letöltés", + "control_bottom_app_bar_edit": "Szerkesztés", "control_bottom_app_bar_edit_location": "Hely Módosítása", "control_bottom_app_bar_edit_time": "Dátum és Idő Módosítása", "control_bottom_app_bar_favorite": "Kedvenc", @@ -189,14 +189,14 @@ "control_bottom_app_bar_unarchive": "Nem Archivált", "control_bottom_app_bar_unfavorite": "Nem Kedvenc", "control_bottom_app_bar_upload": "Feltöltés", - "create_album": "Create album", + "create_album": "Album létrehozása", "create_album_page_untitled": "Névtelen", - "create_new": "CREATE NEW", + "create_new": "ÚJ LÉTREHOZÁSA", "create_shared_album_page_create": "Létrehoz", "create_shared_album_page_share": "Megosztás", "create_shared_album_page_share_add_assets": "ELEMEK HOZZÁADÁSA", "create_shared_album_page_share_select_photos": "Fotók választása", - "crop": "Crop", + "crop": "Kivágás", "curated_location_page_title": "Helyek", "curated_object_page_title": "Dolgok", "daily_title_text_date": "MMM dd (E)", @@ -216,26 +216,26 @@ "delete_shared_link_dialog_title": "Megosztott Link Törlése", "description_input_hint_text": "Leírás hozzáadása...", "description_input_submit_error": "Nem sikerült frissíteni a leírást. További információért kérjük, nézd meg az eseménynaplót", - "download_canceled": "Download canceled", - "download_complete": "Download complete", - "download_enqueue": "Download enqueued", - "download_error": "Download Error", - "download_failed": "Download failed", - "download_filename": "file: {}", - "download_finished": "Download finished", - "downloading": "Downloading...", - "downloading_media": "Downloading media", - "download_notfound": "Download not found", - "download_paused": "Download paused", - "download_started": "Download started", - "download_sucess": "Download success", - "download_sucess_android": "The media has been downloaded to DCIM/Immich", - "download_waiting_to_retry": "Waiting to retry", + "download_canceled": "Letöltés megszakítva", + "download_complete": "Letöltés kész", + "download_enqueue": "Letöltés sorba állítva", + "download_error": "Letöltési Hiba", + "download_failed": "Sikertelen letöltés", + "download_filename": "fájl: {}", + "download_finished": "Letöltés kész", + "downloading": "Letöltés...", + "downloading_media": "Média letöltése", + "download_notfound": "Letöltés nem található", + "download_paused": "Letöltés szüneteltetve", + "download_started": "Letöltés megkezdve", + "download_sucess": "Sikeres letöltés", + "download_sucess_android": "Média letöltve a DCIM/Immich mappába\n", + "download_waiting_to_retry": "Várakozás", "edit_date_time_dialog_date_time": "Dátum és Idő", "edit_date_time_dialog_timezone": "Időzóna", - "edit_image_title": "Edit", + "edit_image_title": "Szerkesztés", "edit_location_dialog_title": "Hely", - "error_saving_image": "Error: {}", + "error_saving_image": "Hiba: {}", "exif_bottom_sheet_description": "Leírás Hozzáadása...", "exif_bottom_sheet_details": "RÉSZLETEK", "exif_bottom_sheet_location": "HELY", @@ -246,20 +246,20 @@ "experimental_settings_new_asset_list_title": "Kisérleti képrács engedélyezése", "experimental_settings_subtitle": "Csak saját felelősségre használd!", "experimental_settings_title": "Kísérleti", - "favorites": "Favorites", + "favorites": "Kedvencek", "favorites_page_no_favorites": "Nem található kedvencnek jelölt elem", "favorites_page_title": "Kedvencek", - "filename_search": "File name or extension", - "filter": "Filter", + "filename_search": "Fájlnév vagy kiterjesztés", + "filter": "Szűrő", "haptic_feedback_switch": "Rezgéses visszajelzés engedélyezése", "haptic_feedback_title": "Rezgéses Visszajelzés", - "header_settings_add_header_tip": "Add Header", - "header_settings_field_validator_msg": "Value cannot be empty", - "header_settings_header_name_input": "Header name", - "header_settings_header_value_input": "Header value", - "header_settings_page_title": "Proxy Headers", - "headers_settings_tile_subtitle": "Define proxy headers the app should send with each network request", - "headers_settings_tile_title": "Custom proxy headers", + "header_settings_add_header_tip": "Fejléc Hozzáadása", + "header_settings_field_validator_msg": "Az érték nem lehet üres", + "header_settings_header_name_input": "Fejléc neve", + "header_settings_header_value_input": "Fejléc értéke", + "header_settings_page_title": "Proxy Fejlécek", + "headers_settings_tile_subtitle": "Add meg azokat a proxy fejléceket, amiket az app elküldjön minden hálózati kérésnél", + "headers_settings_tile_title": "Egyéni proxy fejlécek", "home_page_add_to_album_conflicts": "{added} elem hozzáadva a(z) \"{album}\" albumhoz. {failed} elem már eleve az albumban volt.", "home_page_add_to_album_err_local": "Helyi elemeket még nem lehet albumba tenni. Kihagyjuk.", "home_page_add_to_album_success": "{added} elem hozzáadva a(z) \"{album}\" albumhoz.", @@ -272,25 +272,25 @@ "home_page_favorite_err_local": "Helyi elemeket még nem lehet a kedvencek közé tenni, úgyhogy ezeket kihagyjuk", "home_page_favorite_err_partner": "Partner elemeit még nem lehet a kedvencek közé tenni, úgyhogy ezeket kihagyjuk", "home_page_first_time_notice": "Ha most használod először az alkalmazást, akkor ahhoz, hogy megjelenjenek a fotók és a videók az idővonaladon, állítsd be, hogy melyik albumaidról készüljön biztonsági mentés.", - "home_page_share_err_local": "Helyi elemekről nem lehet megosztási linket készíteni, úgyhogy kihagyjuk", + "home_page_share_err_local": "Helyi elemekről nem lehet megosztott linket készíteni, úgyhogy kihagyjuk", "home_page_upload_err_limit": "Csak 30 elemet tudsz egyszerre feltölteni, úgyhogy kihagyjuk", - "ignore_icloud_photos": "Ignore iCloud photos", - "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server", - "image_saved_successfully": "Image saved", + "ignore_icloud_photos": "iCloud fotók figyelmen kívül hagyása", + "ignore_icloud_photos_description": "Az iCloud-ban tárolt fotók nem lesznek feltöltve az Immich szerverre", + "image_saved_successfully": "Kép elmentve", "image_viewer_page_state_provider_download_error": "Letöltési Hiba", "image_viewer_page_state_provider_download_started": "Letöltés Megkezdődött", "image_viewer_page_state_provider_download_success": "Letöltés Sikeres", - "image_viewer_page_state_provider_share_error": "Megosztási Hiba", - "invalid_date": "Invalid date", - "invalid_date_format": "Invalid date format", - "library": "Library", + "image_viewer_page_state_provider_share_error": "Megosztás Hiba", + "invalid_date": "Érvénytelen dátum", + "invalid_date_format": "Érvénytelen dátumformátum", + "library": "Képtár", "library_page_albums": "Albumok", "library_page_archive": "Archívum", "library_page_device_albums": "Albumok az Eszközön", "library_page_favorites": "Kedvencek", "library_page_new_album": "Új album", "library_page_sharing": "Megosztás", - "library_page_sort_asset_count": "Eszközök száma", + "library_page_sort_asset_count": "Elemek száma", "library_page_sort_created": "Létrehozás ideje", "library_page_sort_last_modified": "Utolsó módosítás ideje", "library_page_sort_most_oldest_photo": "Legrégebbi fotó", @@ -310,14 +310,14 @@ "login_form_email_hint": "email@cimed.hu", "login_form_endpoint_hint": "http(s)://szerver-címe:port/api", "login_form_endpoint_url": "Szerver címe", - "login_form_err_http": "Kérjük, adj meg egy http:// vagy https:// címet", + "login_form_err_http": "Kérjük, hogy egy http:// vagy https:// címet adj meg", "login_form_err_invalid_email": "Érvénytelen email cím", "login_form_err_invalid_url": "Érvénytelen cím", "login_form_err_leading_whitespace": "Az első karakter szóköz", "login_form_err_trailing_whitespace": "Az utolsó karakter szóköz", "login_form_failed_get_oauth_server_config": "Nem sikerült az OAuth bejelentkezés. Ellenőrizd a szerver címét.", "login_form_failed_get_oauth_server_disable": "OAuth bejelentkezés nem elérhető ezen a szerveren", - "login_form_failed_login": "Hiba bejelentkezés közben, ellenőrizd a címet, email-t és a jelszót", + "login_form_failed_login": "Hiba a bejelentkezés közben, ellenőrizd a szerver címét, az emailt és a jelszót", "login_form_handshake_exception": "SSL Kézfogási Hiba törént. Engedélyezd az önaláírt tanúsítvényokat a beállításokban, hogy ha önaláírt tanúsítványt használsz.", "login_form_label_email": "Email", "login_form_label_password": "Jelszó", @@ -339,7 +339,7 @@ "map_no_assets_in_bounds": "Nincsenek fotók a környéken", "map_no_location_permission_content": "A helymeghatározást engedélyezni kell a jelenlegi helyednél lévő elemek megjelenítéséhez. Szeretnéd most engedélyezni?", "map_no_location_permission_title": "Helymeghatározás letiltva", - "map_settings_dark_mode": "Sötét mód", + "map_settings_dark_mode": "Sötét téma", "map_settings_date_range_option_all": "Összes", "map_settings_date_range_option_day": "Elmúlt 24 óra", "map_settings_date_range_option_days": "Elmúlt {} nap", @@ -353,56 +353,56 @@ "map_settings_only_relative_range": "Dátum intervallum", "map_settings_only_show_favorites": "Csak Kedvencek Mutatása", "map_settings_theme_settings": "Térkép Témája", - "map_zoom_to_see_photos": "Kicsinyíts, hogy láss fényképeket", + "map_zoom_to_see_photos": "Kicsinyítsd, hogy láss fényképeket", "memories_all_caught_up": "Naprakész vagy", "memories_check_back_tomorrow": "Nézz vissza holnap újabb emlékekért", "memories_start_over": "Újrakezdés", "memories_swipe_to_close": "Bezáráshoz söpörd ki felfelé", - "memories_year_ago": "A year ago", - "memories_years_ago": "{} years ago", + "memories_year_ago": "Egy éve", + "memories_years_ago": "{} éve", "monthly_title_text_date_format": "y MMMM", - "motion_photos_page_title": "Mozgó Fotók", + "motion_photos_page_title": "Mozgóképek", "multiselect_grid_edit_date_time_err_read_only": "Csak-olvasható elem(ek) dátuma nem módosítható, ezért kihagyjuk", - "multiselect_grid_edit_gps_err_read_only": "Csak-olvasható elem(ek) helyszíne nem módosítható, ezért kihagyjuk", - "my_albums": "My albums", + "multiselect_grid_edit_gps_err_read_only": "Csak-olvasható elem(ek) helye nem módosítható, ezért kihagyjuk", + "my_albums": "Saját albumaim", "no_assets_to_show": "Nincs megjeleníthető elem", - "no_name": "No name", + "no_name": "Névtelen", "notification_permission_dialog_cancel": "Mégsem", "notification_permission_dialog_content": "Az értesítések bekapcsolásához a Beállítások menüben válaszd ki az Engedélyezés-t.", "notification_permission_dialog_settings": "Beállítások", - "notification_permission_list_tile_content": "Értesítések engedélyezése", + "notification_permission_list_tile_content": "Értesítések engedélyezése.", "notification_permission_list_tile_enable_button": "Értesítések Bekapcsolása", "notification_permission_list_tile_title": "Engedély az Értesítésekhez", - "on_this_device": "On this device", + "on_this_device": "Ezen az eszközön", "partner_list_user_photos": "{user} fényképei", "partner_list_view_all": "Összes mutatása", "partner_page_add_partner": "Partner hozzáadása", "partner_page_empty_message": "Még senkivel nem osztottad meg a fényképeidet.", - "partner_page_no_more_users": "Nincs hozzáadható felhasználó", - "partner_page_partner_add_failed": "Nem sikerült hozzáadni a felhasználót", + "partner_page_no_more_users": "Nincs több hozzáadható felhasználó", + "partner_page_partner_add_failed": "Partner hozzáadása sikertelen", "partner_page_select_partner": "Partner kiválasztása", "partner_page_shared_to_title": "Megosztva: ", "partner_page_stop_sharing_content": "{} nem fog többé hozzáférni a fotóidhoz.", "partner_page_stop_sharing_title": "Fotók megosztásának megszűntetése?", "partner_page_title": "Partner", - "partners": "Partners", - "people": "People", + "partners": "Partnerek", + "people": "Emberek", "permission_onboarding_back": "Vissza", "permission_onboarding_continue_anyway": "Folytatás mindenképp", - "permission_onboarding_get_started": "Kezdjük el", + "permission_onboarding_get_started": "Vágjunk bele", "permission_onboarding_go_to_settings": "Beállítások megnyitása", - "permission_onboarding_grant_permission": "Engedélyezés", + "permission_onboarding_grant_permission": "Engedély meadása", "permission_onboarding_log_out": "Kijelentkezés", "permission_onboarding_permission_denied": "Hozzáférés megtagadva. Az Immich használatához engedélyezni kell a fotó és videó hozzáférést a Beállításokban.", "permission_onboarding_permission_granted": "Hozzáférés engedélyezve! Minden készen áll.", "permission_onboarding_permission_limited": "Korlátozott hozzáférés. Ha szeretnéd, hogy az Immich a teljes galéria gyűjteményedet mentse és kezelje, akkor a Beállításokban engedélyezd a fotó és videó jogosultságokat.", - "permission_onboarding_request": "Engedélyezni kell, hogy az Immich hozzáférjen a képekhez és videókhoz", - "places": "Places", + "permission_onboarding_request": "Engedélyezni kell, hogy az Immich hozzáférjen a képeidhez és videóidhoz", + "places": "Helyek", "preferences_settings_title": "Beállítások", "profile_drawer_app_logs": "Naplók", "profile_drawer_client_out_of_date_major": "A mobilalkalmazás elavult. Kérjük, frissítsd a legfrisebb főverzióra.", "profile_drawer_client_out_of_date_minor": "A mobilalkalmazás elavult. Kérjük, frissítsd a legfrisebb alverzióra.", - "profile_drawer_client_server_up_to_date": "Kliens és a szerver is naprakész", + "profile_drawer_client_server_up_to_date": "A Kliens és a Szerver is naprakész", "profile_drawer_documentation": "Dokumentáció", "profile_drawer_github": "GitHub", "profile_drawer_server_out_of_date_major": "A szerver elavult. Kérjük, frissítsd a legfrisebb főverzióra.", @@ -410,42 +410,42 @@ "profile_drawer_settings": "Beállítások", "profile_drawer_sign_out": "Kijelentkezés", "profile_drawer_trash": "Lomtár", - "recently_added": "Recently added", + "recently_added": "Nemrég hozzáadott", "recently_added_page_title": "Nemrég Hozzáadott", - "save_to_gallery": "Save to gallery", + "save_to_gallery": "Mentés a galériába", "scaffold_body_error_occurred": "Hiba történt", - "search_albums": "Search albums", + "search_albums": "Albumok keresése", "search_bar_hint": "Fotók keresése", "search_filter_apply": "Szűrő alkalmazása", - "search_filter_camera": "Camera", + "search_filter_camera": "Kamera", "search_filter_camera_make": "Gyártó", "search_filter_camera_model": "Modell", - "search_filter_camera_title": "Select camera type", - "search_filter_date": "Date", - "search_filter_date_interval": "{start} to {end}", - "search_filter_date_title": "Select a date range", + "search_filter_camera_title": "Válaszd ki a kamera típusát", + "search_filter_date": "Dátum", + "search_filter_date_interval": "{start} - {end}", + "search_filter_date_title": "Válassz dátum intervallumot", "search_filter_display_option_archive": "Archivált", "search_filter_display_option_favorite": "Kedvenc", "search_filter_display_option_not_in_album": "Nincs albumban", - "search_filter_display_options": "Display Options", - "search_filter_display_options_title": "Display options", - "search_filter_location": "Location", - "search_filter_location_city": "Város", + "search_filter_display_options": "Megjelenítési Beállítások", + "search_filter_display_options_title": "Megjelenítési beállítások", + "search_filter_location": "Hely", + "search_filter_location_city": "Település", "search_filter_location_country": "Ország", - "search_filter_location_state": "Állam", - "search_filter_location_title": "Select location", - "search_filter_media_type": "Media Type", + "search_filter_location_state": "Megye/Állam", + "search_filter_location_title": "Válassz helyet", + "search_filter_media_type": "Média Típus", "search_filter_media_type_all": "Összes", "search_filter_media_type_image": "Kép", - "search_filter_media_type_title": "Select media type", + "search_filter_media_type_title": "Válassz média típust", "search_filter_media_type_video": "Videó", - "search_filter_people": "People", - "search_filter_people_title": "Select people", + "search_filter_people": "Emberek", + "search_filter_people_title": "Válassz embereket", "search_page_categories": "Kategóriák", "search_page_favorites": "Kedvencek", - "search_page_motion_photos": "Mozgó Fotók", + "search_page_motion_photos": "Mozgóképek", "search_page_no_objects": "Nincs Információ a Tárgyakról", - "search_page_no_places": "Nincs Információ a Helyszínekről", + "search_page_no_places": "Nincs Információ a Helyekről", "search_page_people": "Emberek", "search_page_person_add_name_dialog_cancel": "Mégsem", "search_page_person_add_name_dialog_hint": "Név", @@ -461,13 +461,13 @@ "search_page_things": "Dolgok", "search_page_videos": "Videók", "search_page_view_all_button": "Összes mutatása", - "search_page_your_activity": "Tevékenységek", - "search_page_your_map": "Térkép", - "search_result_page_new_search_hint": "Új keresés", - "search_suggestion_list_smart_search_hint_1": "Az intelligens keresés alapértelmezetten be van kapcsolva, metaadatokat így kereshetsz", + "search_page_your_activity": "Tevékenységeid", + "search_page_your_map": "Térképed", + "search_result_page_new_search_hint": "Új Keresés", + "search_suggestion_list_smart_search_hint_1": "Az intelligens keresés alapértelmezetten be van kapcsolva, metaadatokat így kereshetsz:", "search_suggestion_list_smart_search_hint_2": "m:keresési-kifejezés", "select_additional_user_for_sharing_page_suggestions": "Javaslatok", - "select_user_for_sharing_page_err_album": "Nem sikerült létrehozni az albumot", + "select_user_for_sharing_page_err_album": "Az album létrehozása sikertelen", "select_user_for_sharing_page_share_suggestions": "Javaslatok", "server_info_box_app_version": "Alkalmazás Verzió", "server_info_box_latest_release": "Legfrissebb Verzió", @@ -507,7 +507,7 @@ "shared_album_activities_input_hint": "Szólj hozzá", "shared_album_activity_remove_content": "Törölni szeretnéd ezt a tevékenységet?", "shared_album_activity_remove_title": "Tevékenység Törlése", - "shared_album_activity_setting_subtitle": "Engedd, hogy mások reagáljanak", + "shared_album_activity_setting_subtitle": "Mások is reagálhatnak", "shared_album_activity_setting_title": "Hozzászólások és lájkok", "shared_album_section_people_action_error": "Hiba az albummal kapcsolatos kilépés/eltávolítás közben", "shared_album_section_people_action_leave": "Felhasználó eltávolítása az albumból", @@ -518,8 +518,8 @@ "shared_link_app_bar_title": "Megosztott Linkek", "shared_link_clipboard_copied_massage": "Vágólapra másolva", "shared_link_clipboard_text": "Link: {}\nJelszó: {}", - "shared_link_create_app_bar_title": "Megosztási link létrehozása", - "shared_link_create_error": "Hiba a megosztási link létrehozásakor", + "shared_link_create_app_bar_title": "Megosztott link létrehozása", + "shared_link_create_error": "Hiba a megosztott link létrehozásakor", "shared_link_create_info": "A linket használva bárki megnézheti a kiválasztott kép(ek)et", "shared_link_create_submit_button": "Link létrehozása", "shared_link_edit_allow_download": "Letöltés engedélyezése", @@ -539,11 +539,11 @@ "shared_link_edit_expire_after_option_never": "Soha", "shared_link_edit_expire_after_option_year": "{} év", "shared_link_edit_password": "Jelszó", - "shared_link_edit_password_hint": "Add meg a megosztási jelszót", + "shared_link_edit_password_hint": "Add meg a megosztáshoz tartozó jelszót", "shared_link_edit_show_meta": "Metaadatok mutatása", "shared_link_edit_submit_button": "Link frissítése", - "shared_link_empty": "Nincsenek megosztási linkek", - "shared_link_error_server_url_fetch": "A szerver címét nem sikerült betölteni", + "shared_link_empty": "Nincsenek megosztott linkek", + "shared_link_error_server_url_fetch": "A szerver címét nem lehet betölteni", "shared_link_expired": "Lejárt", "shared_link_expires_day": "{} nap múlva lejár", "shared_link_expires_days": "{} nap múlva lejár", @@ -558,53 +558,53 @@ "shared_link_info_chip_download": "Letöltés", "shared_link_info_chip_metadata": "EXIF", "shared_link_info_chip_upload": "Feltöltés", - "shared_link_manage_links": "Megosztási linkek kezelése", + "shared_link_manage_links": "Megosztott linkek kezelése", "shared_link_public_album": "Nyilvános album", - "shared_links": "Shared links", + "shared_links": "Megosztott linkek", "share_done": "Kész", - "shared_with_me": "Shared with me", + "shared_with_me": "Velem megosztva", "share_invite": "Meghívás az albumba", "sharing_page_album": "Megosztott albumok", - "sharing_page_description": "Megosztott albumok létrehozásával fényképeket és videókat oszthatsz meg a hálózatodban lévő emberekkel.", + "sharing_page_description": "Megosztott albumok létrehozásával fényképeket és videókat oszthatsz meg az ismerőseiddel.", "sharing_page_empty_list": "ÜRES LISTA", "sharing_silver_appbar_create_shared_album": "Új megosztott album", - "sharing_silver_appbar_shared_links": "Megosztási linkek", + "sharing_silver_appbar_shared_links": "Megosztott linkek", "sharing_silver_appbar_share_partner": "Megosztás partnerrel", - "sync": "Sync", - "sync_albums": "Sync albums", - "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", - "sync_upload_album_setting_subtitle": "Create and upload your photos and videos to the selected albums on Immich", + "sync": "Szinkronizálás", + "sync_albums": "Albumok szinkronizálása", + "sync_albums_manual_subtitle": "Összes fotó és videó létrehozása és szinkronizálása a kiválasztott Immich albumokba", + "sync_upload_album_setting_subtitle": "Fotók és videók létrehozása és szinkronizálása a kiválasztott Immich albumba", "tab_controller_nav_library": "Képtár", "tab_controller_nav_photos": "Képek", "tab_controller_nav_search": "Keresés", "tab_controller_nav_sharing": "Megosztás", "theme_setting_asset_list_storage_indicator_title": "Tárhely ikon mutatása az elemeken", "theme_setting_asset_list_tiles_per_row_title": "Elemek száma soronként ({})", - "theme_setting_colorful_interface_subtitle": "Apply primary color to background surfaces.", - "theme_setting_colorful_interface_title": "Colorful interface", - "theme_setting_dark_mode_switch": "Sötét mód", + "theme_setting_colorful_interface_subtitle": "Alapértelmezett szín használata a háttérben lévő felületekhez", + "theme_setting_colorful_interface_title": "Színes felhasználói felület", + "theme_setting_dark_mode_switch": "Sötét téma", "theme_setting_image_viewer_quality_subtitle": "Részletes képmegjelenítő minőségének beállítása", "theme_setting_image_viewer_quality_title": "Képmegjelenítő minősége", - "theme_setting_primary_color_subtitle": "Pick a color for primary actions and accents.", - "theme_setting_primary_color_title": "Primary color", - "theme_setting_system_primary_color_title": "Use system color", + "theme_setting_primary_color_subtitle": "Válassz egy színt az alapértelmezett műveletekhez és kiemelésekhez", + "theme_setting_primary_color_title": "Alapértelmezett szín", + "theme_setting_system_primary_color_title": "Rendszerszínek használata", "theme_setting_system_theme_switch": "Automatikus (követi a rendszer témáját)", "theme_setting_theme_subtitle": "Alkalmazás témájának választása", "theme_setting_theme_title": "Téma", "theme_setting_three_stage_loading_subtitle": "A háromlépcsős betöltés javíthatja a betöltési teljesítményt, de jelentősen növeli a hálózati forgalmat", "theme_setting_three_stage_loading_title": "Háromlépcsős betöltés engedélyezése", "translated_text_options": "Beállítások", - "trash": "Trash", - "trash_emptied": "Emptied trash", + "trash": "Lomtár", + "trash_emptied": "Lomtár kiürítve", "trash_page_delete": "Töröl", "trash_page_delete_all": "Mindet Töröl", - "trash_page_empty_trash_btn": "Lomtár Ürítése", + "trash_page_empty_trash_btn": "Lomtár ürítése", "trash_page_empty_trash_dialog_content": "Ki szeretnéd üríteni a lomtárban lévő elemeket? Ezeket véglegesen eltávolítjuk az Immich-ből", "trash_page_empty_trash_dialog_ok": "Ok", "trash_page_info": "A Lomátrba helyezett elemek {} nap után véglegesen törlődnek", - "trash_page_no_assets": "Nincsen semmi a Lomtárban", + "trash_page_no_assets": "A Lomtár üres", "trash_page_restore": "Visszaállít", - "trash_page_restore_all": "Mindet Visszaállítja", + "trash_page_restore_all": "Mindet Visszaállít", "trash_page_select_assets_btn": "Elemek kiválasztása", "trash_page_select_btn": "Kiválaszt", "trash_page_title": "Lomtár ({})", @@ -613,12 +613,12 @@ "upload_dialog_ok": "Feltöltés", "upload_dialog_title": "Elem Feltöltése", "version_announcement_overlay_ack": "Megértettem", - "version_announcement_overlay_release_notes": "a változtatások listáját elolvasd", - "version_announcement_overlay_text_1": "Szia, egy új verzió érhető el", - "version_announcement_overlay_text_2": "kérlek szánj időt arra, hogy ", - "version_announcement_overlay_text_3": "és gyöződj meg róla, hogy a docker-compose és .env beállításai naprakészek és pontosak, különösen akkor, ha watchtower-t vagy bármi olyan megoldást használsz, ami automatikusan frissíti a szervert.", - "version_announcement_overlay_title": "Új Szerververzió Érhető El \uD83C\uDF89", - "videos": "Videos", + "version_announcement_overlay_release_notes": "kiadási megjegyzések áttekintésére", + "version_announcement_overlay_text_1": "Szia barátom, ennek az alkalmazásnak van egy új verziója: ", + "version_announcement_overlay_text_2": "Kérjük, szánj időt a", + "version_announcement_overlay_text_3": ", és győződj meg róla, hogy a docker-compose.yml és az .env beállításaid naprakészek, hogy elkerüld a hibás konfigurációkat, különösen, ha a WatchTower-t vagy bármilyen automatikus frissítési megoldást használsz.", + "version_announcement_overlay_title": "Elérhető Új Szerververzió \uD83C\uDF89", + "videos": "Videók", "viewer_remove_from_stack": "Eltávolít a Csoportból", "viewer_stack_use_as_main_asset": "Fő Elemnek Beállít", "viewer_unstack": "Csoport Megszűntetése" diff --git a/mobile/assets/i18n/ja-JP.json b/mobile/assets/i18n/ja-JP.json index e5fed5705d69a..bcc1df654866f 100644 --- a/mobile/assets/i18n/ja-JP.json +++ b/mobile/assets/i18n/ja-JP.json @@ -6,7 +6,7 @@ "action_common_save": "保存", "action_common_select": "選択", "action_common_update": "更新", - "add_a_name": "Add a name", + "add_a_name": "名前を追加", "add_to_album_bottom_sheet_added": "{album}に追加", "add_to_album_bottom_sheet_already_exists": "{album}に追加済み", "advanced_settings_log_level_title": "ログレベル: {}", @@ -22,7 +22,7 @@ "advanced_settings_troubleshooting_title": "トラブルシューティング", "album_info_card_backup_album_excluded": "除外中", "album_info_card_backup_album_included": "選択中", - "albums": "Albums", + "albums": "アルバム", "album_thumbnail_card_item": "1枚", "album_thumbnail_card_items": "{}枚", "album_thumbnail_card_shared": "共有済み", @@ -38,13 +38,13 @@ "album_viewer_appbar_share_remove": "アルバムから削除", "album_viewer_appbar_share_to": "次の方々と共有します", "album_viewer_page_share_add_users": "ユーザーを追加", - "all": "All", + "all": "全て", "all_people_page_title": "人物", "all_videos_page_title": "ビデオ", "app_bar_signout_dialog_content": " サインアウトしますか?", "app_bar_signout_dialog_ok": "はい", "app_bar_signout_dialog_title": " サインアウト", - "archived": "Archived", + "archived": "アーカイブ済み", "archive_page_no_archived_assets": "アーカイブ済みの写真またはビデオがありません", "archive_page_title": "アーカイブ({})", "asset_action_delete_err_read_only": "読み取り専用の項目は削除できません。スキップします", @@ -189,9 +189,9 @@ "control_bottom_app_bar_unarchive": "アーカイブを解除", "control_bottom_app_bar_unfavorite": "お気に入りから外す", "control_bottom_app_bar_upload": "アップロード", - "create_album": "Create album", + "create_album": "アルバム作成", "create_album_page_untitled": "タイトルなし", - "create_new": "CREATE NEW", + "create_new": "新規作成", "create_shared_album_page_create": "作成", "create_shared_album_page_share": "共有", "create_shared_album_page_share_add_assets": "写真を追加", @@ -246,11 +246,11 @@ "experimental_settings_new_asset_list_title": "試験的なグリッドを有効化", "experimental_settings_subtitle": "試験的機能につき自己責任で!", "experimental_settings_title": "試験的機能", - "favorites": "Favorites", + "favorites": "お気に入り", "favorites_page_no_favorites": "お気に入り登録された写真またはビデオがありません", "favorites_page_title": "お気に入り", "filename_search": "ファイル名、又は拡張子", - "filter": "Filter", + "filter": "フィルター", "haptic_feedback_switch": "ハプティックフィードバック", "haptic_feedback_title": "ハプティックフィードバックを有効にする", "header_settings_add_header_tip": "ヘッダを追加", @@ -274,8 +274,8 @@ "home_page_first_time_notice": "はじめてアプリを使う場合、タイムラインに写真を表示するためにアルバムを選択してください", "home_page_share_err_local": "ローカルのみの項目をリンクで共有はできません。スキップします", "home_page_upload_err_limit": "1回でアップロードできる写真の数は30枚です。スキップします", - "ignore_icloud_photos": "Ignore iCloud photos", - "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server", + "ignore_icloud_photos": "iCloud上の写真をスキップ", + "ignore_icloud_photos_description": "iCloudに保存済みの項目をImmichサーバー上にアップロードしません", "image_saved_successfully": "画像が保存されました", "image_viewer_page_state_provider_download_error": "ダウンロード失敗", "image_viewer_page_state_provider_download_started": "ダウンロードが始まります", @@ -283,7 +283,7 @@ "image_viewer_page_state_provider_share_error": "共有エラー", "invalid_date": "日付が無効です", "invalid_date_format": "日付のフォーマットが無効です", - "library": "Library", + "library": "ライブラリ", "library_page_albums": "アルバム", "library_page_archive": "アーカイブ", "library_page_device_albums": "デバイス上のアルバム", @@ -364,7 +364,7 @@ "motion_photos_page_title": "モーションフォト", "multiselect_grid_edit_date_time_err_read_only": "読み取り専用の項目の日付を変更できません", "multiselect_grid_edit_gps_err_read_only": "読み取り専用の項目の位置情報を変更できません", - "my_albums": "My albums", + "my_albums": "自分のアルバム", "no_assets_to_show": "表示する項目がありません", "no_name": "名前がありません", "notification_permission_dialog_cancel": "キャンセル", @@ -373,7 +373,7 @@ "notification_permission_list_tile_content": "通知の許可 をオンにしてください", "notification_permission_list_tile_enable_button": "通知をオンにする", "notification_permission_list_tile_title": "通知の許可", - "on_this_device": "On this device", + "on_this_device": "デバイス上の項目", "partner_list_user_photos": "{user}さんの写真", "partner_list_view_all": "すべて見る", "partner_page_add_partner": "パートナーを追加", @@ -385,8 +385,8 @@ "partner_page_stop_sharing_content": "{}は写真へアクセスできなくなります", "partner_page_stop_sharing_title": "写真の共有を無効化しますか?", "partner_page_title": "パートナー", - "partners": "Partners", - "people": "People", + "partners": "パートナー", + "people": "人物", "permission_onboarding_back": "戻る", "permission_onboarding_continue_anyway": "無視して続行", "permission_onboarding_get_started": "はじめる", @@ -397,7 +397,7 @@ "permission_onboarding_permission_granted": "写真へのアクセスが許可されました", "permission_onboarding_permission_limited": "写真へのアクセスが制限されています。Immichが写真のバックアップと管理を行うには、システム設定から写真と動画のアクセス権限を変更してください。", "permission_onboarding_request": "Immichは写真へのアクセス許可が必要です", - "places": "Places", + "places": "場所", "preferences_settings_title": "設定", "profile_drawer_app_logs": "ログ", "profile_drawer_client_out_of_date_major": "アプリが更新されてません。最新のバージョンに更新してください", @@ -410,11 +410,11 @@ "profile_drawer_settings": "設定", "profile_drawer_sign_out": "サインアウト", "profile_drawer_trash": "ゴミ箱", - "recently_added": "Recently added", + "recently_added": "最近追加された項目", "recently_added_page_title": "最近", "save_to_gallery": "ギャラリーに保存", "scaffold_body_error_occurred": "エラーが発生しました", - "search_albums": "Search albums", + "search_albums": "アルバムを探す", "search_bar_hint": "写真を検索", "search_filter_apply": "フィルターを適用する", "search_filter_camera": "カメラ", @@ -560,9 +560,9 @@ "shared_link_info_chip_upload": "アップロード", "shared_link_manage_links": "共有済みのリンクを管理", "shared_link_public_album": "公開アルバム", - "shared_links": "Shared links", + "shared_links": "共有済みリンク", "share_done": "完了", - "shared_with_me": "Shared with me", + "shared_with_me": "自分と共有中", "share_invite": "アルバムに招待", "sharing_page_album": "共有アルバム", "sharing_page_description": "共有アルバムを作成して同じネットワークにいる人たちに写真を共有", @@ -594,7 +594,7 @@ "theme_setting_three_stage_loading_subtitle": "三段階読み込みを有効にすると、パフォーマンスが改善する可能性がありますが、ネットワーク負荷が著しく増加します。", "theme_setting_three_stage_loading_title": "三段階読み込みをオンにする", "translated_text_options": "オプション", - "trash": "Trash", + "trash": "ゴミ箱", "trash_emptied": "ゴミ箱を空にしました", "trash_page_delete": "削除", "trash_page_delete_all": "すべて削除", @@ -618,7 +618,7 @@ "version_announcement_overlay_text_2": "のバージョンが公開中です。", "version_announcement_overlay_text_3": "を確認してください。docker-composeや.envファイルが最新の状態に更新済みか、特にWatchTowerなどのツールを使ってDockerイメージを自動アップデートされてる方は確認してください。", "version_announcement_overlay_title": "サーバーの最新版が公開中\uD83C\uDF89", - "videos": "Videos", + "videos": "動画", "viewer_remove_from_stack": "スタックから外す", "viewer_stack_use_as_main_asset": "メインの画像として使用する", "viewer_unstack": "スタックを解除" diff --git a/mobile/assets/i18n/ko-KR.json b/mobile/assets/i18n/ko-KR.json index 090925e724949..02eace03b08b8 100644 --- a/mobile/assets/i18n/ko-KR.json +++ b/mobile/assets/i18n/ko-KR.json @@ -30,10 +30,10 @@ "album_thumbnail_shared_by": "{}님이 공유함", "album_viewer_appbar_delete_confirm": "이 앨범을 삭제하시겠습니까?", "album_viewer_appbar_share_delete": "앨범 삭제", - "album_viewer_appbar_share_err_delete": "앨범을 삭제하지 못했습니다.", - "album_viewer_appbar_share_err_leave": "앨범에서 나가지 못했습니다.", + "album_viewer_appbar_share_err_delete": "앨범 삭제에 실패했습니다.", + "album_viewer_appbar_share_err_leave": "앨범 나가기에 실패했습니다.", "album_viewer_appbar_share_err_remove": "앨범에서 항목을 제거하지 못했습니다.", - "album_viewer_appbar_share_err_title": "앨범명을 변경하지 못했습니다.", + "album_viewer_appbar_share_err_title": "앨범명 변경에 실패했습니다.", "album_viewer_appbar_share_leave": "앨범 나가기", "album_viewer_appbar_share_remove": "앨범에서 제거", "album_viewer_appbar_share_to": "공유 대상", @@ -44,7 +44,7 @@ "app_bar_signout_dialog_content": "정말 로그아웃하시겠습니까?", "app_bar_signout_dialog_ok": "네", "app_bar_signout_dialog_title": "로그아웃", - "archived": "아카이브", + "archived": "보관함", "archive_page_no_archived_assets": "보관된 항목 없음", "archive_page_title": "보관함 ({})", "asset_action_delete_err_read_only": "읽기 전용 항목은 삭제할 수 없습니다. 건너뜁니다.", @@ -216,20 +216,20 @@ "delete_shared_link_dialog_title": "공유 링크 삭제", "description_input_hint_text": "설명 추가...", "description_input_submit_error": "설명을 변경하는 중 문제가 발생했습니다. 자세한 내용은 로그를 참조하세요.", - "download_canceled": "다운로드가 취소되었습니다", - "download_complete": "다은로드가 완료되었습니다", + "download_canceled": "다운로드가 취소되었습니다.", + "download_complete": "다은로드가 완료되었습니다.", "download_enqueue": "대기열에 다운로드", - "download_error": "다운로드 중 문제가 발생했습니다", - "download_failed": "다운로드에 실패하였습니다", + "download_error": "다운로드 중 문제가 발생했습니다.", + "download_failed": "다운로드에 실패하였습니다.", "download_filename": "파일: {}", - "download_finished": "다운로드가 완료되었습니다", + "download_finished": "다운로드가 완료되었습니다.", "downloading": "다운로드 중...", "downloading_media": "미디어 다운로드 중", "download_notfound": "다운로드할 수 없음", "download_paused": "다운로드 일시 중지됨", - "download_started": "다운로드가 시작되었습니다", - "download_sucess": "다운로드가 완료되었습니다", - "download_sucess_android": "미디어가 DCIM/Immich에 저장되었습니다", + "download_started": "다운로드가 시작되었습니다.", + "download_sucess": "다운로드가 완료되었습니다.", + "download_sucess_android": "미디어가 DCIM/Immich에 저장되었습니다.", "download_waiting_to_retry": "재시도 대기 중", "edit_date_time_dialog_date_time": "날짜 및 시간", "edit_date_time_dialog_timezone": "시간대", @@ -276,7 +276,7 @@ "home_page_upload_err_limit": "한 번에 최대 30개의 항목만 업로드할 수 있습니다.", "ignore_icloud_photos": "iCloud 사진 제외", "ignore_icloud_photos_description": "iCloud에 저장된 사진은 Immich 서버에 업로드되지 않습니다.", - "image_saved_successfully": "이미지가 저장되었습니다", + "image_saved_successfully": "이미지가 저장되었습니다.", "image_viewer_page_state_provider_download_error": "다운로드 오류", "image_viewer_page_state_provider_download_started": "다운로드가 시작되었습니다.", "image_viewer_page_state_provider_download_success": "다운로드 완료", @@ -313,8 +313,8 @@ "login_form_err_http": "http:// 또는 https://로 시작해야 합니다.", "login_form_err_invalid_email": "유효하지 않은 이메일", "login_form_err_invalid_url": "잘못된 URL입니다.", - "login_form_err_leading_whitespace": "시작 부분에 공백이 있습니다.", - "login_form_err_trailing_whitespace": "끝 부분에 공백이 있습니다.", + "login_form_err_leading_whitespace": "문자 시작에 공백이 있습니다.", + "login_form_err_trailing_whitespace": "문자 끝에 공백이 있습니다.", "login_form_failed_get_oauth_server_config": "OAuth 로그인 중 문제 발생, 서버 URL을 확인하세요.", "login_form_failed_get_oauth_server_disable": "이 서버는 OAuth 기능을 지원하지 않습니다.", "login_form_failed_login": "로그인 오류. 서버 URL, 이메일 및 비밀번호를 확인하세요.", @@ -500,7 +500,7 @@ "setting_video_viewer_title": "동영상", "share_add": "추가", "share_add_photos": "사진 추가", - "share_add_title": "앨범명 추가", + "share_add_title": "제목 추가", "share_assets_selected": "{}개 항목 선택됨", "share_create_album": "앨범 생성", "shared_album_activities_input_disable": "댓글이 비활성화되었습니다", @@ -594,7 +594,7 @@ "theme_setting_three_stage_loading_subtitle": "이 기능은 앱의 로드 성능을 향상시킬 수 있지만 더 많은 데이터를 사용합니다.", "theme_setting_three_stage_loading_title": "3단계 로드 활성화", "translated_text_options": "옵션", - "trash": "쓰레기통", + "trash": "휴지통", "trash_emptied": "휴지통을 비웠습니다.", "trash_page_delete": "삭제", "trash_page_delete_all": "모두 삭제", diff --git a/mobile/assets/i18n/nb-NO.json b/mobile/assets/i18n/nb-NO.json index 8bc8402fd3f31..80c9db2804672 100644 --- a/mobile/assets/i18n/nb-NO.json +++ b/mobile/assets/i18n/nb-NO.json @@ -6,7 +6,7 @@ "action_common_save": "Lagre", "action_common_select": "Velg", "action_common_update": "Oppdater", - "add_a_name": "Add a name", + "add_a_name": "Legg til navn", "add_to_album_bottom_sheet_added": "Lagt til i {album}", "add_to_album_bottom_sheet_already_exists": "Allerede i {album}", "advanced_settings_log_level_title": "Loggnivå: {}", @@ -22,7 +22,7 @@ "advanced_settings_troubleshooting_title": "Feilsøking", "album_info_card_backup_album_excluded": "EKSKLUDERT", "album_info_card_backup_album_included": "INKLUDERT", - "albums": "Albums", + "albums": "Albumer", "album_thumbnail_card_item": "1 objekt", "album_thumbnail_card_items": "{} objekter", "album_thumbnail_card_shared": " · Delt", @@ -38,13 +38,13 @@ "album_viewer_appbar_share_remove": "Fjern fra album", "album_viewer_appbar_share_to": "Del til", "album_viewer_page_share_add_users": "Legg til brukere", - "all": "All", + "all": "Alt", "all_people_page_title": "Folk", "all_videos_page_title": "Videoer", "app_bar_signout_dialog_content": "Er du sikker på at du vil logge ut?", "app_bar_signout_dialog_ok": "Ja", "app_bar_signout_dialog_title": "Logg ut", - "archived": "Archived", + "archived": "Arkivert", "archive_page_no_archived_assets": "Ingen arkiverte objekter funnet", "archive_page_title": "Arkiv ({})", "asset_action_delete_err_read_only": "Kan ikke slette objekt(er) med kun lese-rettighet, hopper over", @@ -189,9 +189,9 @@ "control_bottom_app_bar_unarchive": "Fjern fra arkiv", "control_bottom_app_bar_unfavorite": "Fjern favoritt", "control_bottom_app_bar_upload": "Last opp", - "create_album": "Create album", + "create_album": "Opprett album", "create_album_page_untitled": "Uten navn", - "create_new": "CREATE NEW", + "create_new": "LAG NY", "create_shared_album_page_create": "Opprett", "create_shared_album_page_share": "Del", "create_shared_album_page_share_add_assets": "LEGG TIL OBJEKTER", @@ -246,7 +246,7 @@ "experimental_settings_new_asset_list_title": "Aktiver eksperimentell rutenettsvisning", "experimental_settings_subtitle": "Bruk på egen risiko!", "experimental_settings_title": "Eksperimentelt", - "favorites": "Favorites", + "favorites": "Favoritter", "favorites_page_no_favorites": "Ingen favorittobjekter funnet", "favorites_page_title": "Favoritter", "filename_search": "Filnavn eller filtype", @@ -274,8 +274,8 @@ "home_page_first_time_notice": "Hvis dette er første gangen du benytter appen, velg et album (eller flere) for sikkerhetskopiering, slik at tidslinjen kan fylles med dine bilder og videoer.", "home_page_share_err_local": "Kan ikke dele lokale objekter via link, hopper over", "home_page_upload_err_limit": "Maksimalt 30 objekter kan lastes opp om gangen, hopper over", - "ignore_icloud_photos": "Ignore iCloud photos", - "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server", + "ignore_icloud_photos": "Ignorer iCloud bilder", + "ignore_icloud_photos_description": "Bilder som er lagret på iCloud vil ikke lastes opp til Immich", "image_saved_successfully": "Bilde lagret", "image_viewer_page_state_provider_download_error": "Nedlasting feilet", "image_viewer_page_state_provider_download_started": "Nedlasting startet", @@ -283,7 +283,7 @@ "image_viewer_page_state_provider_share_error": "Delingsfeil", "invalid_date": "Ugyldig dato", "invalid_date_format": "Ugyldig datoformat", - "library": "Library", + "library": "Bibliotek", "library_page_albums": "Albumer", "library_page_archive": "Arkiv", "library_page_device_albums": "Albumer på enheten", @@ -364,7 +364,7 @@ "motion_photos_page_title": "Bevegelige bilder", "multiselect_grid_edit_date_time_err_read_only": "Kan ikke endre dato på objekt(er) med kun lese-rettigheter, hopper over", "multiselect_grid_edit_gps_err_read_only": "Kan ikke endre lokasjon på objekt(er) med kun lese-rettigheter, hopper over", - "my_albums": "My albums", + "my_albums": "Mine albumer", "no_assets_to_show": "Ingen objekter å vise", "no_name": "Ingen navn", "notification_permission_dialog_cancel": "Avbryt", @@ -373,7 +373,7 @@ "notification_permission_list_tile_content": "Gi tilgang for å aktivere notifikasjoner", "notification_permission_list_tile_enable_button": "Aktiver notifikasjoner", "notification_permission_list_tile_title": "Notifikasjonstilgang", - "on_this_device": "On this device", + "on_this_device": "På denne enheten", "partner_list_user_photos": "{user}'s bilder", "partner_list_view_all": "Vis alle", "partner_page_add_partner": "Legg til partner", @@ -385,8 +385,8 @@ "partner_page_stop_sharing_content": "{} vil ikke lenger ha tilgang til dine bilder.", "partner_page_stop_sharing_title": "Stopp deling av bildene dine?", "partner_page_title": "Partner", - "partners": "Partners", - "people": "People", + "partners": "Partnere", + "people": "Mennesker", "permission_onboarding_back": "Tilbake", "permission_onboarding_continue_anyway": "Fortsett uansett", "permission_onboarding_get_started": "Kom i gang", @@ -397,7 +397,7 @@ "permission_onboarding_permission_granted": "Tilgang gitt! Du er i gang.", "permission_onboarding_permission_limited": "Begrenset tilgang. For å la Immich sikkerhetskopiere og håndtere galleriet, tillatt bilde- og video-tilgang i Innstillinger.", "permission_onboarding_request": "Immich trenger tilgang til å se dine bilder og videoer", - "places": "Places", + "places": "Steder", "preferences_settings_title": "Innstillinger", "profile_drawer_app_logs": "Logg", "profile_drawer_client_out_of_date_major": "Mobilapp er utdatert. Vennligst oppdater til nyeste versjon.", @@ -410,11 +410,11 @@ "profile_drawer_settings": "Innstillinger", "profile_drawer_sign_out": "Logg ut", "profile_drawer_trash": "Søppelbøtte", - "recently_added": "Recently added", + "recently_added": "Nylig lagt til", "recently_added_page_title": "Nylig lagt til", "save_to_gallery": "Lagre til galleriet", "scaffold_body_error_occurred": "Feil oppstått", - "search_albums": "Search albums", + "search_albums": "Søk i albumer", "search_bar_hint": "Søk i dine bilder", "search_filter_apply": "Aktiver filter", "search_filter_camera": "Kamera", @@ -560,9 +560,9 @@ "shared_link_info_chip_upload": "Last opp", "shared_link_manage_links": "Håndter delte linker", "shared_link_public_album": "Offentlig album", - "shared_links": "Shared links", + "shared_links": "Delte linker", "share_done": "Ferdig", - "shared_with_me": "Shared with me", + "shared_with_me": "Delt med meg", "share_invite": "Inviter til album", "sharing_page_album": "Delte album", "sharing_page_description": "Lag delte albumer for å dele bilder og videoer med folk i nettverket ditt.", @@ -594,7 +594,7 @@ "theme_setting_three_stage_loading_subtitle": "Tre-trinns innlasting kan øke lasteytelsen, men forårsaker betydelig høyere nettverksbelastning", "theme_setting_three_stage_loading_title": "Aktiver tre-trinns innlasting", "translated_text_options": "Valg", - "trash": "Trash", + "trash": "Søppel", "trash_emptied": "Søppelbøtte tømt", "trash_page_delete": "Slett", "trash_page_delete_all": "Slett alt", @@ -618,7 +618,7 @@ "version_announcement_overlay_text_2": "vennligst ta deg tid til å besøke ", "version_announcement_overlay_text_3": " og verifiser at docker-compose og .env-oppsettet ditt er oppdatert for å forhindre en eventuell feilkonfigurasjon, spesielt hvis du benytter WatchTower eller en annen tjeneste som håndterer oppdatering av server-applikasjonen automatisk.", "version_announcement_overlay_title": "Ny serverversjon tilgjengelig", - "videos": "Videos", + "videos": "Videoer", "viewer_remove_from_stack": "Fjern fra stabling", "viewer_stack_use_as_main_asset": "Bruk som hovedobjekt", "viewer_unstack": "avstable" diff --git a/mobile/assets/i18n/pl-PL.json b/mobile/assets/i18n/pl-PL.json index 3a6ba9f3b45f0..12a7e6faf2fdd 100644 --- a/mobile/assets/i18n/pl-PL.json +++ b/mobile/assets/i18n/pl-PL.json @@ -6,7 +6,7 @@ "action_common_save": "Zapisz", "action_common_select": "Wybierz", "action_common_update": "Aktualizuj", - "add_a_name": "Add a name", + "add_a_name": "Dodaj nazwę", "add_to_album_bottom_sheet_added": "Dodano do {album}", "add_to_album_bottom_sheet_already_exists": "Już w {album}", "advanced_settings_log_level_title": "Poziom dziennika: {}", @@ -22,7 +22,7 @@ "advanced_settings_troubleshooting_title": "Rozwiązywanie problemów", "album_info_card_backup_album_excluded": "WYKLUCZONE", "album_info_card_backup_album_included": "WŁĄCZONE", - "albums": "Albums", + "albums": "Albumy", "album_thumbnail_card_item": "1 pozycja", "album_thumbnail_card_items": "{} pozycje", "album_thumbnail_card_shared": "Udostępniony", @@ -44,7 +44,7 @@ "app_bar_signout_dialog_content": "Czy na pewno chcesz się wylogować?", "app_bar_signout_dialog_ok": "Tak", "app_bar_signout_dialog_title": "Wyloguj się", - "archived": "Archived", + "archived": "Zarchiwizowane", "archive_page_no_archived_assets": "Nie znaleziono zarchiwizowanych zasobów", "archive_page_title": "Archiwum ({})", "asset_action_delete_err_read_only": "Nie można usunąć zasobów tylko do odczytu, pomijam", @@ -183,7 +183,7 @@ "control_bottom_app_bar_edit_time": "Edytuj datę i godzinę", "control_bottom_app_bar_favorite": "Ulubione", "control_bottom_app_bar_share": "Udostępnij", - "control_bottom_app_bar_share_to": "Udostępnij", + "control_bottom_app_bar_share_to": "Wyślij", "control_bottom_app_bar_stack": "Stos", "control_bottom_app_bar_trash_from_immich": "Przenieść do kosza", "control_bottom_app_bar_unarchive": "Cofnij archiwizację", @@ -246,7 +246,7 @@ "experimental_settings_new_asset_list_title": "Włącz eksperymentalną układ zdjęć", "experimental_settings_subtitle": "Używaj na własne ryzyko!", "experimental_settings_title": "Eksperymentalny", - "favorites": "Favorites", + "favorites": "Ulubione", "favorites_page_no_favorites": "Nie znaleziono ulubionych zasobów", "favorites_page_title": "Ulubione", "filename_search": "Nazwa pliku lub rozszerzenie", @@ -373,7 +373,7 @@ "notification_permission_list_tile_content": "Przyznaj uprawnienia, aby włączyć powiadomienia.", "notification_permission_list_tile_enable_button": "Włącz Powiadomienia", "notification_permission_list_tile_title": "Pozwolenie na powiadomienia", - "on_this_device": "On this device", + "on_this_device": "Na tym urządzeniu", "partner_list_user_photos": "{user} zdjęcia", "partner_list_view_all": "Pokaż wszystkie", "partner_page_add_partner": "Dodaj partnera", @@ -386,7 +386,7 @@ "partner_page_stop_sharing_title": "Przestać udostępniać swoje zdjęcia?", "partner_page_title": "Partner", "partners": "Partners", - "people": "People", + "people": "Ludzie", "permission_onboarding_back": "Cofnij", "permission_onboarding_continue_anyway": "Kontynuuj mimo to", "permission_onboarding_get_started": "Rozpocznij", @@ -397,7 +397,7 @@ "permission_onboarding_permission_granted": "Pozwolenie udzielone! Wszystko gotowe.", "permission_onboarding_permission_limited": "Pozwolenie ograniczone. Aby umożliwić Immichowi tworzenie kopii zapasowych całej kolekcji galerii i zarządzanie nią, przyznaj uprawnienia do zdjęć i filmów w Ustawieniach.", "permission_onboarding_request": "Immich potrzebuje pozwolenia na przeglądanie Twoich zdjęć i filmów.", - "places": "Places", + "places": "Miejsca", "preferences_settings_title": "Ustawienia", "profile_drawer_app_logs": "Logi", "profile_drawer_client_out_of_date_major": "Aplikacja mobilna jest nieaktualna. Zaktualizuj do najnowszej wersji głównej.", @@ -414,7 +414,7 @@ "recently_added_page_title": "Ostatnio Dodane", "save_to_gallery": "Zapisz w galerii", "scaffold_body_error_occurred": "Wystąpił błąd", - "search_albums": "Search albums", + "search_albums": "Przeszukaj albumy", "search_bar_hint": "Szukaj swoich zdjęć", "search_filter_apply": "Zastosuj filtr", "search_filter_camera": "Kamera", @@ -560,7 +560,7 @@ "shared_link_info_chip_upload": "Wgraj", "shared_link_manage_links": "Zarządzaj udostępnionymi linkami", "shared_link_public_album": "Album publiczny", - "shared_links": "Shared links", + "shared_links": "Udostępnione linki", "share_done": "Zrobione", "shared_with_me": "Shared with me", "share_invite": "Zaproś do albumu", @@ -594,7 +594,7 @@ "theme_setting_three_stage_loading_subtitle": "Trójstopniowe ładowanie może zwiększyć wydajność ładowania, ale powoduje znacznie większe obciążenie sieci", "theme_setting_three_stage_loading_title": "Włączenie trójstopniowego ładowania", "translated_text_options": "Opcje", - "trash": "Trash", + "trash": "Kosz", "trash_emptied": "Opróżnione śmieci", "trash_page_delete": "Usuń", "trash_page_delete_all": "Usuń wszystko", diff --git a/mobile/assets/i18n/pt-PT.json b/mobile/assets/i18n/pt-PT.json index 1adae1b1ec536..a17bae556707f 100644 --- a/mobile/assets/i18n/pt-PT.json +++ b/mobile/assets/i18n/pt-PT.json @@ -6,7 +6,7 @@ "action_common_save": "Salvar", "action_common_select": "Selecionar", "action_common_update": "Atualizar", - "add_a_name": "Add a name", + "add_a_name": "Adicionar nome", "add_to_album_bottom_sheet_added": "Adicionado a {album}", "add_to_album_bottom_sheet_already_exists": "Já existe em {album}", "advanced_settings_log_level_title": "Nível de log: {}", @@ -22,7 +22,7 @@ "advanced_settings_troubleshooting_title": "Resolução de problemas", "album_info_card_backup_album_excluded": "EXCLUÍDO", "album_info_card_backup_album_included": "INCLUÍDO", - "albums": "Albums", + "albums": "Álbuns", "album_thumbnail_card_item": "1 arquivo", "album_thumbnail_card_items": "{} arquivos", "album_thumbnail_card_shared": " · Compartilhado", @@ -38,13 +38,13 @@ "album_viewer_appbar_share_remove": "Remover do álbum", "album_viewer_appbar_share_to": "Compartilhar com", "album_viewer_page_share_add_users": "Adicionar usuários", - "all": "All", + "all": "Todos", "all_people_page_title": "Pessoas", "all_videos_page_title": "Vídeos", "app_bar_signout_dialog_content": "Tem certeza que deseja sair?", "app_bar_signout_dialog_ok": "Sim", "app_bar_signout_dialog_title": "Sair", - "archived": "Archived", + "archived": "Arquivado", "archive_page_no_archived_assets": "Nenhum arquivo encontrado", "archive_page_title": "Arquivado ({})", "asset_action_delete_err_read_only": "Não é possível excluir arquivo só leitura, ignorando", @@ -189,9 +189,9 @@ "control_bottom_app_bar_unarchive": "Desarquivar", "control_bottom_app_bar_unfavorite": "Remover favorito", "control_bottom_app_bar_upload": "Enviar", - "create_album": "Create album", + "create_album": "Criar Álbum", "create_album_page_untitled": "Sem título", - "create_new": "CREATE NEW", + "create_new": "CRIAR NOVO", "create_shared_album_page_create": "Criar", "create_shared_album_page_share": "Compartilhar", "create_shared_album_page_share_add_assets": "ADICIONAR ARQUIVOS", @@ -199,9 +199,9 @@ "crop": "Cortar", "curated_location_page_title": "Locais", "curated_object_page_title": "Objetos", - "daily_title_text_date": "E, MMM dd", - "daily_title_text_date_year": "E, MMM dd, yyyy", - "date_format": "E, LLL d, y • h:mm a", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM, yyyy", + "date_format": "E, d LLL, y • h:mm a", "delete_dialog_alert": "Esses arquivos serão permanentemente apagados do Immich e de seu dispositivo", "delete_dialog_alert_local": "Estes arquivos serão permanentemente excluídos do seu dispositivo, mas continuarão disponíveis no servidor Immich", "delete_dialog_alert_local_non_backed_up": "Não há backup de alguns dos arquivos no servidor e eles serão excluídos permanentemente do seu dispositivo", @@ -246,11 +246,11 @@ "experimental_settings_new_asset_list_title": "Ativar visualização de grade experimental", "experimental_settings_subtitle": "Use por sua conta e risco!", "experimental_settings_title": "Experimental", - "favorites": "Favorites", + "favorites": "Favoritos", "favorites_page_no_favorites": "Nenhum favorito encontrado", "favorites_page_title": "Favoritos", "filename_search": "Nome do arquivo ou extensão", - "filter": "Filter", + "filter": "Filtro", "haptic_feedback_switch": "Habilitar vibração", "haptic_feedback_title": "Vibração", "header_settings_add_header_tip": "Adicionar cabeçalho", @@ -274,8 +274,8 @@ "home_page_first_time_notice": "Se é a primeira vez que utiliza o aplicativo, certifique-se de marcar um ou mais álbuns do dispositivo para backup, assim a linha do tempo será preenchida com as fotos e vídeos.", "home_page_share_err_local": "Não é possível compartilhar arquivos locais com um link, ignorando", "home_page_upload_err_limit": "Só é possível enviar 30 arquivos por vez, ignorando", - "ignore_icloud_photos": "Ignore iCloud photos", - "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server", + "ignore_icloud_photos": "ignorar fotos no iCloud", + "ignore_icloud_photos_description": "Fotos que estão armazenadas no iCloud não serão carregadas para o servidor do Immich", "image_saved_successfully": "Imagem salva", "image_viewer_page_state_provider_download_error": "Erro ao baixar", "image_viewer_page_state_provider_download_started": "Baixando arquivo", @@ -283,7 +283,7 @@ "image_viewer_page_state_provider_share_error": "Erro ao compartilhar", "invalid_date": "Data inválida", "invalid_date_format": "Formato de data inválido", - "library": "Library", + "library": "Biblioteca", "library_page_albums": "Álbuns", "library_page_archive": "Arquivado", "library_page_device_albums": "Álbuns no dispositivo", @@ -364,7 +364,7 @@ "motion_photos_page_title": "Fotos com movimento", "multiselect_grid_edit_date_time_err_read_only": "Não é possível editar a data de arquivo só leitura, ignorando", "multiselect_grid_edit_gps_err_read_only": "Não é possível editar a localização de arquivo só leitura, ignorando", - "my_albums": "My albums", + "my_albums": "Meus álbuns", "no_assets_to_show": "Não há arquivos para exibir", "no_name": "Sem nome", "notification_permission_dialog_cancel": "Cancelar", @@ -373,7 +373,7 @@ "notification_permission_list_tile_content": "Dar permissões para ativar notificações", "notification_permission_list_tile_enable_button": "Ativar notificações", "notification_permission_list_tile_title": "Permissão de notificações", - "on_this_device": "On this device", + "on_this_device": "Neste dispositivo", "partner_list_user_photos": "Fotos de {user}", "partner_list_view_all": "Ver tudo", "partner_page_add_partner": "Adicionar parceiro", @@ -385,8 +385,8 @@ "partner_page_stop_sharing_content": "{} não poderá mais acessar as suas fotos.", "partner_page_stop_sharing_title": "Parar de compartilhar as suas fotos?", "partner_page_title": "Parceiro", - "partners": "Partners", - "people": "People", + "partners": "Parceiros", + "people": "Pessoas", "permission_onboarding_back": "Voltar", "permission_onboarding_continue_anyway": "Continuar mesmo assim", "permission_onboarding_get_started": "Começar", @@ -397,7 +397,7 @@ "permission_onboarding_permission_granted": "Permissão concedida! Está tudo pronto.", "permission_onboarding_permission_limited": "Permissão limitada. Para permitir que o Immich faça backups e gerencie sua galeria, conceda permissões para fotos e vídeos nas configurações.", "permission_onboarding_request": "O Immich requer autorização para ver as suas fotos e vídeos.", - "places": "Places", + "places": "Lugares", "preferences_settings_title": "Preferências", "profile_drawer_app_logs": "Logs", "profile_drawer_client_out_of_date_major": "O aplicativo está desatualizado. Por favor, atualize para a versão mais recente.", @@ -410,11 +410,11 @@ "profile_drawer_settings": "Configurações", "profile_drawer_sign_out": "Sair", "profile_drawer_trash": "Lixeira", - "recently_added": "Recently added", + "recently_added": "Adicionados Recentemente", "recently_added_page_title": "Adicionado recentemente", "save_to_gallery": "Salvar na galeria", "scaffold_body_error_occurred": "Ocorreu um erro", - "search_albums": "Search albums", + "search_albums": "Pesquisar Álbuns", "search_bar_hint": "Pesquisar em suas fotos", "search_filter_apply": "Aplicar filtro", "search_filter_camera": "Câmera", @@ -560,9 +560,9 @@ "shared_link_info_chip_upload": "Enviar", "shared_link_manage_links": "Gerenciar links compartilhados", "shared_link_public_album": "Álbum público", - "shared_links": "Shared links", + "shared_links": "Links compartilhados", "share_done": "Feito", - "shared_with_me": "Shared with me", + "shared_with_me": "Compartilhado comigo", "share_invite": "Convidar para o álbum", "sharing_page_album": "Álbuns compartilhados", "sharing_page_description": "Crie álbuns compartilhados para compartilhar fotos e vídeos com pessoas da sua rede.", @@ -594,7 +594,7 @@ "theme_setting_three_stage_loading_subtitle": "O carregamento em três estágios pode aumentar o desempenho do carregamento, mas causa uma carga de rede significativamente maior", "theme_setting_three_stage_loading_title": "Habilitar carregamento em três estágios", "translated_text_options": "Opções", - "trash": "Trash", + "trash": "Lixo", "trash_emptied": "Lixeira esvaziada", "trash_page_delete": "Excluir", "trash_page_delete_all": "Excluir tudo", @@ -618,7 +618,7 @@ "version_announcement_overlay_text_2": "por favor, Verifique com calma as ", "version_announcement_overlay_text_3": "e certifique-se de que a configuração do docker-compose e do arquivo .env estejam atualizadas para evitar configurações incorretas, especialmente se utiliza o WatchTower ou qualquer outro mecanismo que faça atualização automática do servidor.", "version_announcement_overlay_title": "Nova versão do servidor disponível \uD83C\uDF89", - "videos": "Videos", + "videos": "Vídeos", "viewer_remove_from_stack": "Remover da pilha", "viewer_stack_use_as_main_asset": "Usar como foto principal", "viewer_unstack": "Desempilhar" diff --git a/mobile/assets/i18n/ru-RU.json b/mobile/assets/i18n/ru-RU.json index 80e0611d3f7e0..ae7fc46f5d107 100644 --- a/mobile/assets/i18n/ru-RU.json +++ b/mobile/assets/i18n/ru-RU.json @@ -6,45 +6,45 @@ "action_common_save": "Сохранить", "action_common_select": "Выбрать", "action_common_update": "Обновить", - "add_a_name": "Add a name", + "add_a_name": "Добавить имя", "add_to_album_bottom_sheet_added": "Добавлено в {album}", "add_to_album_bottom_sheet_already_exists": "Уже в {album}", - "advanced_settings_log_level_title": "Log level: {}", - "advanced_settings_prefer_remote_subtitle": "Некоторые устройства очень медленно загружают предпросмотр объектов, находящихся на устройстве. Активируйте эту настройку, чтобы вместо них загружались изображения с сервера.", + "advanced_settings_log_level_title": "Уровень логирования:", + "advanced_settings_prefer_remote_subtitle": "Некоторые устройства очень медленно загружают локальные изображения. Активируйте эту настройку, чтобы изображения всегда загружались с сервера.", "advanced_settings_prefer_remote_title": "Предпочитать фото на сервере", "advanced_settings_proxy_headers_subtitle": "Определите заголовки прокси-сервера, которые Immich должен отправлять с каждым сетевым запросом.", - "advanced_settings_proxy_headers_title": "Прокси-заголовки", - "advanced_settings_self_signed_ssl_subtitle": "Пропускает проверку SSL-сертификата сервера. Требуется для самоподписанных сертификатов.", + "advanced_settings_proxy_headers_title": "Заголовки прокси", + "advanced_settings_self_signed_ssl_subtitle": "Пропускать проверку SSL-сертификата сервера. Требуется для самоподписанных сертификатов.", "advanced_settings_self_signed_ssl_title": "Разрешить самоподписанные SSL-сертификаты", - "advanced_settings_tile_subtitle": "Расширенные настройки пользователя", + "advanced_settings_tile_subtitle": "Расширенные настройки", "advanced_settings_tile_title": "Расширенные", "advanced_settings_troubleshooting_subtitle": "Включить расширенные возможности для решения проблем", "advanced_settings_troubleshooting_title": "Решение проблем", "album_info_card_backup_album_excluded": "ИСКЛЮЧЕН", "album_info_card_backup_album_included": "ВКЛЮЧЕН", - "albums": "Albums", + "albums": "Альбомы", "album_thumbnail_card_item": "1 объект", "album_thumbnail_card_items": "{} объектов", "album_thumbnail_card_shared": "· Общий", "album_thumbnail_owned": "Автор", "album_thumbnail_shared_by": "Поделился {}", - "album_viewer_appbar_delete_confirm": "Вы уверены, что хотите удалить этот альбом из своей учетной записи?", + "album_viewer_appbar_delete_confirm": "Вы уверены, что хотите удалить альбом из своей учетной записи?", "album_viewer_appbar_share_delete": "Удалить альбом", - "album_viewer_appbar_share_err_delete": "Невозможно удалить альбом", - "album_viewer_appbar_share_err_leave": "Невозможно покинуть альбом", + "album_viewer_appbar_share_err_delete": "Не удалось удалить альбом", + "album_viewer_appbar_share_err_leave": "Не удалось покинуть альбом", "album_viewer_appbar_share_err_remove": "Возникли проблемы с удалением объектов из альбома", - "album_viewer_appbar_share_err_title": "Ошибка переименования альбома", + "album_viewer_appbar_share_err_title": "Не удалось переименовать альбом", "album_viewer_appbar_share_leave": "Покинуть альбом", "album_viewer_appbar_share_remove": "Удалить из альбома", "album_viewer_appbar_share_to": "Поделиться", "album_viewer_page_share_add_users": "Добавить пользователей", - "all": "All", + "all": "Все", "all_people_page_title": "Люди", "all_videos_page_title": "Видео", - "app_bar_signout_dialog_content": "Вы уверены, что хотите выйти из системы?", + "app_bar_signout_dialog_content": "Вы уверены, что хотите выйти?", "app_bar_signout_dialog_ok": "Да", - "app_bar_signout_dialog_title": "Выйти из системы", - "archived": "Archived", + "app_bar_signout_dialog_title": "Выйти", + "archived": "Архив", "archive_page_no_archived_assets": "В архиве сейчас пусто", "archive_page_title": "Архив ({})", "asset_action_delete_err_read_only": "Невозможно удалить объект(ы) только для чтения, пропуск...", @@ -52,11 +52,11 @@ "asset_list_group_by_sub_title": "Группировать по", "asset_list_layout_settings_dynamic_layout_title": "Динамическое расположение", "asset_list_layout_settings_group_automatically": "Автоматически", - "asset_list_layout_settings_group_by": "Группировать объекты по:", + "asset_list_layout_settings_group_by": "Группировать объекты по", "asset_list_layout_settings_group_by_month": "Месяцу", "asset_list_layout_settings_group_by_month_day": "Месяцу и дню", "asset_list_layout_sub_title": "Разметка", - "asset_list_settings_subtitle": "Настройка макета сетки фотографий", + "asset_list_settings_subtitle": "Настройка сетки фотографий", "asset_list_settings_title": "Сетка фотографий", "asset_restored_successfully": "Объект успешно восстановлен", "assets_deleted_permanently": "{} объект(ы) удален(ы) навсегда", @@ -65,11 +65,11 @@ "assets_restored_successfully": "{} объект(ы) успешно восстановлен(ы)", "assets_trashed": "{} объект(ы) помещен(ы) в корзину", "assets_trashed_from_server": "{} объект(ы) помещен(ы) в корзину на сервере Immich", - "asset_viewer_settings_title": "Просмотрщик изображений", - "backup_album_selection_page_albums_device": "Альбомов на устройстве ({})", + "asset_viewer_settings_title": "Просмотр изображений", + "backup_album_selection_page_albums_device": "Альбомы на устройстве ({})", "backup_album_selection_page_albums_tap": "Нажмите, чтобы включить,\nнажмите дважды, чтобы исключить", - "backup_album_selection_page_assets_scatter": "Объекты могут быть разбросаны по нескольким альбомам. Таким образом, альбомы могут быть включены или исключены из процесса резервного копирования.", - "backup_album_selection_page_select_albums": "Выбрать альбомы", + "backup_album_selection_page_assets_scatter": "Ваши изображения и видео могут находиться в разных альбомах. Вы можете выбрать, какие альбомы включить, а какие исключить из резервного копирования.", + "backup_album_selection_page_select_albums": "Выбор альбомов", "backup_album_selection_page_selection_info": "Информация о выборе", "backup_album_selection_page_total_assets": "Всего уникальных объектов", "backup_all": "Все", @@ -81,11 +81,11 @@ "backup_background_service_in_progress_notification": "Резервное копирование ваших объектов…", "backup_background_service_upload_failure_notification": "Ошибка загрузки {}", "backup_controller_page_albums": "Резервное копирование альбомов", - "backup_controller_page_background_app_refresh_disabled_content": "Включите фоновое обновление приложений в меню Настройки > Общие > Фоновое обновление приложений, чтобы использовать фоновое резервное копирование.", + "backup_controller_page_background_app_refresh_disabled_content": "Включите фоновое обновление приложения в Настройки > Общие > Фоновое обновление приложений, чтобы использовать фоновое резервное копирование.", "backup_controller_page_background_app_refresh_disabled_title": "Фоновое обновление отключено", "backup_controller_page_background_app_refresh_enable_button_text": "Перейти в настройки", - "backup_controller_page_background_battery_info_link": "Показать как", - "backup_controller_page_background_battery_info_message": "Для наилучшего фонового резервного копирования отключите любые настройки оптимизации батареи, ограничивающие фоновую активность для Immich.\n\nПоскольку это зависит от устройства, найдите необходимую информацию для производителя вашего устройства.", + "backup_controller_page_background_battery_info_link": "Подробнее", + "backup_controller_page_background_battery_info_message": "Для стабильного резервного копирования в фоновом режиме, отключите любые настройки оптимизации батареи, ограничивающие фоновую активность приложения.\n\nПоскольку настройки зависят от устройства, найдите необходимую информацию для производителя вашего устройства.", "backup_controller_page_background_battery_info_ok": "ОК", "backup_controller_page_background_battery_info_title": "Оптимизация батареи", "backup_controller_page_background_charging": "Только во время зарядки", @@ -102,7 +102,7 @@ "backup_controller_page_backup_sub": "Загруженные фото и видео", "backup_controller_page_cancel": "Отмена", "backup_controller_page_created": "Создано: {}", - "backup_controller_page_desc_backup": "Включите резервное копирование в активном режиме, чтобы автоматически загружать новые объекты на сервер при открытии приложения.", + "backup_controller_page_desc_backup": "Включите резервное копирование в активном режиме, чтобы автоматически загружать новые объекты при открытии приложения.", "backup_controller_page_excluded": "Исключены:", "backup_controller_page_failed": "Неудачных ({})", "backup_controller_page_filename": "Имя файла: {} [{}]", @@ -110,7 +110,7 @@ "backup_controller_page_info": "Информация о резервном копировании", "backup_controller_page_none_selected": "Ничего не выбрано", "backup_controller_page_remainder": "Осталось", - "backup_controller_page_remainder_sub": "Оставшиеся фото и видео для резервного копирования из выбранного", + "backup_controller_page_remainder_sub": "Фото и видео для загрузки", "backup_controller_page_select": "Выбор", "backup_controller_page_server_storage": "Хранилище на сервере", "backup_controller_page_start_backup": "Начать резервное копирование", @@ -120,20 +120,20 @@ "backup_controller_page_to_backup": "Альбомы для резервного копирования", "backup_controller_page_total": "Всего", "backup_controller_page_total_sub": "Все уникальные фото и видео из выбранных альбомов", - "backup_controller_page_turn_off": "Выключить резервное копирование в активном режиме", - "backup_controller_page_turn_on": "Включить резервное копирование в активном режиме", + "backup_controller_page_turn_off": "Выключить", + "backup_controller_page_turn_on": "Включить", "backup_controller_page_uploading_file_info": "Информация о загружаемом файле", "backup_err_only_album": "Невозможно удалить единственный альбом", "backup_info_card_assets": "объектов", "backup_manual_cancelled": "Отменено", "backup_manual_failed": "Неудачно", - "backup_manual_in_progress": "Загрузка уже в процессе, попробуйте позже", + "backup_manual_in_progress": "Загрузка в процессе. Попробуйте позже", "backup_manual_success": "Успешно", "backup_manual_title": "Статус загрузки", "backup_options_page_title": "Резервное копирование", "cache_settings_album_thumbnails": "Миниатюры страниц библиотеки ({} объектов)", "cache_settings_clear_cache_button": "Очистить кэш", - "cache_settings_clear_cache_button_title": "Очищает кэш приложения. Это значительно повлияет на производительность приложения, до тех пор, пока кэш не будет перестроен заново.", + "cache_settings_clear_cache_button_title": "Очищает кэш приложения. Это негативно повлияет на производительность, пока кэш не будет создан заново.", "cache_settings_duplicated_assets_clear_button": "ОЧИСТИТЬ", "cache_settings_duplicated_assets_subtitle": "Фото и видео, занесенные приложением в черный список", "cache_settings_duplicated_assets_title": "Дублирующиеся объекты ({})", @@ -144,13 +144,13 @@ "cache_settings_statistics_shared": "Миниатюры общих альбомов", "cache_settings_statistics_thumbnail": "Миниатюры", "cache_settings_statistics_title": "Размер кэша", - "cache_settings_subtitle": "Управление кэшированием мобильного приложения Immich", - "cache_settings_thumbnail_size": "Размер кэша эскизов ({} объектов)", - "cache_settings_tile_subtitle": "Управление поведением локального хранилища", + "cache_settings_subtitle": "Управление кэшированием мобильного приложения", + "cache_settings_thumbnail_size": "Размер кэша миниатюр ({} объектов)", + "cache_settings_tile_subtitle": "Управление локальным хранилищем", "cache_settings_tile_title": "Локальное хранилище", "cache_settings_title": "Настройки кэширования", "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_password_mismatch": "Пароли не совпадают", "change_password_form_reenter_new_password": "Повторно введите новый пароль", @@ -161,7 +161,7 @@ "client_cert_invalid_msg": "Неверный файл сертификата или неверный пароль", "client_cert_remove": "Удалить", "client_cert_remove_msg": "Клиентский сертификат удален", - "client_cert_subtitle": "Поддерживается только формат PKCS12 (.p12, .pfx). Импорт/удаление сертификата доступно только перед входом в систему.", + "client_cert_subtitle": "Поддерживается только формат PKCS12 (.p12, .pfx). Импорт/удаление сертификата доступно только перед входом в систему", "client_cert_title": "Клиентский SSL-сертификат ", "common_add_to_album": "Добавить в альбом", "common_change_password": "Изменить пароль", @@ -170,39 +170,39 @@ "common_shared": "Общие", "contextual_search": "Восход солнца на пляже", "control_bottom_app_bar_add_to_album": "Добавить в альбом", - "control_bottom_app_bar_album_info": "{} файлов", - "control_bottom_app_bar_album_info_shared": "{} файлов · Общий", + "control_bottom_app_bar_album_info": "{} медиа", + "control_bottom_app_bar_album_info_shared": "{} медиа · Общий", "control_bottom_app_bar_archive": "Архив", - "control_bottom_app_bar_create_new_album": "Создать новый альбом", + "control_bottom_app_bar_create_new_album": "Создать альбом", "control_bottom_app_bar_delete": "Удалить", "control_bottom_app_bar_delete_from_immich": "Удалить из Immich\n", "control_bottom_app_bar_delete_from_local": "Удалить с устройства", "control_bottom_app_bar_download": "Скачать", - "control_bottom_app_bar_edit": "Редактировать", - "control_bottom_app_bar_edit_location": "Редактировать местоположение", - "control_bottom_app_bar_edit_time": "Редактировать дату и время", + "control_bottom_app_bar_edit": "Изменить", + "control_bottom_app_bar_edit_location": "Изменить место", + "control_bottom_app_bar_edit_time": "Изменить дату", "control_bottom_app_bar_favorite": "В избранное", "control_bottom_app_bar_share": "Поделиться", "control_bottom_app_bar_share_to": "Поделиться", "control_bottom_app_bar_stack": "Стек", - "control_bottom_app_bar_trash_from_immich": "Переместить в корзину", + "control_bottom_app_bar_trash_from_immich": "В корзину", "control_bottom_app_bar_unarchive": "Восстановить", "control_bottom_app_bar_unfavorite": "Удалить из избранного", "control_bottom_app_bar_upload": "Загрузить", - "create_album": "Create album", + "create_album": "Создать альбом", "create_album_page_untitled": "Без названия", - "create_new": "CREATE NEW", + "create_new": "СОЗДАТЬ НОВЫЙ", "create_shared_album_page_create": "Создать", "create_shared_album_page_share": "Поделиться", "create_shared_album_page_share_add_assets": "ДОБАВИТЬ ОБЪЕКТЫ", - "create_shared_album_page_share_select_photos": "Выберите фотографии", - "crop": "Кадрировать", + "create_shared_album_page_share_select_photos": "Выбрать фотографии", + "crop": "Обрезать", "curated_location_page_title": "Места", "curated_object_page_title": "Предметы", "daily_title_text_date": "E, MMM dd", "daily_title_text_date_year": "E, MMM dd, yyyy", "date_format": "E, LLL d, y • h:mm a", - "delete_dialog_alert": "Эти элементы будут безвозвратно удалены с сервера Immich, а также с вашего устройства", + "delete_dialog_alert": "Эти элементы будут безвозвратно удалены с сервера, а также с вашего устройства", "delete_dialog_alert_local": "Эти объекты будут безвозвратно удалены с Вашего устройства, но по-прежнему будут доступны на сервере Immich", "delete_dialog_alert_local_non_backed_up": "Резервные копии некоторых объектов не были загружены в Immich и будут безвозвратно удалены с Вашего устройства", "delete_dialog_alert_remote": "Эти объекты будут безвозвратно удалены с сервера Immich", @@ -212,8 +212,8 @@ "delete_dialog_title": "Удалить навсегда", "delete_local_dialog_ok_backed_up_only": "Удалить только резервные копии", "delete_local_dialog_ok_force": "Все равно удалить", - "delete_shared_link_dialog_content": "Вы уверены, что хотите удалить эту общую ссылку?", - "delete_shared_link_dialog_title": "Удалить общую ссылку", + "delete_shared_link_dialog_content": "Вы уверены, что хотите удалить публичную ссылку?", + "delete_shared_link_dialog_title": "Удалить публичную ссылку", "description_input_hint_text": "Добавить описание...", "description_input_submit_error": "Не удалось обновить описание, проверьте логи, чтобы узнать причину", "download_canceled": "Загрузка отменена", @@ -225,7 +225,7 @@ "download_finished": "Загрузка окончена", "downloading": "Загрузка...", "downloading_media": "Загрузка медиа", - "download_notfound": "Загрузка не обнаружена", + "download_notfound": "Загрузка не найдена", "download_paused": "Загрузка приостановлена", "download_started": "Загрузка началась", "download_sucess": "Успешная загрузка", @@ -238,15 +238,15 @@ "error_saving_image": "Ошибка: {}", "exif_bottom_sheet_description": "Добавить описание...", "exif_bottom_sheet_details": "ПОДРОБНОСТИ", - "exif_bottom_sheet_location": "Местоположение", - "exif_bottom_sheet_location_add": "Добавить местоположение", + "exif_bottom_sheet_location": "МЕСТО", + "exif_bottom_sheet_location_add": "Добавить место", "exif_bottom_sheet_people": "ЛЮДИ", "exif_bottom_sheet_person_add_person": "Добавить имя", - "experimental_settings_new_asset_list_subtitle": "Ведутся работы", + "experimental_settings_new_asset_list_subtitle": "В разработке", "experimental_settings_new_asset_list_title": "Включить экспериментальную сетку фотографий", "experimental_settings_subtitle": "Используйте на свой страх и риск!", "experimental_settings_title": "Экспериментальные функции", - "favorites": "Favorites", + "favorites": "Избранное", "favorites_page_no_favorites": "В избранном сейчас пусто", "favorites_page_title": "Избранное", "filename_search": "Имя или расширение файла", @@ -258,24 +258,24 @@ "header_settings_header_name_input": "Имя заголовка", "header_settings_header_value_input": "Значение заголовка", "header_settings_page_title": "Прокси-заголовки", - "headers_settings_tile_subtitle": "Определите заголовки прокси, которые приложение должно отправлять с каждым сетевым запросом.", + "headers_settings_tile_subtitle": "Определите заголовки прокси, которые приложение должно отправлять с каждым сетевым запросом", "headers_settings_tile_title": "Пользовательские заголовки прокси", - "home_page_add_to_album_conflicts": "Добавлено {added} объектов в альбом {album}. Объекты {failed} уже есть в альбоме.", - "home_page_add_to_album_err_local": "Пока нельзя добавлять локальные объекты в альбомы, пропускаем", - "home_page_add_to_album_success": "Добавлено {added} объектов в альбом {album}.", - "home_page_album_err_partner": "Пока не удается добавить объекты партнера в альбом, пропуск...", - "home_page_archive_err_local": "Пока невозможно добавить локальные объекты в архив, пропускаем", - "home_page_archive_err_partner": "Невозможно архивировать объекты партнера, пропуск...", + "home_page_add_to_album_conflicts": "Добавлено {added} медиа в альбом {album}. {failed} медиа уже в альбоме.", + "home_page_add_to_album_err_local": "Пока нельзя добавлять локальные объекты в альбомы, пропуск", + "home_page_add_to_album_success": "Добавлено {added} медиа в альбом {album}.", + "home_page_album_err_partner": "Пока нельзя добавить медиа партнера в альбом, пропуск", + "home_page_archive_err_local": "Пока нельзя добавить локальные файлы в архив, пропуск", + "home_page_archive_err_partner": "Невозможно архивировать медиа партнера, пропуск", "home_page_building_timeline": "Построение хронологии", - "home_page_delete_err_partner": "Невозможно удалить объекты партнера, пропуск...", - "home_page_delete_remote_err_local": "Локальные объект(ы) уже в процессе удаления с сервера, пропуск...", - "home_page_favorite_err_local": "Пока не удается добавить в избранное локальные объекты, пропуск...", - "home_page_favorite_err_partner": "Пока не удается добавить в избранное объекты партнера, пропуск...", - "home_page_first_time_notice": "Если вы используете приложение впервые, убедитесь, что вы выбрали резервный(е) альбом(ы), чтобы временная шкала могла заполнить фотографии и видео в альбоме(ах).", - "home_page_share_err_local": "Невозможно поделиться локальными данными по ссылке, пропуск...", - "home_page_upload_err_limit": "Вы можете выгрузить максимум 30 файлов за раз", - "ignore_icloud_photos": "Ignore iCloud photos", - "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server", + "home_page_delete_err_partner": "Невозможно удалить медиа партнера, пропуск", + "home_page_delete_remote_err_local": "Невозможно удалить локальные файлы с сервера, пропуск", + "home_page_favorite_err_local": "Пока нельзя добавить в избранное локальные файлы, пропуск", + "home_page_favorite_err_partner": "Пока нельзя добавить в избранное медиа партнера, пропуск", + "home_page_first_time_notice": "Если вы используете приложение впервые, выберите альбомы для резервного копирования или загрузите их вручную, чтобы заполнить ими временную шкалу.", + "home_page_share_err_local": "Нельзя поделиться локальными файлами по ссылке, пропуск", + "home_page_upload_err_limit": "Вы можете загрузить максимум 30 файлов за раз, пропуск", + "ignore_icloud_photos": "Пропускать файлы из iCloud", + "ignore_icloud_photos_description": "Не загружать файлы в Immich, если они хранятся в iCloud", "image_saved_successfully": "Изображение сохранено", "image_viewer_page_state_provider_download_error": "Ошибка загрузки", "image_viewer_page_state_provider_download_started": "Загрузка началась", @@ -283,7 +283,7 @@ "image_viewer_page_state_provider_share_error": "Ошибка общего доступа", "invalid_date": "Неверная дата", "invalid_date_format": "Неверный формат даты", - "library": "Library", + "library": "Библиотека", "library_page_albums": "Альбомы", "library_page_archive": "Архив", "library_page_device_albums": "Альбомы на устройстве", @@ -293,39 +293,39 @@ "library_page_sort_asset_count": "Количество объектов", "library_page_sort_created": "Недавно созданные", "library_page_sort_last_modified": "Последнее изменение", - "library_page_sort_most_oldest_photo": "Самые старые фото", - "library_page_sort_most_recent_photo": "Самые последние фото", + "library_page_sort_most_oldest_photo": "Старые фото", + "library_page_sort_most_recent_photo": "Последние фото", "library_page_sort_title": "Название альбома", "location_picker_choose_on_map": "Выбрать на карте", "location_picker_latitude": "Широта", "location_picker_latitude_error": "Укажите правильную широту", - "location_picker_latitude_hint": "Укажите широту", + "location_picker_latitude_hint": "Введите широту", "location_picker_longitude": "Долгота", "location_picker_longitude_error": "Укажите правильную долготу", - "location_picker_longitude_hint": "Укажите долготу", + "location_picker_longitude_hint": "Введите долготу", "login_disabled": "Вход отключен", - "login_form_api_exception": "Ошибка при попытке взаимодействия с сервером. Проверьте URL-адрес до него и попробуйте еще раз.", + "login_form_api_exception": "Ошибка подключения к серверу. Проверьте URL-адрес и попробуйте еще раз.", "login_form_back_button_text": "Назад", "login_form_button_text": "Войти", "login_form_email_hint": "youremail@email.com", "login_form_endpoint_hint": "http://your-server-ip:port/api", "login_form_endpoint_url": "URL-aдрес сервера", - "login_form_err_http": "Пожалуйста, укажите http:// или https://", - "login_form_err_invalid_email": "Неверный адрес Email", - "login_form_err_invalid_url": "Неверная ссылка", + "login_form_err_http": "Пожалуйста, укажите протокол http:// или https://", + "login_form_err_invalid_email": "Некорректный адрес электронной почты", + "login_form_err_invalid_url": "Некорректный URL", "login_form_err_leading_whitespace": "Пробел до", "login_form_err_trailing_whitespace": "Пробел после", "login_form_failed_get_oauth_server_config": "Ошибка авторизации с использованием OAuth, проверьте URL-адрес сервера", - "login_form_failed_get_oauth_server_disable": "Функция OAuth недоступна на этом сервере.", - "login_form_failed_login": "Ошибка при входе в систему, проверьте URL-адрес сервера, адрес электронной почты и пароль", - "login_form_handshake_exception": "Произошло нарушение рукопожатия с сервером. Включите в настройках поддержку самоподписанных сертификатов, если вы используете самоподписанный сертификат.", + "login_form_failed_get_oauth_server_disable": "Авторизация через OAuth недоступна на этом сервере", + "login_form_failed_login": "Ошибка при входе, проверьте URL-адрес сервера, адрес электронной почты и пароль", + "login_form_handshake_exception": "Ошибка проверки сертификата. Если вы используете самоподписанный сертификат, включите поддержку самоподписанных сертификатов в настройках.", "login_form_label_email": "Email", "login_form_label_password": "Пароль", "login_form_next_button": "Далее", "login_form_password_hint": "пароль", "login_form_save_login": "Оставаться в системе", - "login_form_server_empty": "Введите URL-адрес вашего сервера.", - "login_form_server_error": "Нет соединения с сервером.", + "login_form_server_empty": "Введите URL-адрес сервера.", + "login_form_server_error": "Не удалось установить соединение с сервером.", "login_password_changed_error": "Произошла ошибка при обновлении пароля", "login_password_changed_success": "Пароль успешно обновлен", "map_assets_in_bound": "{} фото", @@ -334,38 +334,38 @@ "map_location_dialog_cancel": "Отмена", "map_location_dialog_yes": "Да", "map_location_picker_page_use_location": "Это местоположение", - "map_location_service_disabled_content": "Для отображения объектов в данном месте необходимо включить службу определения местоположения. Хотите включить ее сейчас?", + "map_location_service_disabled_content": "Для отображения объектов в текущем месте необходимо включить службу определения местоположения. Включить?", "map_location_service_disabled_title": "Служба определения местоположения отключена", "map_no_assets_in_bounds": "Нет фотографий в этой области", - "map_no_location_permission_content": "Для отображения объектов из текущего местоположения необходимо разрешение на определение местоположения. Хотите ли вы разрешить его сейчас?", + "map_no_location_permission_content": "Для отображения объектов в текущем месте необходимо разрешение на определение местоположения. Предоставить разрешение?", "map_no_location_permission_title": "Доступ к местоположению отклонен", "map_settings_dark_mode": "Темный режим", "map_settings_date_range_option_all": "Все", - "map_settings_date_range_option_day": "Прошлые 24 часа", - "map_settings_date_range_option_days": "Прошлые {} дней", - "map_settings_date_range_option_year": "Прошлый год", - "map_settings_date_range_option_years": "Прошлые {} года", + "map_settings_date_range_option_day": "24 часа", + "map_settings_date_range_option_days": "{} дней", + "map_settings_date_range_option_year": "Год", + "map_settings_date_range_option_years": "{} года", "map_settings_dialog_cancel": "Отмена", "map_settings_dialog_save": "Сохранить", "map_settings_dialog_title": "Настройки карты", - "map_settings_include_show_archived": "Отображать архив", - "map_settings_include_show_partners": "Отображать снимки партнера", + "map_settings_include_show_archived": "Отображать архивированное", + "map_settings_include_show_partners": "Отображать медиа партнера", "map_settings_only_relative_range": "Период времени", "map_settings_only_show_favorites": "Показать только избранное", - "map_settings_theme_settings": "Тема карты", + "map_settings_theme_settings": "Цвет карты", "map_zoom_to_see_photos": "Уменьшение масштаба для просмотра фотографий", "memories_all_caught_up": "Это всё на сегодня", "memories_check_back_tomorrow": "Загляните завтра, чтобы увидеть больше воспоминаний", "memories_start_over": "Начать заново", "memories_swipe_to_close": "Смахните вверх, чтобы закрыть", "memories_year_ago": "Год назад", - "memories_years_ago": "{} лет назад", + "memories_years_ago": "Лет назад: {}", "monthly_title_text_date_format": "MMMM y", "motion_photos_page_title": "Динамические фото", - "multiselect_grid_edit_date_time_err_read_only": "Невозможно редактировать дату объектов только для чтения, пропуск...", - "multiselect_grid_edit_gps_err_read_only": "Невозможно редактировать местоположение объектов только для чтения, пропуск...", - "my_albums": "My albums", - "no_assets_to_show": "Объекты отсутствуют", + "multiselect_grid_edit_date_time_err_read_only": "Невозможно изменить дату файлов только для чтения, пропуск", + "multiselect_grid_edit_gps_err_read_only": "Невозможно изменить местоположение файлов только для чтения, пропуск", + "my_albums": "Мои альбомы", + "no_assets_to_show": "Медиа отсутствуют", "no_name": "Без имени", "notification_permission_dialog_cancel": "Отмена", "notification_permission_dialog_content": "Чтобы включить уведомления, перейдите в «Настройки» и выберите «Разрешить».", @@ -373,48 +373,48 @@ "notification_permission_list_tile_content": "Предоставьте разрешение на включение уведомлений", "notification_permission_list_tile_enable_button": "Включить уведомления", "notification_permission_list_tile_title": "Разрешение на уведомление", - "on_this_device": "On this device", + "on_this_device": "На этом устройстве", "partner_list_user_photos": "Фотографии {user}", "partner_list_view_all": "Посмотреть все", "partner_page_add_partner": "Добавить партнёра", - "partner_page_empty_message": "У вашего партнёра еще пока нет доступа к вашим фото", + "partner_page_empty_message": "У вашего партнёра еще нет доступа к вашим фото", "partner_page_no_more_users": "Выбраны все доступные пользователи", "partner_page_partner_add_failed": "Не удалось добавить партнёра", "partner_page_select_partner": "Выбрать партнёра", "partner_page_shared_to_title": "Поделиться с...", "partner_page_stop_sharing_content": "{} больше не сможет получить доступ к вашим фотографиям", - "partner_page_stop_sharing_title": "Закрыть доступ партнёра к вашим фото?", + "partner_page_stop_sharing_title": "Закрыть доступ к вашим фото?", "partner_page_title": "Партнёр", - "partners": "Partners", - "people": "People", + "partners": "Партнёры", + "people": "Люди", "permission_onboarding_back": "Назад", "permission_onboarding_continue_anyway": "Все равно продолжить", "permission_onboarding_get_started": "Давайте начнём", "permission_onboarding_go_to_settings": "Перейти в настройки", "permission_onboarding_grant_permission": "Предоставить разрешение", "permission_onboarding_log_out": "Выйти", - "permission_onboarding_permission_denied": "Не удалось получить доступ.", + "permission_onboarding_permission_denied": "Не удалось получить доступ. Чтобы использовать приложение, разрешите доступ к \"Фото и видео\" в настройках.", "permission_onboarding_permission_granted": "Доступ получен! Всё готово.", - "permission_onboarding_permission_limited": "Доступ к файлам ограничен. Чтобы Immich мог создавать резервные копии и управлять вашей галереей, пожалуйста, предоставьте приложению разрешение на доступ к \"Фото и видео\" в Настройках.", - "permission_onboarding_request": "Immich просит вас предоставить разрешение на доступ к вашим фото и видео", - "places": "Places", + "permission_onboarding_permission_limited": "Доступ к файлам ограничен. Чтобы Immich мог создавать резервные копии и управлять вашей галереей, пожалуйста, предоставьте приложению разрешение на доступ к \"Фото и видео\" в настройках.", + "permission_onboarding_request": "Приложению необходимо разрешение на доступ к вашим фото и видео", + "places": "Места", "preferences_settings_title": "Параметры", "profile_drawer_app_logs": "Журнал", - "profile_drawer_client_out_of_date_major": "Версия мобильного приложения устарела. Пожалуйста, обновитесь до последней основной версии.", - "profile_drawer_client_out_of_date_minor": "Версия мобильного приложения устарела. Пожалуйста, обновитесь до последней вспомогательной версии.", + "profile_drawer_client_out_of_date_major": "Версия мобильного приложения устарела. Пожалуйста, обновите его.", + "profile_drawer_client_out_of_date_minor": "Версия мобильного приложения устарела. Пожалуйста, обновите его.", "profile_drawer_client_server_up_to_date": "Клиент и сервер обновлены", "profile_drawer_documentation": "Документация", "profile_drawer_github": "GitHub", - "profile_drawer_server_out_of_date_major": "Серверная версия устарела. Пожалуйста, обновитесь до последней основной версии.", - "profile_drawer_server_out_of_date_minor": "Серверная версия устарела. Пожалуйста, обновитесь до последней вспомогательной версии.", + "profile_drawer_server_out_of_date_major": "Версия сервера устарела. Пожалуйста, обновите его.", + "profile_drawer_server_out_of_date_minor": "Версия сервера устарела. Пожалуйста, обновите его.", "profile_drawer_settings": "Настройки", "profile_drawer_sign_out": "Выйти", "profile_drawer_trash": "Корзина", - "recently_added": "Recently added", + "recently_added": "Недавно добавленные", "recently_added_page_title": "Недавно добавленные", "save_to_gallery": "Сохранить в галерею", "scaffold_body_error_occurred": "Возникла ошибка", - "search_albums": "Search albums", + "search_albums": "Поиск альбома", "search_bar_hint": "Поиск фотографий", "search_filter_apply": "Применить фильтр", "search_filter_camera": "Камера", @@ -422,22 +422,22 @@ "search_filter_camera_model": "Модель", "search_filter_camera_title": "Выберите тип камеры", "search_filter_date": "Дата", - "search_filter_date_interval": "{start} до {end}", - "search_filter_date_title": "Выберите диапазон дат", + "search_filter_date_interval": "{start} — {end}", + "search_filter_date_title": "Выберите промежуток", "search_filter_display_option_archive": "Архив", "search_filter_display_option_favorite": "Избранное", "search_filter_display_option_not_in_album": "Не в альбоме", "search_filter_display_options": "Настройки отображения", "search_filter_display_options_title": "Настройки отображения", - "search_filter_location": "Местоположение", + "search_filter_location": "Место", "search_filter_location_city": "Город", "search_filter_location_country": "Страна", "search_filter_location_state": "Регион", - "search_filter_location_title": "Выберите местонахождение", - "search_filter_media_type": "Тип носителя", + "search_filter_location_title": "Выберите место", + "search_filter_media_type": "Тип файла", "search_filter_media_type_all": "Все", "search_filter_media_type_image": "Изображения", - "search_filter_media_type_title": "Выберите тип носителя", + "search_filter_media_type_title": "Выберите тип медиа", "search_filter_media_type_video": "Видео", "search_filter_people": "Люди", "search_filter_people_title": "Выберите людей", @@ -451,7 +451,7 @@ "search_page_person_add_name_dialog_hint": "Имя", "search_page_person_add_name_dialog_save": "Сохранить", "search_page_person_add_name_dialog_title": "Добавить имя", - "search_page_person_add_name_subtitle": "Быстро найдите их по имени с помощью поиска", + "search_page_person_add_name_subtitle": "Быстро находите их по имени с помощью поиска", "search_page_person_add_name_title": "Добавить имя", "search_page_person_edit_name": "Редактировать имя", "search_page_places": "Места", @@ -473,20 +473,20 @@ "server_info_box_latest_release": "Последняя версия", "server_info_box_server_url": "URL сервера", "server_info_box_server_version": "Версия сервера", - "setting_image_viewer_help": "Полноэкранный просмотрщик сначала загружает изображение для предпросмотра в низком разрешении, затем загружает изображение в уменьшенном разрешении относительно оригинала (если включено) и в конце концов загружает оригинал (если включено).", + "setting_image_viewer_help": "При просмотре изображения сперва загружается миниатюра, затем \nуменьшенное изображение среднего качества (если включено), а затем оригинал (если включено).", "setting_image_viewer_original_subtitle": "Включите для загрузки исходного изображения в полном разрешении (большое!).\nОтключите, чтобы уменьшить объем данных (как сети, так и кэша устройства).", "setting_image_viewer_original_title": "Загружать исходное изображение", - "setting_image_viewer_preview_subtitle": "Включите для загрузки изображения среднего разрешения.\nОтключите, чтобы загружать оригинал напрямую или использовать только миниатюру.", - "setting_image_viewer_preview_title": "Загружать изображение для предварительного просмотра", + "setting_image_viewer_preview_subtitle": "Включите для загрузки изображения среднего разрешения.\nОтключите, чтобы загружать только оригинал или миниатюру.", + "setting_image_viewer_preview_title": "Загружать уменьшенное изображение", "setting_image_viewer_title": "Изображения", "setting_languages_apply": "Применить", "setting_languages_title": "Язык", "setting_notifications_notify_failures_grace_period": "Уведомлять об ошибках фонового резервного копирования: {}", - "setting_notifications_notify_hours": "{} часов", + "setting_notifications_notify_hours": "{} ч.", "setting_notifications_notify_immediately": "немедленно", - "setting_notifications_notify_minutes": "{} минут", + "setting_notifications_notify_minutes": "{} мин.", "setting_notifications_notify_never": "никогда", - "setting_notifications_notify_seconds": "{} секунд", + "setting_notifications_notify_seconds": "{} сек.", "setting_notifications_single_progress_subtitle": "Подробная информация о ходе загрузки для каждого объекта", "setting_notifications_single_progress_title": "Показать ход выполнения фонового резервного копирования", "setting_notifications_subtitle": "Настройка параметров уведомлений", @@ -503,10 +503,10 @@ "share_add_title": "Добавить название", "share_assets_selected": "{} выбрано", "share_create_album": "Создать альбом", - "shared_album_activities_input_disable": "Комментирование отключено", + "shared_album_activities_input_disable": "Комментарии отключены", "shared_album_activities_input_hint": "Скажите что-нибудь", - "shared_album_activity_remove_content": "Хотите ли Вы удалить это сообщение?", - "shared_album_activity_remove_title": "Удалить сообщение", + "shared_album_activity_remove_content": "Удалить сообщение?", + "shared_album_activity_remove_title": "Удалить", "shared_album_activity_setting_subtitle": "Разрешить другим отвечать", "shared_album_activity_setting_title": "Комментарии и лайки", "shared_album_section_people_action_error": "Ошибка при выходе/удалении из альбома", @@ -515,19 +515,19 @@ "shared_album_section_people_owner_label": "Владелец", "shared_album_section_people_title": "ЛЮДИ", "share_dialog_preparing": "Подготовка...", - "shared_link_app_bar_title": "Общие ссылки", + "shared_link_app_bar_title": "Публичные ссылки", "shared_link_clipboard_copied_massage": "Скопировано в буфер обмена", "shared_link_clipboard_text": "Ссылка: {}\nПароль: {}", - "shared_link_create_app_bar_title": "Создать ссылку общего доступа", - "shared_link_create_error": "Ошибка при создании общей ссылки", + "shared_link_create_app_bar_title": "Создать ссылку для общего доступа", + "shared_link_create_error": "Ошибка при создании публичной ссылки", "shared_link_create_info": "Разрешить всем, у кого есть ссылка, просматривать выбранные фото", "shared_link_create_submit_button": "Создать ссылку", - "shared_link_edit_allow_download": "Разрешить публичному пользователю скачивать файлы", - "shared_link_edit_allow_upload": "Разрешить публичному пользователю загружать файлы", + "shared_link_edit_allow_download": "Разрешить всем скачивать файлы", + "shared_link_edit_allow_upload": "Разрешить всем загружать файлы", "shared_link_edit_app_bar_title": "Редактировать ссылку", - "shared_link_edit_change_expiry": "Изменить срок действия доступа", + "shared_link_edit_change_expiry": "Изменить срок действия", "shared_link_edit_description": "Описание", - "shared_link_edit_description_hint": "Введите описание для общего доступа", + "shared_link_edit_description_hint": "Введите описание публичного доступа", "shared_link_edit_expire_after": "Истекает через", "shared_link_edit_expire_after_option_day": "1 день", "shared_link_edit_expire_after_option_days": "{} дней", @@ -539,10 +539,10 @@ "shared_link_edit_expire_after_option_never": "Никогда", "shared_link_edit_expire_after_option_year": "{} лет", "shared_link_edit_password": "Пароль", - "shared_link_edit_password_hint": "Введите пароль для общего доступа", + "shared_link_edit_password_hint": "Введите пароль для публичного доступа", "shared_link_edit_show_meta": "Показывать метаданные", "shared_link_edit_submit_button": "Обновить ссылку", - "shared_link_empty": "У вас нет общих ссылок", + "shared_link_empty": "У вас нет публичных ссылок", "shared_link_error_server_url_fetch": "Невозможно запросить URL с сервера", "shared_link_expired": "Срок действия истек", "shared_link_expires_day": "Истекает через {} день", @@ -551,24 +551,24 @@ "shared_link_expires_hours": "Истекает через {} часов", "shared_link_expires_minute": "Истекает через {} минуту", "shared_link_expires_minutes": "Истекает через {} минут", - "shared_link_expires_never": "Истекает ∞", + "shared_link_expires_never": "Вечная ссылка", "shared_link_expires_second": "Истекает через {} секунду", "shared_link_expires_seconds": "Истекает через {} секунд", "shared_link_individual_shared": "Индивидуальный общий доступ", "shared_link_info_chip_download": "Скачать", "shared_link_info_chip_metadata": "EXIF", "shared_link_info_chip_upload": "Загрузить", - "shared_link_manage_links": "Управление общими ссылками", + "shared_link_manage_links": "Управление публичными ссылками", "shared_link_public_album": "Публичный альбом", - "shared_links": "Shared links", + "shared_links": "Публичные ссылки", "share_done": "Готово", - "shared_with_me": "Shared with me", + "shared_with_me": "Доступные мне", "share_invite": "Пригласить в альбом", "sharing_page_album": "Общие альбомы", "sharing_page_description": "Создавайте общие альбомы, чтобы делиться фотографиями и видео с людьми в вашей сети.", "sharing_page_empty_list": "ПУСТОЙ СПИСОК", "sharing_silver_appbar_create_shared_album": "Создать общий альбом", - "sharing_silver_appbar_shared_links": "Общие ссылки", + "sharing_silver_appbar_shared_links": "Публичные ссылки", "sharing_silver_appbar_share_partner": "Поделиться с партнёром", "sync": "Синхронизировать", "sync_albums": "Синхронизировать альбомы", @@ -580,29 +580,29 @@ "tab_controller_nav_sharing": "Общие", "theme_setting_asset_list_storage_indicator_title": "Показать индикатор хранилища на плитках объектов", "theme_setting_asset_list_tiles_per_row_title": "Количество объектов в строке ({})", - "theme_setting_colorful_interface_subtitle": "Применить основной цвет на поверхность фона.", - "theme_setting_colorful_interface_title": "Красочный интерфейс", + "theme_setting_colorful_interface_subtitle": "Добавить оттенок к фону", + "theme_setting_colorful_interface_title": "Цвет фона", "theme_setting_dark_mode_switch": "Тёмная тема", - "theme_setting_image_viewer_quality_subtitle": "Настройка качества просмотра полноэкранных изображения", + "theme_setting_image_viewer_quality_subtitle": "Настройка качества просмотра изображения", "theme_setting_image_viewer_quality_title": "Качество просмотра изображений", - "theme_setting_primary_color_subtitle": "Выберите цвет для основных действий и акцентов.", + "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_theme_title": "Тема", - "theme_setting_three_stage_loading_subtitle": "Трехэтапная загрузка может повысить производительность загрузки, но вызывает значительно более высокую нагрузку на сеть", + "theme_setting_three_stage_loading_subtitle": "Трехэтапная загрузка может повысить производительность, но значительно нагружает сеть", "theme_setting_three_stage_loading_title": "Включить трехэтапную загрузку", "translated_text_options": "Опции", - "trash": "Trash", + "trash": "Корзина", "trash_emptied": "Корзина очищена", "trash_page_delete": "Удалить", "trash_page_delete_all": "Удалить все", "trash_page_empty_trash_btn": "Очистить корзину", - "trash_page_empty_trash_dialog_content": "Вы хотите очистить свою корзину? Эти объекты будут навсегда удалены из Immich.", + "trash_page_empty_trash_dialog_content": "Очистить корзину? Эти файлы будут навсегда удалены из Immich.", "trash_page_empty_trash_dialog_ok": "ОК", - "trash_page_info": "Удаленные элементы будут окончательно удалены через {} дней", - "trash_page_no_assets": "Удаленные объекты отсутствуют", + "trash_page_info": "Элементы в корзине будут окончательно удалены через {} дней", + "trash_page_no_assets": "Корзина пуста", "trash_page_restore": "Восстановить", "trash_page_restore_all": "Восстановить все", "trash_page_select_assets_btn": "Выбранные объекты", @@ -612,13 +612,13 @@ "upload_dialog_info": "Хотите создать резервную копию выбранных объектов на сервере?", "upload_dialog_ok": "Загрузить", "upload_dialog_title": "Загрузить объект", - "version_announcement_overlay_ack": "Подтверждение", + "version_announcement_overlay_ack": "Понятно", "version_announcement_overlay_release_notes": "примечания к выпуску", - "version_announcement_overlay_text_1": "Привет друг, вышел новый релиз", - "version_announcement_overlay_text_2": "пожалуйста, найдите время, чтобы посетить", - "version_announcement_overlay_text_3": " и убедитесь, что ваши настройки docker-compose и .env обновлены, чтобы предотвратить любые неправильные настройки, особенно если вы используете WatchTower или любой другой механизм, который обрабатывает обновление вашего серверного приложения автоматически.", + "version_announcement_overlay_text_1": "Привет, друг! Вышла новая версия", + "version_announcement_overlay_text_2": "пожалуйста, посетите", + "version_announcement_overlay_text_3": " и убедитесь, что ваши настройки docker-compose и .env обновлены, особенно если вы используете WatchTower или любой другой механизм, который автоматически обновляет сервер.", "version_announcement_overlay_title": "Доступна новая версия сервера \uD83C\uDF89", - "videos": "Videos", + "videos": "Видео", "viewer_remove_from_stack": "Удалить из стека", "viewer_stack_use_as_main_asset": "Использовать в качестве основного объекта", "viewer_unstack": "Разобрать стек" diff --git a/mobile/assets/i18n/sv-SE.json b/mobile/assets/i18n/sv-SE.json index 078f5780fc1af..76ec0e7e1d0eb 100644 --- a/mobile/assets/i18n/sv-SE.json +++ b/mobile/assets/i18n/sv-SE.json @@ -6,7 +6,7 @@ "action_common_save": "Spara", "action_common_select": "Välj", "action_common_update": "Uppdatera", - "add_a_name": "Add a name", + "add_a_name": "Lägg till namn", "add_to_album_bottom_sheet_added": "Tillagd till {album}", "add_to_album_bottom_sheet_already_exists": "Redan i {album}", "advanced_settings_log_level_title": "Loggnivå: {}", @@ -22,7 +22,7 @@ "advanced_settings_troubleshooting_title": "Felsökning", "album_info_card_backup_album_excluded": "EXKLUDERAD", "album_info_card_backup_album_included": "INKLUDERAD", - "albums": "Albums", + "albums": "Album", "album_thumbnail_card_item": "1 objekt", "album_thumbnail_card_items": "{} objekt", "album_thumbnail_card_shared": " · Delad", @@ -38,13 +38,13 @@ "album_viewer_appbar_share_remove": "Ta bort från album", "album_viewer_appbar_share_to": "Dela Till", "album_viewer_page_share_add_users": "Lägg till användare", - "all": "All", + "all": "Alla", "all_people_page_title": "Personer", "all_videos_page_title": "Videor", "app_bar_signout_dialog_content": "Är du säker på att du vill logga ut?", "app_bar_signout_dialog_ok": "Ja", "app_bar_signout_dialog_title": "Logga ut", - "archived": "Archived", + "archived": "Arkiverade", "archive_page_no_archived_assets": "Inga arkiverade objekt hittade", "archive_page_title": "Arkiv ({})", "asset_action_delete_err_read_only": "Kan inte ta bort skrivskyddade objekt, hoppar över", @@ -189,9 +189,9 @@ "control_bottom_app_bar_unarchive": "Avarkivera", "control_bottom_app_bar_unfavorite": "Avfavorisera", "control_bottom_app_bar_upload": "Ladda Upp", - "create_album": "Create album", + "create_album": "Skapa album", "create_album_page_untitled": "Namnlös", - "create_new": "CREATE NEW", + "create_new": "SKAPA NY", "create_shared_album_page_create": "Skapa", "create_shared_album_page_share": "Dela", "create_shared_album_page_share_add_assets": "LÄGG TILL OBJEKT", @@ -246,7 +246,7 @@ "experimental_settings_new_asset_list_title": "Aktivera experimentellt fotorutnät", "experimental_settings_subtitle": "Använd på egen risk!", "experimental_settings_title": "Experimentellt", - "favorites": "Favorites", + "favorites": "Favoriter", "favorites_page_no_favorites": "Inga favoritobjekt hittades", "favorites_page_title": "Favoriter", "filename_search": "Filnamn eller filändelse", @@ -274,8 +274,8 @@ "home_page_first_time_notice": "Om det här är första gången du använder appen, välj ett eller flera backup-album så att tidslinjen kan fyllas med foton och videor från albumen.", "home_page_share_err_local": "Kan inte dela lokalt objekt via länk, hoppar över", "home_page_upload_err_limit": "Kan bara ladda upp max 30 objekt åt gången, hoppar över", - "ignore_icloud_photos": "Ignore iCloud photos", - "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server", + "ignore_icloud_photos": "Ignorera iCloud-foton", + "ignore_icloud_photos_description": "Foton lagrade i iCloud kommer inte laddas upp till Immich-servern", "image_saved_successfully": "Bild sparad", "image_viewer_page_state_provider_download_error": "Fel Vid Nedladdning", "image_viewer_page_state_provider_download_started": "Nedladdning Påbörjad", @@ -283,7 +283,7 @@ "image_viewer_page_state_provider_share_error": "Delningsfel", "invalid_date": "Felaktigt datum", "invalid_date_format": "Felaktigt datumformat", - "library": "Library", + "library": "Bibliotek", "library_page_albums": "Album", "library_page_archive": "Arkiv", "library_page_device_albums": "Album på Enheten", @@ -364,7 +364,7 @@ "motion_photos_page_title": "Rörelsefoton", "multiselect_grid_edit_date_time_err_read_only": "Kan inte ändra datum på skrivskyddade objekt, hoppar över", "multiselect_grid_edit_gps_err_read_only": "Kan inte ändra plats på skrivskyddade objekt, hoppar över", - "my_albums": "My albums", + "my_albums": "Mina album", "no_assets_to_show": "Inga objekt att visa", "no_name": "Inget namn", "notification_permission_dialog_cancel": "Avbryt", @@ -373,7 +373,7 @@ "notification_permission_list_tile_content": "Tillåt rättighet för att slå på notiser.", "notification_permission_list_tile_enable_button": "Aktivera Notiser", "notification_permission_list_tile_title": "Notisrättighet", - "on_this_device": "On this device", + "on_this_device": "På enheten", "partner_list_user_photos": "{user}s foton", "partner_list_view_all": "Visa alla", "partner_page_add_partner": "Lägg till partner", @@ -385,8 +385,8 @@ "partner_page_stop_sharing_content": "{} kommer inte längre att komma åt dina foton.", "partner_page_stop_sharing_title": "Sluta dela dina foton?", "partner_page_title": "Partner", - "partners": "Partners", - "people": "People", + "partners": "Partner", + "people": "Människor", "permission_onboarding_back": "Bakåt", "permission_onboarding_continue_anyway": "Fortsätt ändå", "permission_onboarding_get_started": "Kom igång", @@ -397,7 +397,7 @@ "permission_onboarding_permission_granted": "Rättigheten beviljad! Du är klar.", "permission_onboarding_permission_limited": "Rättighet begränsad. För att låta Immich säkerhetskopiera och hantera hela ditt galleri, tillåt foto- och video-rättigheter i Inställningar.", "permission_onboarding_request": "Immich kräver tillstånd för att se dina foton och videor.", - "places": "Places", + "places": "Platser", "preferences_settings_title": "Inställningar", "profile_drawer_app_logs": "Loggar", "profile_drawer_client_out_of_date_major": "Mobilappen är utdaterad. Uppdatera till senaste huvudversionen.", @@ -410,11 +410,11 @@ "profile_drawer_settings": "Inställningar", "profile_drawer_sign_out": "Logga ut", "profile_drawer_trash": "Papperskorg", - "recently_added": "Recently added", + "recently_added": "Nyligen tillagda", "recently_added_page_title": "Nyligen tillagda", "save_to_gallery": "Spara i galleri", "scaffold_body_error_occurred": "Fel uppstod", - "search_albums": "Search albums", + "search_albums": "Sök i album", "search_bar_hint": "Sök bland dina foton", "search_filter_apply": "Aktivera filter", "search_filter_camera": "Kamera", @@ -560,9 +560,9 @@ "shared_link_info_chip_upload": "Ladda upp", "shared_link_manage_links": "Hantera Delade länkar", "shared_link_public_album": "Publikt album", - "shared_links": "Shared links", + "shared_links": "Delade länkar", "share_done": "Klart", - "shared_with_me": "Shared with me", + "shared_with_me": "Delade med mig", "share_invite": "Bjuder in till album", "sharing_page_album": "Delade album", "sharing_page_description": "Skapa delade album för att dela foton och video med personer i ditt nätverk.", @@ -594,7 +594,7 @@ "theme_setting_three_stage_loading_subtitle": "Trestegsladdning kan öka prestandan, men kan också leda till signifikant högre nätverksbelastning", "theme_setting_three_stage_loading_title": "Aktivera trestegsladdning", "translated_text_options": "Val", - "trash": "Trash", + "trash": "Papperskorg", "trash_emptied": "Tömd papperskorg", "trash_page_delete": "Ta Bort", "trash_page_delete_all": "Ta Bort Alla", @@ -618,7 +618,7 @@ "version_announcement_overlay_text_2": ". Ta gärna din tid att besöka ", "version_announcement_overlay_text_3": " för att se till att din docker-compose och .env-fil är uppdaterad för att undvika felkonfiguration, speciellt om du använder WatchTower eller liknande mekanism som automatiskt uppdaterar din container", "version_announcement_overlay_title": "Ny server-version finns tillgänglig \uD83C\uDF89", - "videos": "Videos", + "videos": "Videor", "viewer_remove_from_stack": "Ta bort från Stapeln", "viewer_stack_use_as_main_asset": "Använd som Huvudobjekt", "viewer_unstack": "Stapla Av" diff --git a/mobile/assets/i18n/uk-UA.json b/mobile/assets/i18n/uk-UA.json index 8bdd9aeaf2339..8f9b6370ec698 100644 --- a/mobile/assets/i18n/uk-UA.json +++ b/mobile/assets/i18n/uk-UA.json @@ -6,7 +6,7 @@ "action_common_save": "Зберегти", "action_common_select": "Вибрати", "action_common_update": "Оновити", - "add_a_name": "Add a name", + "add_a_name": "Додати ім'я", "add_to_album_bottom_sheet_added": "Додати до {album}", "add_to_album_bottom_sheet_already_exists": "Вже є в {album}", "advanced_settings_log_level_title": "Log level: {}", @@ -22,7 +22,7 @@ "advanced_settings_troubleshooting_title": "Усунення несправностей", "album_info_card_backup_album_excluded": "ВИЛУЧЕНИЙ", "album_info_card_backup_album_included": "ВКЛЮЧЕНИЙ", - "albums": "Albums", + "albums": "Альбоми", "album_thumbnail_card_item": "1 елемент", "album_thumbnail_card_items": "{} елементів", "album_thumbnail_card_shared": " · Спільний", @@ -38,13 +38,13 @@ "album_viewer_appbar_share_remove": "Видалити з альбому", "album_viewer_appbar_share_to": "Поділитися", "album_viewer_page_share_add_users": "Додати користувачів", - "all": "All", + "all": "Усі", "all_people_page_title": "Люди", "all_videos_page_title": "Відео", "app_bar_signout_dialog_content": "Ви впевнені, що бажаєте вийти з аккаунта?", "app_bar_signout_dialog_ok": "Так", "app_bar_signout_dialog_title": "Вийти з аккаунта", - "archived": "Archived", + "archived": "Архів", "archive_page_no_archived_assets": "Немає архівних елементів", "archive_page_title": "Архів ({})", "asset_action_delete_err_read_only": "Неможливо видалити елемент(и) лише для читання, пропущено", @@ -189,9 +189,9 @@ "control_bottom_app_bar_unarchive": "Розархівувати", "control_bottom_app_bar_unfavorite": "Видалити з улюблених", "control_bottom_app_bar_upload": "Завантажити", - "create_album": "Create album", + "create_album": "Створити альбом", "create_album_page_untitled": "Без назви", - "create_new": "CREATE NEW", + "create_new": "СТВОРИТИ НОВИЙ", "create_shared_album_page_create": "Створити", "create_shared_album_page_share": "Поділитися", "create_shared_album_page_share_add_assets": "ДОДАТИ ЕЛЕМЕНТИ", @@ -216,8 +216,8 @@ "delete_shared_link_dialog_title": "Видалити спільне посилання", "description_input_hint_text": "Додати опис...", "description_input_submit_error": "Помилка оновлення опису, перевірте логи для подробиць", - "download_canceled": "\nЗавантаження скасовано", - "download_complete": "\nЗавантаження закінчено", + "download_canceled": "Завантаження скасовано", + "download_complete": "Завантаження закінчено", "download_enqueue": "Завантаження поставлено в чергу", "download_error": "Помилка завантаження", "download_failed": "Завантаження не вдалося", @@ -226,7 +226,7 @@ "downloading": "Завантаження...", "downloading_media": "Завантаження медіа", "download_notfound": "Завантаження не виявлено", - "download_paused": "\nЗавантаження призупинено", + "download_paused": "Завантаження призупинено", "download_started": "Завантаження розпочато", "download_sucess": "Успішне завантаження", "download_sucess_android": "Медіафайли завантажено в DCIM/Immich", @@ -246,7 +246,7 @@ "experimental_settings_new_asset_list_title": "Експериментальний макет знімків", "experimental_settings_subtitle": "На власний ризик!", "experimental_settings_title": "Експериментальні", - "favorites": "Favorites", + "favorites": "Вибране", "favorites_page_no_favorites": "Немає улюблених елементів", "favorites_page_title": "Улюблені", "filename_search": "Ім'я або розширення файлу", @@ -274,8 +274,8 @@ "home_page_first_time_notice": "Якщо ви вперше користуєтеся програмою, переконайтеся, що ви вибрали альбоми для резервування, щоб могти заповнювати хронологію знімків та відео в альбомах.", "home_page_share_err_local": "Неможливо поділитися локальними елементами через посилання, пропущено", "home_page_upload_err_limit": "Можна вантажити не більше 30 елементів водночас, пропущено", - "ignore_icloud_photos": "Ignore iCloud photos", - "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server", + "ignore_icloud_photos": "Пропускати файли з iCloud", + "ignore_icloud_photos_description": "Не завантажувати файли в Immich, якщо вони зберігаються в iCloud", "image_saved_successfully": "Зображення збережено", "image_viewer_page_state_provider_download_error": "Помилка завантаження", "image_viewer_page_state_provider_download_started": "Завантаження почалося", @@ -283,7 +283,7 @@ "image_viewer_page_state_provider_share_error": "Помилка спільного доступу", "invalid_date": "Недійсна дата", "invalid_date_format": "Недійсний формат дати", - "library": "Library", + "library": "Бібліотека", "library_page_albums": "Альбоми", "library_page_archive": "Архів", "library_page_device_albums": "Альбоми на пристрої", @@ -364,7 +364,7 @@ "motion_photos_page_title": "Рухомі Знімки", "multiselect_grid_edit_date_time_err_read_only": "Неможливо редагувати дату елементів лише для читання, пропущено", "multiselect_grid_edit_gps_err_read_only": "Неможливо редагувати місцезнаходження елементів лише для читання, пропущено", - "my_albums": "My albums", + "my_albums": "Мої альбоми", "no_assets_to_show": "Елементи відсутні", "no_name": "Без імені", "notification_permission_dialog_cancel": "Скасувати", @@ -373,7 +373,7 @@ "notification_permission_list_tile_content": "Надати дозвіл для сповіщень.", "notification_permission_list_tile_enable_button": "Увімкнути Сповіщення", "notification_permission_list_tile_title": "Дозвіл на Сповіщення", - "on_this_device": "On this device", + "on_this_device": "На цьому пристрої", "partner_list_user_photos": "Фотографії {user}", "partner_list_view_all": "Переглянути усі", "partner_page_add_partner": "Додати партнера", @@ -385,8 +385,8 @@ "partner_page_stop_sharing_content": "{} втратить доступ до ваших знімків.", "partner_page_stop_sharing_title": "Припинити надання ваших знімків?", "partner_page_title": "Партнер", - "partners": "Partners", - "people": "People", + "partners": "\nПартнери", + "people": "Люди", "permission_onboarding_back": "Назад", "permission_onboarding_continue_anyway": "Все одно продовжити", "permission_onboarding_get_started": "Розпочати", @@ -397,7 +397,7 @@ "permission_onboarding_permission_granted": "Доступ надано! Все готово.", "permission_onboarding_permission_limited": "Обмежений доступ. Аби дозволити Immich резервне копіювання та керування вашою галереєю, надайте доступ до знімків та відео у Налаштуваннях", "permission_onboarding_request": "Immich потребує доступу до ваших знімків та відео.", - "places": "Places", + "places": "Місця", "preferences_settings_title": "Параметри", "profile_drawer_app_logs": "Журнал", "profile_drawer_client_out_of_date_major": "Мобільний додаток застарів. Будь ласка, оновіть до останньої мажорної версії.", @@ -410,11 +410,11 @@ "profile_drawer_settings": "Налаштування", "profile_drawer_sign_out": "Вийти", "profile_drawer_trash": "Кошик", - "recently_added": "Recently added", + "recently_added": "Нещодавно додані", "recently_added_page_title": "Нещодавні", "save_to_gallery": "Зберегти в галерею", "scaffold_body_error_occurred": "Виникла помилка", - "search_albums": "Search albums", + "search_albums": "Пошук альбому", "search_bar_hint": "Шукати ваші знімки", "search_filter_apply": "Застосувати фільтр", "search_filter_camera": "Камера", @@ -560,9 +560,9 @@ "shared_link_info_chip_upload": "Завантажити", "shared_link_manage_links": "Керування спільними посиланнями", "shared_link_public_album": "Публічний альбом", - "shared_links": "Shared links", + "shared_links": "Публічні посилання", "share_done": "Готово", - "shared_with_me": "Shared with me", + "shared_with_me": "Доступні мені", "share_invite": "Запросити в альбом", "sharing_page_album": "Спільні альбоми", "sharing_page_description": "Створюйте спільні альбоми, щоб ділитися знімками та відео з людьми у вашій мережі.", @@ -594,7 +594,7 @@ "theme_setting_three_stage_loading_subtitle": "Триетапне завантаження може підвищити продуктивність завантаження, але спричинить значно більше навантаження на мережу", "theme_setting_three_stage_loading_title": "Увімкнути триетапне завантаження", "translated_text_options": "Налаштування", - "trash": "Trash", + "trash": "Кошик", "trash_emptied": "Кошик очищений", "trash_page_delete": "Видалити", "trash_page_delete_all": "Видалити усі", @@ -618,7 +618,7 @@ "version_announcement_overlay_text_2": "знайдіть хвильку навідатися на ", "version_announcement_overlay_text_3": "і переконайтеся, що ваші налаштування docker-compose та .env оновлені, аби запобігти будь-якій неправильній конфігурації, особливо, якщо ви використовуєте WatchTower або інший механізм, для автоматичних оновлень вашої серверної частини.", "version_announcement_overlay_title": "Доступна нова версія сервера \uD83C\uDF89", - "videos": "Videos", + "videos": "Відео", "viewer_remove_from_stack": "Видалити зі стеку", "viewer_stack_use_as_main_asset": "Використовувати як основний елементи", "viewer_unstack": "Розібрати стек" diff --git a/mobile/assets/i18n/vi-VN.json b/mobile/assets/i18n/vi-VN.json index c77f9427f1309..0a26e6dd70fc6 100644 --- a/mobile/assets/i18n/vi-VN.json +++ b/mobile/assets/i18n/vi-VN.json @@ -38,7 +38,7 @@ "album_viewer_appbar_share_remove": "Xoá khỏi album", "album_viewer_appbar_share_to": "Chia sẻ với", "album_viewer_page_share_add_users": "Thêm người dùng", - "all": "All", + "all": "Tất cả", "all_people_page_title": "Mọi người", "all_videos_page_title": "Video", "app_bar_signout_dialog_content": "Bạn có muốn đăng xuất?", diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index 567406aef0df2..55d8e0fa00ac4 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -48,7 +48,7 @@ PODS: - flutter_udid (0.0.1): - Flutter - SAMKeychain - - flutter_web_auth (0.5.0): + - flutter_web_auth (0.6.0): - Flutter - fluttertoast (0.0.2): - Flutter @@ -202,12 +202,12 @@ SPEC CHECKSUMS: flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086 flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778 flutter_udid: a2482c67a61b9c806ef59dd82ed8d007f1b7ac04 - flutter_web_auth: c25208760459cec375a3c39f6a8759165ca0fa4d + flutter_web_auth: acc15a8fd7bba796a933c724a6dffc3d00f07c27 fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c geolocator_apple: 6cbaf322953988e009e5ecb481f07efece75c450 image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 - isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073 + isar_flutter_libs: fdf730ca925d05687f36d7f1d355e482529ed097 MapLibre: 620fc933c1d6029b33738c905c1490d024e5d4ef maplibre_gl: a2efec727dd340e4c65e26d2b03b584f14881fd9 package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index 076edc078339f..f01807716e0df 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -401,7 +401,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 179; + CURRENT_PROJECT_VERSION = 184; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -543,7 +543,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 179; + CURRENT_PROJECT_VERSION = 184; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -571,7 +571,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 179; + CURRENT_PROJECT_VERSION = 184; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; diff --git a/mobile/ios/Runner/Info.plist b/mobile/ios/Runner/Info.plist index 4ed247245a6d9..2617c7f96f208 100644 --- a/mobile/ios/Runner/Info.plist +++ b/mobile/ios/Runner/Info.plist @@ -58,11 +58,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.117.0 + 1.120.2 CFBundleSignature ???? CFBundleVersion - 179 + 184 FLTEnableImpeller ITSAppUsesNonExemptEncryption diff --git a/mobile/ios/fastlane/Fastfile b/mobile/ios/fastlane/Fastfile index d259a3781e6d1..c4e0f6ca5cf87 100644 --- a/mobile/ios/fastlane/Fastfile +++ b/mobile/ios/fastlane/Fastfile @@ -19,7 +19,7 @@ platform :ios do desc "iOS Release" lane :release do increment_version_number( - version_number: "1.118.2" + version_number: "1.120.2" ) increment_build_number( build_number: latest_testflight_build_number + 1, diff --git a/mobile/lib/constants/immich_colors.dart b/mobile/lib/constants/immich_colors.dart index 6f6d1a6a31e88..a49e783602b4d 100644 --- a/mobile/lib/constants/immich_colors.dart +++ b/mobile/lib/constants/immich_colors.dart @@ -19,6 +19,9 @@ const String defaultColorPresetName = "indigo"; const Color immichBrandColorLight = Color(0xFF4150AF); const Color immichBrandColorDark = Color(0xFFACCBFA); +const Color whiteOpacity75 = Color.fromARGB((0.75 * 255) ~/ 1, 255, 255, 255); +const Color blackOpacity90 = Color.fromARGB((0.90 * 255) ~/ 1, 0, 0, 0); +const Color red400 = Color(0xFFEF5350); final Map _themePresetsMap = { ImmichColorPreset.indigo: ImmichTheme( diff --git a/mobile/lib/entities/album.entity.g.dart b/mobile/lib/entities/album.entity.g.dart index 11046ec1e0d67..b1e322e397cb2 100644 --- a/mobile/lib/entities/album.entity.g.dart +++ b/mobile/lib/entities/album.entity.g.dart @@ -131,7 +131,7 @@ const AlbumSchema = CollectionSchema( getId: _albumGetId, getLinks: _albumGetLinks, attach: _albumAttach, - version: '3.1.0+1', + version: '3.1.8', ); int _albumEstimateSize( diff --git a/mobile/lib/entities/android_device_asset.entity.g.dart b/mobile/lib/entities/android_device_asset.entity.g.dart index 9b1eef0ae59d7..eaa7658565f1c 100644 --- a/mobile/lib/entities/android_device_asset.entity.g.dart +++ b/mobile/lib/entities/android_device_asset.entity.g.dart @@ -49,7 +49,7 @@ const AndroidDeviceAssetSchema = CollectionSchema( getId: _androidDeviceAssetGetId, getLinks: _androidDeviceAssetGetLinks, attach: _androidDeviceAssetAttach, - version: '3.1.0+1', + version: '3.1.8', ); int _androidDeviceAssetEstimateSize( diff --git a/mobile/lib/entities/asset.entity.dart b/mobile/lib/entities/asset.entity.dart index 8e2d9c84d5d1e..182c10307fdef 100644 --- a/mobile/lib/entities/asset.entity.dart +++ b/mobile/lib/entities/asset.entity.dart @@ -524,7 +524,8 @@ bool isRotated270CW(int orientation) { /// Returns `true` if this [Asset] is flipped 90° or 270° clockwise bool isFlipped(AssetResponseDto response) { - final int orientation = response.exifInfo?.orientation?.toInt() ?? 0; + final int orientation = + int.tryParse(response.exifInfo?.orientation ?? '0') ?? 0; return orientation != 0 && (isRotated90CW(orientation) || isRotated270CW(orientation)); } diff --git a/mobile/lib/entities/asset.entity.g.dart b/mobile/lib/entities/asset.entity.g.dart index 23bf23604635d..07eee4825e0bd 100644 --- a/mobile/lib/entities/asset.entity.g.dart +++ b/mobile/lib/entities/asset.entity.g.dart @@ -180,7 +180,7 @@ const AssetSchema = CollectionSchema( getId: _assetGetId, getLinks: _assetGetLinks, attach: _assetAttach, - version: '3.1.0+1', + version: '3.1.8', ); int _assetEstimateSize( diff --git a/mobile/lib/entities/backup_album.entity.g.dart b/mobile/lib/entities/backup_album.entity.g.dart index 7fb6c0e03b6b2..23d00e43cadbe 100644 --- a/mobile/lib/entities/backup_album.entity.g.dart +++ b/mobile/lib/entities/backup_album.entity.g.dart @@ -45,7 +45,7 @@ const BackupAlbumSchema = CollectionSchema( getId: _backupAlbumGetId, getLinks: _backupAlbumGetLinks, attach: _backupAlbumAttach, - version: '3.1.0+1', + version: '3.1.8', ); int _backupAlbumEstimateSize( diff --git a/mobile/lib/entities/duplicated_asset.entity.g.dart b/mobile/lib/entities/duplicated_asset.entity.g.dart index 28faa05b6d0f9..8965d47c97e33 100644 --- a/mobile/lib/entities/duplicated_asset.entity.g.dart +++ b/mobile/lib/entities/duplicated_asset.entity.g.dart @@ -34,7 +34,7 @@ const DuplicatedAssetSchema = CollectionSchema( getId: _duplicatedAssetGetId, getLinks: _duplicatedAssetGetLinks, attach: _duplicatedAssetAttach, - version: '3.1.0+1', + version: '3.1.8', ); int _duplicatedAssetEstimateSize( diff --git a/mobile/lib/entities/etag.entity.g.dart b/mobile/lib/entities/etag.entity.g.dart index 5327f6041afca..afabca4aeaa4e 100644 --- a/mobile/lib/entities/etag.entity.g.dart +++ b/mobile/lib/entities/etag.entity.g.dart @@ -58,7 +58,7 @@ const ETagSchema = CollectionSchema( getId: _eTagGetId, getLinks: _eTagGetLinks, attach: _eTagAttach, - version: '3.1.0+1', + version: '3.1.8', ); int _eTagEstimateSize( diff --git a/mobile/lib/entities/exif_info.entity.g.dart b/mobile/lib/entities/exif_info.entity.g.dart index 016f6d71260d0..015983abf289f 100644 --- a/mobile/lib/entities/exif_info.entity.g.dart +++ b/mobile/lib/entities/exif_info.entity.g.dart @@ -109,7 +109,7 @@ const ExifInfoSchema = CollectionSchema( getId: _exifInfoGetId, getLinks: _exifInfoGetLinks, attach: _exifInfoAttach, - version: '3.1.0+1', + version: '3.1.8', ); int _exifInfoEstimateSize( diff --git a/mobile/lib/entities/ios_device_asset.entity.g.dart b/mobile/lib/entities/ios_device_asset.entity.g.dart index 6ecf9f0b73098..ffed338c916fb 100644 --- a/mobile/lib/entities/ios_device_asset.entity.g.dart +++ b/mobile/lib/entities/ios_device_asset.entity.g.dart @@ -66,7 +66,7 @@ const IOSDeviceAssetSchema = CollectionSchema( getId: _iOSDeviceAssetGetId, getLinks: _iOSDeviceAssetGetLinks, attach: _iOSDeviceAssetAttach, - version: '3.1.0+1', + version: '3.1.8', ); int _iOSDeviceAssetEstimateSize( diff --git a/mobile/lib/entities/logger_message.entity.g.dart b/mobile/lib/entities/logger_message.entity.g.dart index 50c7fcf8ed281..e292e7173a48d 100644 --- a/mobile/lib/entities/logger_message.entity.g.dart +++ b/mobile/lib/entities/logger_message.entity.g.dart @@ -60,7 +60,7 @@ const LoggerMessageSchema = CollectionSchema( getId: _loggerMessageGetId, getLinks: _loggerMessageGetLinks, attach: _loggerMessageAttach, - version: '3.1.0+1', + version: '3.1.8', ); int _loggerMessageEstimateSize( diff --git a/mobile/lib/entities/store.entity.g.dart b/mobile/lib/entities/store.entity.g.dart index eb8fa62f4078d..7d3210ff85a7a 100644 --- a/mobile/lib/entities/store.entity.g.dart +++ b/mobile/lib/entities/store.entity.g.dart @@ -39,7 +39,7 @@ const StoreValueSchema = CollectionSchema( getId: _storeValueGetId, getLinks: _storeValueGetLinks, attach: _storeValueAttach, - version: '3.1.0+1', + version: '3.1.8', ); int _storeValueEstimateSize( diff --git a/mobile/lib/entities/user.entity.g.dart b/mobile/lib/entities/user.entity.g.dart index a0ecc4705c738..a7aaee44bf164 100644 --- a/mobile/lib/entities/user.entity.g.dart +++ b/mobile/lib/entities/user.entity.g.dart @@ -129,7 +129,7 @@ const UserSchema = CollectionSchema( getId: _userGetId, getLinks: _userGetLinks, attach: _userAttach, - version: '3.1.0+1', + version: '3.1.8', ); int _userEstimateSize( diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 48bc936a8276d..3b582e336ca17 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -11,6 +11,7 @@ import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/utils/download.dart'; +import 'package:intl/date_symbol_data_local.dart'; import 'package:timezone/data/latest.dart'; import 'package:immich_mobile/constants/locales.dart'; import 'package:immich_mobile/services/background.service.dart'; @@ -56,6 +57,7 @@ void main() async { Future initApp() async { await EasyLocalization.ensureInitialized(); + await initializeDateFormatting(); if (kReleaseMode && Platform.isAndroid) { try { diff --git a/mobile/lib/models/backup/current_upload_asset.model.dart b/mobile/lib/models/backup/current_upload_asset.model.dart index 9a761c9e4a507..787f1172697a4 100644 --- a/mobile/lib/models/backup/current_upload_asset.model.dart +++ b/mobile/lib/models/backup/current_upload_asset.model.dart @@ -18,6 +18,9 @@ class CurrentUploadAsset { this.iCloudAsset, }); + @pragma('vm:prefer-inline') + bool get isIcloudAsset => iCloudAsset != null && iCloudAsset!; + CurrentUploadAsset copyWith({ String? id, DateTime? fileCreatedAt, diff --git a/mobile/lib/models/search/search_result.model.dart b/mobile/lib/models/search/search_result.model.dart new file mode 100644 index 0000000000000..f51353ad613dd --- /dev/null +++ b/mobile/lib/models/search/search_result.model.dart @@ -0,0 +1,37 @@ +import 'package:collection/collection.dart'; + +import 'package:immich_mobile/entities/asset.entity.dart'; + +class SearchResult { + final List assets; + final int? nextPage; + + SearchResult({ + required this.assets, + this.nextPage, + }); + + SearchResult copyWith({ + List? assets, + int? nextPage, + }) { + return SearchResult( + assets: assets ?? this.assets, + nextPage: nextPage ?? this.nextPage, + ); + } + + @override + String toString() => 'SearchResult(assets: $assets, nextPage: $nextPage)'; + + @override + bool operator ==(covariant SearchResult other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return listEquals(other.assets, assets) && other.nextPage == nextPage; + } + + @override + int get hashCode => assets.hashCode ^ nextPage.hashCode; +} diff --git a/mobile/lib/pages/search/search.page.dart b/mobile/lib/pages/search/search.page.dart index 60e61da4cc5d5..21d0e8f5c2dc3 100644 --- a/mobile/lib/pages/search/search.page.dart +++ b/mobile/lib/pages/search/search.page.dart @@ -58,23 +58,22 @@ class SearchPage extends HookConsumerWidget { final mediaTypeCurrentFilterWidget = useState(null); final displayOptionCurrentFilterWidget = useState(null); - final currentPage = useState(1); - final searchProvider = ref.watch(paginatedSearchProvider); - final searchResultCount = useState(0); + final isSearching = useState(false); search() async { if (prefilter == null && filter.value == previousFilter.value) return; + isSearching.value = true; ref.watch(paginatedSearchProvider.notifier).clear(); - - currentPage.value = 1; - - final searchResult = await ref - .watch(paginatedSearchProvider.notifier) - .getNextPage(filter.value, currentPage.value); - + await ref.watch(paginatedSearchProvider.notifier).search(filter.value); previousFilter.value = filter.value; - searchResultCount.value = searchResult.length; + isSearching.value = false; + } + + loadMoreSearchResult() async { + isSearching.value = true; + await ref.watch(paginatedSearchProvider.notifier).search(filter.value); + isSearching.value = false; } searchPrefilter() { @@ -97,20 +96,16 @@ class SearchPage extends HookConsumerWidget { useEffect( () { + Future.microtask( + () => ref.invalidate(paginatedSearchProvider), + ); searchPrefilter(); + return null; }, [], ); - loadMoreSearchResult() async { - currentPage.value += 1; - final searchResult = await ref - .watch(paginatedSearchProvider.notifier) - .getNextPage(filter.value, currentPage.value); - searchResultCount.value = searchResult.length; - } - showPeoplePicker() { handleOnSelect(Set value) { filter.value = filter.value.copyWith( @@ -192,7 +187,7 @@ class SearchPage extends HookConsumerWidget { showFilterBottomSheet( context: context, isScrollControlled: true, - isDismissible: false, + isDismissible: true, child: FilterBottomSheetScaffold( title: 'search_filter_location_title'.tr(), onSearch: search, @@ -243,7 +238,7 @@ class SearchPage extends HookConsumerWidget { showFilterBottomSheet( context: context, isScrollControlled: true, - isDismissible: false, + isDismissible: true, child: FilterBottomSheetScaffold( title: 'search_filter_camera_title'.tr(), onSearch: search, @@ -465,41 +460,6 @@ class SearchPage extends HookConsumerWidget { search(); } - buildSearchResult() { - return switch (searchProvider) { - AsyncData() => Expanded( - child: Padding( - padding: const EdgeInsets.only(top: 8.0), - child: NotificationListener( - onNotification: (notification) { - final metrics = notification.metrics; - final shouldLoadMore = searchResultCount.value > 75; - if (metrics.pixels >= metrics.maxScrollExtent && - shouldLoadMore) { - loadMoreSearchResult(); - } - return true; - }, - child: MultiselectGrid( - renderListProvider: paginatedSearchRenderListProvider, - archiveEnabled: true, - deleteEnabled: true, - editEnabled: true, - favoriteEnabled: true, - stackEnabled: false, - emptyIndicator: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: SearchEmptyContent(), - ), - ), - ), - ), - ), - AsyncError(:final error) => Text('Error: $error'), - _ => const Expanded(child: Center(child: CircularProgressIndicator())), - }; - } - return Scaffold( resizeToAvoidBottomInset: true, appBar: AppBar( @@ -635,13 +595,67 @@ class SearchPage extends HookConsumerWidget { ), ), ), - buildSearchResult(), + SearchResultGrid( + onScrollEnd: loadMoreSearchResult, + isSearching: isSearching.value, + ), ], ), ); } } +class SearchResultGrid extends StatelessWidget { + final VoidCallback onScrollEnd; + final bool isSearching; + + const SearchResultGrid({ + super.key, + required this.onScrollEnd, + this.isSearching = false, + }); + + @override + Widget build(BuildContext context) { + return Expanded( + child: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: NotificationListener( + onNotification: (notification) { + final isBottomSheetNotification = notification.context + ?.findAncestorWidgetOfExactType< + DraggableScrollableSheet>() != + null; + + final metrics = notification.metrics; + final isVerticalScroll = metrics.axis == Axis.vertical; + + if (metrics.pixels >= metrics.maxScrollExtent && + isVerticalScroll && + !isBottomSheetNotification) { + onScrollEnd(); + } + + return true; + }, + child: MultiselectGrid( + renderListProvider: paginatedSearchRenderListProvider, + archiveEnabled: true, + deleteEnabled: true, + editEnabled: true, + favoriteEnabled: true, + stackEnabled: false, + emptyIndicator: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: !isSearching ? SearchEmptyContent() : SizedBox.shrink(), + ), + ), + ), + ), + ); + } +} + class SearchEmptyContent extends StatelessWidget { const SearchEmptyContent({super.key}); diff --git a/mobile/lib/providers/asset.provider.dart b/mobile/lib/providers/asset.provider.dart index c7e75df79b27f..3855a00b76a61 100644 --- a/mobile/lib/providers/asset.provider.dart +++ b/mobile/lib/providers/asset.provider.dart @@ -84,34 +84,48 @@ class AssetNotifier extends StateNotifier { _deleteInProgress = true; state = true; try { + // Filter the assets based on the backed-up status final assets = onlyBackedUp ? deleteAssets.where((e) => e.storage == AssetState.merged) : deleteAssets; + + if (assets.isEmpty) { + return false; // No assets to delete + } + + // Proceed with local deletion of the filtered assets final localDeleted = await _deleteLocalAssets(assets); + if (localDeleted.isNotEmpty) { - final localOnlyIds = deleteAssets + final localOnlyIds = assets .where((e) => e.storage == AssetState.local) .map((e) => e.id) .toList(); - // Update merged assets to remote only + + // Update merged assets to remote-only final mergedAssets = - deleteAssets.where((e) => e.storage == AssetState.merged).map((e) { + assets.where((e) => e.storage == AssetState.merged).map((e) { e.localId = null; return e; }).toList(); + + // Update the local database await _db.writeTxn(() async { if (mergedAssets.isNotEmpty) { - await _db.assets.putAll(mergedAssets); + await _db.assets + .putAll(mergedAssets); // Use the filtered merged assets } await _db.exifInfos.deleteAll(localOnlyIds); await _db.assets.deleteAll(localOnlyIds); }); + return true; } } finally { _deleteInProgress = false; state = false; } + return false; } diff --git a/mobile/lib/providers/authentication.provider.dart b/mobile/lib/providers/authentication.provider.dart index 1fe7db5d46f42..60e31d707e6aa 100644 --- a/mobile/lib/providers/authentication.provider.dart +++ b/mobile/lib/providers/authentication.provider.dart @@ -41,6 +41,8 @@ class AuthenticationNotifier extends StateNotifier { _ref; final _log = Logger("AuthenticationNotifier"); + static const Duration _timeoutDuration = Duration(seconds: 7); + Future login( String email, String password, @@ -102,12 +104,15 @@ class AuthenticationNotifier extends StateNotifier { await _apiService.authenticationApi .logout() + .timeout(_timeoutDuration) .then((_) => log.info("Logout was successful for $userEmail")) .onError( (error, stackTrace) => log.severe("Logout failed for $userEmail", error, stackTrace), ); - + } catch (e, stack) { + log.severe('Logout failed', e, stack); + } finally { await Future.wait([ clearAssetsAndAlbums(_db), Store.delete(StoreKey.currentUser), @@ -125,8 +130,6 @@ class AuthenticationNotifier extends StateNotifier { shouldChangePassword: false, isAuthenticated: false, ); - } catch (e, stack) { - log.severe('Logout failed', e, stack); } } @@ -168,10 +171,8 @@ class AuthenticationNotifier extends StateNotifier { UserPreferencesResponseDto? userPreferences; try { final responses = await Future.wait([ - _apiService.usersApi.getMyUser().timeout(const Duration(seconds: 7)), - _apiService.usersApi - .getMyPreferences() - .timeout(const Duration(seconds: 7)), + _apiService.usersApi.getMyUser().timeout(_timeoutDuration), + _apiService.usersApi.getMyPreferences().timeout(_timeoutDuration), ]); userResponse = responses[0] as UserAdminResponseDto; userPreferences = responses[1] as UserPreferencesResponseDto; diff --git a/mobile/lib/providers/partner.provider.dart b/mobile/lib/providers/partner.provider.dart index 6ea335979f6f6..bf638ae355cea 100644 --- a/mobile/lib/providers/partner.provider.dart +++ b/mobile/lib/providers/partner.provider.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/providers/album/suggested_shared_users.provider.dart'; import 'package:immich_mobile/services/partner.service.dart'; @@ -9,9 +10,19 @@ import 'package:isar/isar.dart'; class PartnerSharedWithNotifier extends StateNotifier> { PartnerSharedWithNotifier(Isar db, this._ps) : super([]) { - final query = db.users.filter().isPartnerSharedWithEqualTo(true); - query.findAll().then((partners) => state = partners); - query.watch().listen((partners) => state = partners); + Function eq = const ListEquality().equals; + final query = db.users.filter().isPartnerSharedWithEqualTo(true).sortById(); + query.findAll().then((partners) { + if (!eq(state, partners)) { + state = partners; + } + }).then((_) { + query.watch().listen((partners) { + if (!eq(state, partners)) { + state = partners; + } + }); + }); } Future updatePartner(User partner, {required bool inTimeline}) { @@ -31,9 +42,19 @@ final partnerSharedWithProvider = class PartnerSharedByNotifier extends StateNotifier> { PartnerSharedByNotifier(Isar db) : super([]) { - final query = db.users.filter().isPartnerSharedByEqualTo(true); - query.findAll().then((partners) => state = partners); - streamSub = query.watch().listen((partners) => state = partners); + Function eq = const ListEquality().equals; + final query = db.users.filter().isPartnerSharedByEqualTo(true).sortById(); + query.findAll().then((partners) { + if (!eq(state, partners)) { + state = partners; + } + }).then((_) { + streamSub = query.watch().listen((partners) { + if (!eq(state, partners)) { + state = partners; + } + }); + }); } late final StreamSubscription> streamSub; diff --git a/mobile/lib/providers/search/paginated_search.provider.dart b/mobile/lib/providers/search/paginated_search.provider.dart index abf711f0ad691..270f1148e8fb6 100644 --- a/mobile/lib/providers/search/paginated_search.provider.dart +++ b/mobile/lib/providers/search/paginated_search.provider.dart @@ -1,46 +1,39 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/models/search/search_result.model.dart'; import 'package:immich_mobile/providers/asset_viewer/render_list.provider.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/services/search.service.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'paginated_search.provider.g.dart'; -@riverpod -class PaginatedSearch extends _$PaginatedSearch { - Future?> _search(SearchFilter filter, int page) async { - final service = ref.read(searchServiceProvider); - final result = await service.search(filter, page); - - return result; - } +final paginatedSearchProvider = + StateNotifierProvider( + (ref) => PaginatedSearchNotifier(ref.watch(searchServiceProvider)), +); - @override - Future> build() async { - return []; - } +class PaginatedSearchNotifier extends StateNotifier { + final SearchService _searchService; - Future> getNextPage(SearchFilter filter, int nextPage) async { - state = const AsyncValue.loading(); + PaginatedSearchNotifier(this._searchService) + : super(SearchResult(assets: [], nextPage: 1)); - final newState = await AsyncValue.guard(() async { - final assets = await _search(filter, nextPage); + search(SearchFilter filter) async { + if (state.nextPage == null) return; - if (assets != null) { - return [...?state.value, ...assets]; - } - }); + final result = await _searchService.search(filter, state.nextPage!); - state = newState.valueOrNull == null - ? const AsyncValue.data([]) - : AsyncValue.data(newState.value!); + if (result == null) return; - return newState.valueOrNull ?? []; + state = SearchResult( + assets: [...state.assets, ...result.assets], + nextPage: result.nextPage, + ); } clear() { - state = const AsyncValue.data([]); + state = SearchResult(assets: [], nextPage: 1); } } @@ -48,15 +41,11 @@ class PaginatedSearch extends _$PaginatedSearch { AsyncValue paginatedSearchRenderList( PaginatedSearchRenderListRef ref, ) { - final assets = ref.watch(paginatedSearchProvider).value; + final result = ref.watch(paginatedSearchProvider); - if (assets != null) { - return ref.watch( - renderListProviderWithGrouping( - (assets, GroupAssetsBy.none), - ), - ); - } else { - return const AsyncValue.loading(); - } + return ref.watch( + renderListProviderWithGrouping( + (result.assets, GroupAssetsBy.none), + ), + ); } diff --git a/mobile/lib/providers/search/paginated_search.provider.g.dart b/mobile/lib/providers/search/paginated_search.provider.g.dart index 3357be7776450..cdf8cdd741a8d 100644 --- a/mobile/lib/providers/search/paginated_search.provider.g.dart +++ b/mobile/lib/providers/search/paginated_search.provider.g.dart @@ -7,7 +7,7 @@ part of 'paginated_search.provider.dart'; // ************************************************************************** String _$paginatedSearchRenderListHash() => - r'c2cc2381ee6ea8f8e08d6d4c1289bbf0c6b9647e'; + r'4585c832106b16b6d294055f47bbbe83e0802846'; /// See also [paginatedSearchRenderList]. @ProviderFor(paginatedSearchRenderList) @@ -24,21 +24,5 @@ final paginatedSearchRenderListProvider = typedef PaginatedSearchRenderListRef = AutoDisposeProviderRef>; -String _$paginatedSearchHash() => r'8312f358261368cf2b5572b839fdd8f8fbe9a62e'; - -/// See also [PaginatedSearch]. -@ProviderFor(PaginatedSearch) -final paginatedSearchProvider = - AutoDisposeAsyncNotifierProvider>.internal( - PaginatedSearch.new, - name: r'paginatedSearchProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$paginatedSearchHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$PaginatedSearch = AutoDisposeAsyncNotifier>; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/mobile/lib/services/album.service.dart b/mobile/lib/services/album.service.dart index 53a65e2869aea..1a2370591658f 100644 --- a/mobile/lib/services/album.service.dart +++ b/mobile/lib/services/album.service.dart @@ -76,10 +76,16 @@ class AlbumService { final Stopwatch sw = Stopwatch()..start(); bool changes = false; try { - final List excludedIds = await _backupAlbumRepository - .getIdsBySelection(BackupSelection.exclude); - final List selectedIds = await _backupAlbumRepository - .getIdsBySelection(BackupSelection.select); + final (selectedIds, excludedIds, onDevice) = await ( + _backupAlbumRepository + .getIdsBySelection(BackupSelection.select) + .then((value) => value.toSet()), + _backupAlbumRepository + .getIdsBySelection(BackupSelection.exclude) + .then((value) => value.toSet()), + _albumMediaRepository.getAll() + ).wait; + _log.info("Found ${onDevice.length} device albums"); if (selectedIds.isEmpty) { final numLocal = await _albumRepository.count(local: true); if (numLocal > 0) { @@ -87,8 +93,6 @@ class AlbumService { } return false; } - final List onDevice = await _albumMediaRepository.getAll(); - _log.info("Found ${onDevice.length} device albums"); Set? excludedAssets; if (excludedIds.isNotEmpty) { if (Platform.isIOS) { @@ -108,22 +112,19 @@ class AlbumService { "Ignoring ${excludedIds.length} excluded albums resulting in ${onDevice.length} device albums", ); } - final hasAll = selectedIds - .map( - (id) => onDevice.firstWhereOrNull((album) => album.localId == id), - ) - .whereNotNull() - .any((a) => a.isAll); + + final allAlbum = onDevice.firstWhereOrNull((album) => album.isAll); + final hasAll = allAlbum != null && selectedIds.contains(allAlbum.localId); if (hasAll) { if (Platform.isAndroid) { // remove the virtual "Recent" album and keep and individual albums // on Android, the virtual "Recent" `lastModified` value is always null - onDevice.removeWhere((e) => e.isAll); + onDevice.removeWhere((album) => album.isAll); _log.info("'Recents' is selected, keeping all individual albums"); } } else { // keep only the explicitly selected albums - onDevice.removeWhere((e) => !selectedIds.contains(e.localId)); + onDevice.removeWhere((album) => !selectedIds.contains(album.localId)); _log.info("'Recents' is not selected, keeping only selected albums"); } changes = @@ -138,15 +139,19 @@ class AlbumService { Future> _loadExcludedAssetIds( List albums, - List excludedAlbumIds, + Set excludedAlbumIds, ) async { final Set result = HashSet(); - for (Album album in albums) { - if (excludedAlbumIds.contains(album.localId)) { - final assetIds = - await _albumMediaRepository.getAssetIds(album.localId!); - result.addAll(assetIds); - } + for (final batchAlbums in albums + .where((album) => excludedAlbumIds.contains(album.localId)) + .slices(5)) { + await batchAlbums + .map( + (album) => _albumMediaRepository + .getAssetIds(album.localId!) + .then((assetIds) => result.addAll(assetIds)), + ) + .wait; } return result; } @@ -163,11 +168,10 @@ class AlbumService { bool changes = false; try { await _userService.refreshUsers(); - final List sharedAlbum = - await _albumApiRepository.getAll(shared: true); - - final List ownedAlbum = - await _albumApiRepository.getAll(shared: null); + final (sharedAlbum, ownedAlbum) = await ( + _albumApiRepository.getAll(shared: true), + _albumApiRepository.getAll(shared: null) + ).wait; final albums = HashSet( equals: (a, b) => a.remoteId == b.remoteId, diff --git a/mobile/lib/services/search.service.dart b/mobile/lib/services/search.service.dart index 336fe450108d3..3dd0106c09b47 100644 --- a/mobile/lib/services/search.service.dart +++ b/mobile/lib/services/search.service.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/string_extensions.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/models/search/search_result.model.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/repositories/asset.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; @@ -44,7 +46,7 @@ class SearchService { } } - Future?> search(SearchFilter filter, int page) async { + Future search(SearchFilter filter, int page) async { try { SearchResponseDto? response; AssetTypeEnum? type; @@ -103,8 +105,12 @@ class SearchService { return null; } - return _assetRepository - .getAllByRemoteId(response.assets.items.map((e) => e.id)); + return SearchResult( + assets: await _assetRepository.getAllByRemoteId( + response.assets.items.map((e) => e.id), + ), + nextPage: response.assets.nextPage?.toInt(), + ); } catch (error, stackTrace) { _log.severe("Failed to search for assets", error, stackTrace); } diff --git a/mobile/lib/utils/immich_app_theme.dart b/mobile/lib/utils/immich_app_theme.dart index c0cf60514f04d..42d338956ff8e 100644 --- a/mobile/lib/utils/immich_app_theme.dart +++ b/mobile/lib/utils/immich_app_theme.dart @@ -7,10 +7,10 @@ import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; class ImmichTheme { - ColorScheme light; - ColorScheme dark; + final ColorScheme light; + final ColorScheme dark; - ImmichTheme({required this.light, required this.dark}); + const ImmichTheme({required this.light, required this.dark}); } ImmichTheme? _immichDynamicTheme; @@ -151,7 +151,7 @@ ThemeData getThemeData({required ColorScheme colorScheme}) { return ThemeData( useMaterial3: true, - brightness: isDark ? Brightness.dark : Brightness.light, + brightness: colorScheme.brightness, colorScheme: colorScheme, primaryColor: primaryColor, hintColor: colorScheme.onSurfaceSecondary, diff --git a/mobile/lib/widgets/asset_grid/multiselect_grid.dart b/mobile/lib/widgets/asset_grid/multiselect_grid.dart index eeecfa9b58435..2bceafb595d3d 100644 --- a/mobile/lib/widgets/asset_grid/multiselect_grid.dart +++ b/mobile/lib/widgets/asset_grid/multiselect_grid.dart @@ -203,18 +203,30 @@ class MultiselectGrid extends HookConsumerWidget { void onDeleteLocal(bool onlyBackedUp) async { processing.value = true; try { + // Select only the local assets from the selection final localIds = selection.value.where((a) => a.isLocal).toList(); + // Delete only the backed-up assets if 'onlyBackedUp' is true final isDeleted = await ref .read(assetProvider.notifier) .deleteLocalOnlyAssets(localIds, onlyBackedUp: onlyBackedUp); + if (isDeleted) { + // Show a toast with the correct number of deleted assets + final deletedCount = localIds + .where( + (e) => !onlyBackedUp || e.isRemote, + ) // Only count backed-up assets + .length; + ImmichToast.show( context: context, msg: 'assets_removed_permanently_from_device' - .tr(args: ["${localIds.length}"]), + .tr(args: ["$deletedCount"]), gravity: ToastGravity.BOTTOM, ); + + // Reset the selection selectionEnabledHook.value = false; } } finally { diff --git a/mobile/lib/widgets/asset_grid/thumbnail_image.dart b/mobile/lib/widgets/asset_grid/thumbnail_image.dart index 40c6f219b7810..35013bb5956c2 100644 --- a/mobile/lib/widgets/asset_grid/thumbnail_image.dart +++ b/mobile/lib/widgets/asset_grid/thumbnail_image.dart @@ -131,23 +131,21 @@ class ThumbnailImage extends ConsumerWidget { } Widget buildImage() { - final image = SizedBox( - width: 300, - height: 300, + final image = SizedBox.expand( child: Hero( tag: isFromDto ? '${asset.remoteId}-$heroOffset' : asset.id + heroOffset, child: Stack( children: [ - ImmichThumbnail( - asset: asset, - height: 250, - width: 250, + SizedBox.expand( + child: ImmichThumbnail( + asset: asset, + height: 250, + width: 250, + ), ), Container( - height: 250, - width: 250, decoration: const BoxDecoration( gradient: LinearGradient( colors: [ diff --git a/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart b/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart index f550857b9d867..eadaf0bf9f4ad 100644 --- a/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart +++ b/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart @@ -5,6 +5,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/immich_colors.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/album/album.provider.dart'; import 'package:immich_mobile/providers/album/current_album.provider.dart'; @@ -327,39 +328,51 @@ class BottomGalleryBar extends ConsumerWidget { child: AnimatedOpacity( duration: const Duration(milliseconds: 100), opacity: ref.watch(showControlsProvider) ? 1.0 : 0.0, - child: Column( - children: [ - Visibility( - visible: showVideoPlayerControls, - child: const VideoControls(), + child: DecoratedBox( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: [blackOpacity90, Colors.transparent], ), - BottomNavigationBar( - backgroundColor: Colors.black.withOpacity(0.4), - unselectedIconTheme: const IconThemeData(color: Colors.white), - selectedIconTheme: const IconThemeData(color: Colors.white), - unselectedLabelStyle: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w500, - height: 2.3, - ), - selectedLabelStyle: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w500, - height: 2.3, - ), - unselectedFontSize: 14, - selectedFontSize: 14, - selectedItemColor: Colors.white, - unselectedItemColor: Colors.white, - showSelectedLabels: true, - showUnselectedLabels: true, - items: - albumActions.map((e) => e.keys.first).toList(growable: false), - onTap: (index) { - albumActions[index].values.first.call(index); - }, + ), + position: DecorationPosition.background, + child: Padding( + padding: EdgeInsets.only(top: 40.0), + child: Column( + children: [ + if (showVideoPlayerControls) const VideoControls(), + BottomNavigationBar( + elevation: 0.0, + backgroundColor: Colors.transparent, + unselectedIconTheme: const IconThemeData(color: Colors.white), + selectedIconTheme: const IconThemeData(color: Colors.white), + unselectedLabelStyle: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500, + height: 2.3, + ), + selectedLabelStyle: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500, + height: 2.3, + ), + unselectedFontSize: 14, + selectedFontSize: 14, + selectedItemColor: Colors.white, + unselectedItemColor: Colors.white, + showSelectedLabels: true, + showUnselectedLabels: true, + items: albumActions + .map((e) => e.keys.first) + .toList(growable: false), + onTap: (index) { + albumActions[index].values.first.call(index); + }, + ), + ], ), - ], + ), ), ), ); diff --git a/mobile/lib/widgets/asset_viewer/formatted_duration.dart b/mobile/lib/widgets/asset_viewer/formatted_duration.dart new file mode 100644 index 0000000000000..a34aab7d125d2 --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/formatted_duration.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +@pragma('vm:prefer-inline') +String _formatDuration(Duration position) { + final seconds = position.inSeconds.remainder(60).toString().padLeft(2, "0"); + final minutes = position.inMinutes.remainder(60).toString().padLeft(2, "0"); + if (position.inHours == 0) { + return "$minutes:$seconds"; + } + final hours = position.inHours.toString().padLeft(2, '0'); + return "$hours:$minutes:$seconds"; +} + +class FormattedDuration extends StatelessWidget { + final Duration data; + const FormattedDuration(this.data, {super.key}); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: data.inHours > 0 ? 70 : 60, // use a fixed width to prevent jitter + child: Text( + _formatDuration(data), + style: const TextStyle( + fontSize: 14.0, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ); + } +} diff --git a/mobile/lib/widgets/asset_viewer/video_controls.dart b/mobile/lib/widgets/asset_viewer/video_controls.dart index a5f5f18ce87c1..e4d78324c8bbd 100644 --- a/mobile/lib/widgets/asset_viewer/video_controls.dart +++ b/mobile/lib/widgets/asset_viewer/video_controls.dart @@ -1,125 +1,20 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; -import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart'; -import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart'; +import 'package:immich_mobile/widgets/asset_viewer/video_position.dart'; -/// The video controls for the [videPlayerControlsProvider] +/// The video controls for the [videoPlayerControlsProvider] class VideoControls extends ConsumerWidget { const VideoControls({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final duration = - ref.watch(videoPlaybackValueProvider.select((v) => v.duration)); - final position = - ref.watch(videoPlaybackValueProvider.select((v) => v.position)); - - return AnimatedOpacity( - opacity: ref.watch(showControlsProvider) ? 1.0 : 0.0, - duration: const Duration(milliseconds: 100), - child: OrientationBuilder( - builder: (context, orientation) => Container( - padding: EdgeInsets.symmetric( - horizontal: orientation == Orientation.portrait ? 12.0 : 64.0, - ), - color: Colors.black.withOpacity(0.4), - child: Padding( - padding: MediaQuery.of(context).orientation == Orientation.portrait - ? const EdgeInsets.symmetric(horizontal: 12.0) - : const EdgeInsets.symmetric(horizontal: 64.0), - child: Row( - children: [ - Text( - _formatDuration(position), - style: TextStyle( - fontSize: 14.0, - color: Colors.white.withOpacity(.75), - fontWeight: FontWeight.normal, - ), - ), - Expanded( - child: Slider( - value: duration == Duration.zero - ? 0.0 - : min( - position.inMicroseconds / - duration.inMicroseconds * - 100, - 100, - ), - min: 0, - max: 100, - thumbColor: Colors.white, - activeColor: Colors.white, - inactiveColor: Colors.white.withOpacity(0.75), - onChanged: (position) { - ref.read(videoPlayerControlsProvider.notifier).position = - position; - }, - ), - ), - Text( - _formatDuration(duration), - style: TextStyle( - fontSize: 14.0, - color: Colors.white.withOpacity(.75), - fontWeight: FontWeight.normal, - ), - ), - IconButton( - icon: Icon( - ref.watch( - videoPlayerControlsProvider.select((value) => value.mute), - ) - ? Icons.volume_off - : Icons.volume_up, - ), - onPressed: () => ref - .read(videoPlayerControlsProvider.notifier) - .toggleMute(), - color: Colors.white, - ), - ], - ), - ), - ), - ), - ); - } - - String _formatDuration(Duration position) { - final ms = position.inMilliseconds; - - int seconds = ms ~/ 1000; - final int hours = seconds ~/ 3600; - seconds = seconds % 3600; - final minutes = seconds ~/ 60; - seconds = seconds % 60; - - final hoursString = hours >= 10 - ? '$hours' - : hours == 0 - ? '00' - : '0$hours'; - - final minutesString = minutes >= 10 - ? '$minutes' - : minutes == 0 - ? '00' - : '0$minutes'; - - final secondsString = seconds >= 10 - ? '$seconds' - : seconds == 0 - ? '00' - : '0$seconds'; - - final formattedTime = - '${hoursString == '00' ? '' : '$hoursString:'}$minutesString:$secondsString'; - - return formattedTime; + final isPortrait = + MediaQuery.orientationOf(context) == Orientation.portrait; + return isPortrait + ? const VideoPosition() + : const Padding( + padding: EdgeInsets.symmetric(horizontal: 60.0), + child: VideoPosition(), + ); } } diff --git a/mobile/lib/widgets/asset_viewer/video_position.dart b/mobile/lib/widgets/asset_viewer/video_position.dart new file mode 100644 index 0000000000000..ef309b9c8561a --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/video_position.dart @@ -0,0 +1,110 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/immich_colors.dart'; +import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart'; +import 'package:immich_mobile/widgets/asset_viewer/formatted_duration.dart'; + +class VideoPosition extends HookConsumerWidget { + const VideoPosition({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final (position, duration) = ref.watch( + videoPlaybackValueProvider.select((v) => (v.position, v.duration)), + ); + final wasPlaying = useRef(true); + return duration == Duration.zero + ? const _VideoPositionPlaceholder() + : Column( + children: [ + Padding( + // align with slider's inherent padding + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + FormattedDuration(position), + FormattedDuration(duration), + ], + ), + ), + Row( + children: [ + Expanded( + child: Slider( + value: min( + position.inMicroseconds / duration.inMicroseconds * 100, + 100, + ), + min: 0, + max: 100, + thumbColor: Colors.white, + activeColor: Colors.white, + inactiveColor: whiteOpacity75, + onChangeStart: (value) { + final state = + ref.read(videoPlaybackValueProvider).state; + wasPlaying.value = state != VideoPlaybackState.paused; + ref.read(videoPlayerControlsProvider.notifier).pause(); + }, + onChangeEnd: (value) { + if (wasPlaying.value) { + ref.read(videoPlayerControlsProvider.notifier).play(); + } + }, + onChanged: (position) { + ref + .read(videoPlayerControlsProvider.notifier) + .position = position; + }, + ), + ), + ], + ), + ], + ); + } +} + +class _VideoPositionPlaceholder extends StatelessWidget { + const _VideoPositionPlaceholder(); + + static void _onChangedDummy(_) {} + + @override + Widget build(BuildContext context) { + return const Column( + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + FormattedDuration(Duration.zero), + FormattedDuration(Duration.zero), + ], + ), + ), + Row( + children: [ + Expanded( + child: Slider( + value: 0.0, + min: 0, + max: 100, + thumbColor: Colors.white, + activeColor: Colors.white, + inactiveColor: whiteOpacity75, + onChanged: _onChangedDummy, + ), + ), + ], + ), + ], + ); + } +} diff --git a/mobile/lib/widgets/backup/asset_info_table.dart b/mobile/lib/widgets/backup/asset_info_table.dart new file mode 100644 index 0000000000000..bbcbbe375f0ff --- /dev/null +++ b/mobile/lib/widgets/backup/asset_info_table.dart @@ -0,0 +1,102 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/models/backup/backup_state.model.dart'; +import 'package:immich_mobile/models/backup/current_upload_asset.model.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; + +class BackupAssetInfoTable extends ConsumerWidget { + const BackupAssetInfoTable({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isManualUpload = ref.watch( + backupProvider.select( + (value) => value.backupProgress == BackUpProgressEnum.manualInProgress, + ), + ); + + final asset = isManualUpload + ? ref.watch( + manualUploadProvider.select((value) => value.currentUploadAsset), + ) + : ref.watch(backupProvider.select((value) => value.currentUploadAsset)); + + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Table( + border: TableBorder.all( + color: context.colorScheme.outlineVariant, + width: 1, + ), + children: [ + TableRow( + children: [ + TableCell( + verticalAlignment: TableCellVerticalAlignment.middle, + child: Padding( + padding: const EdgeInsets.all(6.0), + child: Text( + 'backup_controller_page_filename', + style: TextStyle( + color: context.colorScheme.onSurfaceSecondary, + fontWeight: FontWeight.bold, + fontSize: 10.0, + ), + ).tr( + args: [asset.fileName, asset.fileType.toLowerCase()], + ), + ), + ), + ], + ), + TableRow( + children: [ + TableCell( + verticalAlignment: TableCellVerticalAlignment.middle, + child: Padding( + padding: const EdgeInsets.all(6.0), + child: Text( + "backup_controller_page_created", + style: TextStyle( + color: context.colorScheme.onSurfaceSecondary, + fontWeight: FontWeight.bold, + fontSize: 10.0, + ), + ).tr( + args: [_getAssetCreationDate(asset)], + ), + ), + ), + ], + ), + TableRow( + children: [ + TableCell( + child: Padding( + padding: const EdgeInsets.all(6.0), + child: Text( + "backup_controller_page_id", + style: TextStyle( + color: context.colorScheme.onSurfaceSecondary, + fontWeight: FontWeight.bold, + fontSize: 10.0, + ), + ).tr(args: [asset.id]), + ), + ), + ], + ), + ], + ), + ); + } + + @pragma('vm:prefer-inline') + String _getAssetCreationDate(CurrentUploadAsset asset) { + return DateFormat.yMMMMd().format(asset.fileCreatedAt.toLocal()); + } +} diff --git a/mobile/lib/widgets/backup/current_backup_asset_info_box.dart b/mobile/lib/widgets/backup/current_backup_asset_info_box.dart index f2f84e271f155..b6d0edb2004ff 100644 --- a/mobile/lib/widgets/backup/current_backup_asset_info_box.dart +++ b/mobile/lib/widgets/backup/current_backup_asset_info_box.dart @@ -1,296 +1,43 @@ import 'dart:io'; -import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/models/backup/backup_state.model.dart'; -import 'package:immich_mobile/providers/backup/backup.provider.dart'; -import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; -import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; -import 'package:immich_mobile/repositories/asset_media.repository.dart'; -import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/widgets/common/immich_thumbnail.dart'; +import 'package:immich_mobile/widgets/backup/asset_info_table.dart'; +import 'package:immich_mobile/widgets/backup/error_chip.dart'; +import 'package:immich_mobile/widgets/backup/icloud_download_progress_bar.dart'; +import 'package:immich_mobile/widgets/backup/upload_progress_bar.dart'; +import 'package:immich_mobile/widgets/backup/upload_stats.dart'; -class CurrentUploadingAssetInfoBox extends HookConsumerWidget { +class CurrentUploadingAssetInfoBox extends StatelessWidget { const CurrentUploadingAssetInfoBox({super.key}); - @override - Widget build(BuildContext context, WidgetRef ref) { - var isManualUpload = ref.watch(backupProvider).backupProgress == - BackUpProgressEnum.manualInProgress; - var asset = !isManualUpload - ? ref.watch(backupProvider).currentUploadAsset - : ref.watch(manualUploadProvider).currentUploadAsset; - var uploadProgress = !isManualUpload - ? ref.watch(backupProvider).progressInPercentage - : ref.watch(manualUploadProvider).progressInPercentage; - var uploadFileProgress = !isManualUpload - ? ref.watch(backupProvider).progressInFileSize - : ref.watch(manualUploadProvider).progressInFileSize; - var uploadFileSpeed = !isManualUpload - ? ref.watch(backupProvider).progressInFileSpeed - : ref.watch(manualUploadProvider).progressInFileSpeed; - var iCloudDownloadProgress = - ref.watch(backupProvider).iCloudDownloadProgress; - final isShowThumbnail = useState(false); - - String formatUploadFileSpeed(double uploadFileSpeed) { - if (uploadFileSpeed < 1024) { - return '${uploadFileSpeed.toStringAsFixed(2)} B/s'; - } else if (uploadFileSpeed < 1024 * 1024) { - return '${(uploadFileSpeed / 1024).toStringAsFixed(2)} KB/s'; - } else if (uploadFileSpeed < 1024 * 1024 * 1024) { - return '${(uploadFileSpeed / (1024 * 1024)).toStringAsFixed(2)} MB/s'; - } else { - return '${(uploadFileSpeed / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB/s'; - } - } - - String getAssetCreationDate() { - return DateFormat.yMMMMd().format( - DateTime.parse( - asset.fileCreatedAt.toString(), - ).toLocal(), - ); - } - - Widget buildErrorChip() { - return ActionChip( - avatar: Icon( - Icons.info, - color: Colors.red[400], - ), - elevation: 1, - visualDensity: VisualDensity.compact, - label: Text( - "backup_controller_page_failed", - style: TextStyle( - color: Colors.red[400], - fontWeight: FontWeight.bold, - fontSize: 11, - ), - ).tr( - args: [ref.watch(errorBackupListProvider).length.toString()], - ), - backgroundColor: Colors.white, - onPressed: () => context.pushRoute(const FailedBackupStatusRoute()), - ); - } - Widget buildAssetInfoTable() { - return Table( - border: TableBorder.all( - color: context.colorScheme.outlineVariant, - width: 1, - ), + @override + Widget build(BuildContext context) { + return ListTile( + isThreeLine: true, + leading: Icon( + Icons.image_outlined, + color: context.primaryColor, + size: 30, + ), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - TableRow( - children: [ - TableCell( - verticalAlignment: TableCellVerticalAlignment.middle, - child: Padding( - padding: const EdgeInsets.all(6.0), - child: Text( - 'backup_controller_page_filename', - style: TextStyle( - color: context.colorScheme.onSurfaceSecondary, - fontWeight: FontWeight.bold, - fontSize: 10.0, - ), - ).tr( - args: [asset.fileName, asset.fileType.toLowerCase()], - ), - ), - ), - ], - ), - TableRow( - children: [ - TableCell( - verticalAlignment: TableCellVerticalAlignment.middle, - child: Padding( - padding: const EdgeInsets.all(6.0), - child: Text( - "backup_controller_page_created", - style: TextStyle( - color: context.colorScheme.onSurfaceSecondary, - fontWeight: FontWeight.bold, - fontSize: 10.0, - ), - ).tr( - args: [getAssetCreationDate()], - ), - ), - ), - ], - ), - TableRow( - children: [ - TableCell( - child: Padding( - padding: const EdgeInsets.all(6.0), - child: Text( - "backup_controller_page_id", - style: TextStyle( - color: context.colorScheme.onSurfaceSecondary, - fontWeight: FontWeight.bold, - fontSize: 10.0, - ), - ).tr(args: [asset.id]), - ), - ), - ], - ), + Text( + "backup_controller_page_uploading_file_info", + style: context.textTheme.titleSmall, + ).tr(), + const BackupErrorChip(), + ], + ), + subtitle: Column( + children: [ + if (Platform.isIOS) const IcloudDownloadProgressBar(), + const BackupUploadProgressBar(), + const BackupUploadStats(), + const BackupAssetInfoTable(), ], - ); - } - - buildiCloudDownloadProgerssBar() { - if (asset.iCloudAsset != null && asset.iCloudAsset!) { - return Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Row( - children: [ - SizedBox( - width: 110, - child: Text( - "iCloud Download", - style: context.textTheme.labelSmall, - ), - ), - Expanded( - child: LinearProgressIndicator( - minHeight: 10.0, - value: uploadProgress / 100.0, - borderRadius: const BorderRadius.all(Radius.circular(10.0)), - ), - ), - Text( - " ${iCloudDownloadProgress.toStringAsFixed(0)}%", - style: const TextStyle(fontSize: 12), - ), - ], - ), - ); - } - - return const SizedBox(); - } - - buildUploadProgressBar() { - return Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Row( - children: [ - if (asset.iCloudAsset != null && asset.iCloudAsset!) - SizedBox( - width: 110, - child: Text( - "Immich Upload", - style: context.textTheme.labelSmall, - ), - ), - Expanded( - child: LinearProgressIndicator( - minHeight: 10.0, - value: uploadProgress / 100.0, - borderRadius: const BorderRadius.all(Radius.circular(10.0)), - ), - ), - Text( - " ${uploadProgress.toStringAsFixed(0)}%", - style: const TextStyle(fontSize: 12, fontFamily: "OverpassMono"), - ), - ], - ), - ); - } - - buildUploadStats() { - return Padding( - padding: const EdgeInsets.only(top: 2.0, bottom: 2.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - uploadFileProgress, - style: const TextStyle(fontSize: 10, fontFamily: "OverpassMono"), - ), - Text( - formatUploadFileSpeed(uploadFileSpeed), - style: const TextStyle(fontSize: 10, fontFamily: "OverpassMono"), - ), - ], - ), - ); - } - - return FutureBuilder( - future: ref.read(assetMediaRepositoryProvider).get(asset.id), - builder: (context, thumbnail) => ListTile( - isThreeLine: true, - leading: AnimatedCrossFade( - alignment: Alignment.centerLeft, - firstChild: GestureDetector( - onTap: () => isShowThumbnail.value = false, - child: thumbnail.hasData - ? ClipRRect( - borderRadius: BorderRadius.circular(5), - child: ImmichThumbnail( - asset: thumbnail.data, - width: 50, - height: 50, - ), - ) - : const SizedBox( - width: 50, - height: 50, - child: Padding( - padding: EdgeInsets.all(8.0), - child: CircularProgressIndicator.adaptive( - strokeWidth: 1, - ), - ), - ), - ), - secondChild: GestureDetector( - onTap: () => isShowThumbnail.value = true, - child: Icon( - Icons.image_outlined, - color: context.primaryColor, - size: 30, - ), - ), - crossFadeState: isShowThumbnail.value - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - duration: const Duration(milliseconds: 200), - ), - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "backup_controller_page_uploading_file_info", - style: context.textTheme.titleSmall, - ).tr(), - if (ref.watch(errorBackupListProvider).isNotEmpty) buildErrorChip(), - ], - ), - subtitle: Column( - children: [ - if (Platform.isIOS) buildiCloudDownloadProgerssBar(), - buildUploadProgressBar(), - buildUploadStats(), - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: buildAssetInfoTable(), - ), - ], - ), ), ); } diff --git a/mobile/lib/widgets/backup/error_chip.dart b/mobile/lib/widgets/backup/error_chip.dart new file mode 100644 index 0000000000000..4bbc040d4d908 --- /dev/null +++ b/mobile/lib/widgets/backup/error_chip.dart @@ -0,0 +1,32 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/immich_colors.dart'; +import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/backup/error_chip_text.dart'; + +class BackupErrorChip extends ConsumerWidget { + const BackupErrorChip({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final hasErrors = + ref.watch(errorBackupListProvider.select((value) => value.isNotEmpty)); + if (!hasErrors) { + return const SizedBox(); + } + + return ActionChip( + avatar: const Icon( + Icons.info, + color: red400, + ), + elevation: 1, + visualDensity: VisualDensity.compact, + label: const BackupErrorChipText(), + backgroundColor: Colors.white, + onPressed: () => context.pushRoute(const FailedBackupStatusRoute()), + ); + } +} diff --git a/mobile/lib/widgets/backup/error_chip_text.dart b/mobile/lib/widgets/backup/error_chip_text.dart new file mode 100644 index 0000000000000..94148da1761c7 --- /dev/null +++ b/mobile/lib/widgets/backup/error_chip_text.dart @@ -0,0 +1,28 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/immich_colors.dart'; +import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; + +class BackupErrorChipText extends ConsumerWidget { + const BackupErrorChipText({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final count = ref.watch(errorBackupListProvider).length; + if (count == 0) { + return const SizedBox(); + } + + return const Text( + "backup_controller_page_failed", + style: TextStyle( + color: red400, + fontWeight: FontWeight.bold, + fontSize: 11, + ), + ).tr( + args: [count.toString()], + ); + } +} diff --git a/mobile/lib/widgets/backup/icloud_download_progress_bar.dart b/mobile/lib/widgets/backup/icloud_download_progress_bar.dart new file mode 100644 index 0000000000000..c61fb1a0d1280 --- /dev/null +++ b/mobile/lib/widgets/backup/icloud_download_progress_bar.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/models/backup/backup_state.model.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; + +class IcloudDownloadProgressBar extends ConsumerWidget { + const IcloudDownloadProgressBar({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + final isManualUpload = ref.watch( + backupProvider.select( + (value) => value.backupProgress == BackUpProgressEnum.manualInProgress, + ), + ); + + final isIcloudAsset = isManualUpload + ? ref.watch( + manualUploadProvider + .select((value) => value.currentUploadAsset.isIcloudAsset), + ) + : ref.watch( + backupProvider + .select((value) => value.currentUploadAsset.isIcloudAsset), + ); + + if (!isIcloudAsset) { + return const SizedBox(); + } + + final iCloudDownloadProgress = ref + .watch(backupProvider.select((value) => value.iCloudDownloadProgress)); + + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Row( + children: [ + SizedBox( + width: 110, + child: Text( + "iCloud Download", + style: context.textTheme.labelSmall, + ), + ), + Expanded( + child: LinearProgressIndicator( + minHeight: 10.0, + value: iCloudDownloadProgress / 100.0, + borderRadius: const BorderRadius.all(Radius.circular(10.0)), + ), + ), + Text( + " ${iCloudDownloadProgress ~/ 1}%", + style: const TextStyle(fontSize: 12), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/widgets/backup/upload_progress_bar.dart b/mobile/lib/widgets/backup/upload_progress_bar.dart new file mode 100644 index 0000000000000..9281914d9cc50 --- /dev/null +++ b/mobile/lib/widgets/backup/upload_progress_bar.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/models/backup/backup_state.model.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; + +class BackupUploadProgressBar extends ConsumerWidget { + const BackupUploadProgressBar({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isManualUpload = ref.watch( + backupProvider.select( + (value) => value.backupProgress == BackUpProgressEnum.manualInProgress, + ), + ); + + final isIcloudAsset = isManualUpload + ? ref.watch( + manualUploadProvider + .select((value) => value.currentUploadAsset.isIcloudAsset), + ) + : ref.watch( + backupProvider + .select((value) => value.currentUploadAsset.isIcloudAsset), + ); + + final uploadProgress = isManualUpload + ? ref.watch( + manualUploadProvider.select((value) => value.progressInPercentage), + ) + : ref.watch( + backupProvider.select((value) => value.progressInPercentage), + ); + + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Row( + children: [ + if (isIcloudAsset) + SizedBox( + width: 110, + child: Text( + "Immich Upload", + style: context.textTheme.labelSmall, + ), + ), + Expanded( + child: LinearProgressIndicator( + minHeight: 10.0, + value: uploadProgress / 100.0, + borderRadius: const BorderRadius.all(Radius.circular(10.0)), + ), + ), + Text( + " ${uploadProgress.toStringAsFixed(0)}%", + style: const TextStyle(fontSize: 12, fontFamily: "OverpassMono"), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/widgets/backup/upload_stats.dart b/mobile/lib/widgets/backup/upload_stats.dart new file mode 100644 index 0000000000000..965202ce33523 --- /dev/null +++ b/mobile/lib/widgets/backup/upload_stats.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/models/backup/backup_state.model.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; + +class BackupUploadStats extends ConsumerWidget { + const BackupUploadStats({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isManualUpload = ref.watch( + backupProvider.select( + (value) => value.backupProgress == BackUpProgressEnum.manualInProgress, + ), + ); + + final uploadFileProgress = isManualUpload + ? ref.watch( + manualUploadProvider.select((value) => value.progressInFileSize), + ) + : ref.watch(backupProvider.select((value) => value.progressInFileSize)); + + final uploadFileSpeed = isManualUpload + ? ref.watch( + manualUploadProvider.select((value) => value.progressInFileSpeed), + ) + : ref.watch( + backupProvider.select((value) => value.progressInFileSpeed), + ); + + return Padding( + padding: const EdgeInsets.only(top: 2.0, bottom: 2.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + uploadFileProgress, + style: const TextStyle(fontSize: 10, fontFamily: "OverpassMono"), + ), + Text( + _formatUploadFileSpeed(uploadFileSpeed), + style: const TextStyle(fontSize: 10, fontFamily: "OverpassMono"), + ), + ], + ), + ); + } + + @pragma('vm:prefer-inline') + String _formatUploadFileSpeed(double uploadFileSpeed) { + if (uploadFileSpeed < 1024) { + return '${uploadFileSpeed.toStringAsFixed(2)} B/s'; + } else if (uploadFileSpeed < 1024 * 1024) { + return '${(uploadFileSpeed / 1024).toStringAsFixed(2)} KB/s'; + } else if (uploadFileSpeed < 1024 * 1024 * 1024) { + return '${(uploadFileSpeed / (1024 * 1024)).toStringAsFixed(2)} MB/s'; + } else { + return '${(uploadFileSpeed / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB/s'; + } + } +} diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart index cd694336bc5af..ca6b827f66265 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart @@ -28,6 +28,7 @@ class ImmichAppBarDialog extends HookConsumerWidget { bool isHorizontal = !context.isMobile; final horizontalPadding = isHorizontal ? 100.0 : 20.0; final user = ref.watch(currentUserProvider); + final isLoggingOut = useState(false); useEffect( () { @@ -63,11 +64,16 @@ class ImmichAppBarDialog extends HookConsumerWidget { ); } - buildActionButton(IconData icon, String text, Function() onTap) { + buildActionButton( + IconData icon, + String text, + Function() onTap, { + Widget? trailing, + }) { return ListTile( dense: true, visualDensity: VisualDensity.standard, - contentPadding: const EdgeInsets.only(left: 30), + contentPadding: const EdgeInsets.only(left: 30, right: 30), minLeadingWidth: 40, leading: SizedBox( child: Icon( @@ -83,6 +89,7 @@ class ImmichAppBarDialog extends HookConsumerWidget { ), ).tr(), onTap: onTap, + trailing: trailing, ); } @@ -107,6 +114,10 @@ class ImmichAppBarDialog extends HookConsumerWidget { Icons.logout_rounded, "profile_drawer_sign_out", () async { + if (isLoggingOut.value) { + return; + } + showDialog( context: context, builder: (BuildContext ctx) { @@ -115,7 +126,11 @@ class ImmichAppBarDialog extends HookConsumerWidget { content: "app_bar_signout_dialog_content", ok: "app_bar_signout_dialog_ok", onOk: () async { - await ref.read(authenticationProvider.notifier).logout(); + isLoggingOut.value = true; + await ref + .read(authenticationProvider.notifier) + .logout() + .whenComplete(() => isLoggingOut.value = false); ref.read(manualUploadProvider.notifier).cancelBackup(); ref.read(backupProvider.notifier).cancelBackup(); @@ -127,6 +142,12 @@ class ImmichAppBarDialog extends HookConsumerWidget { }, ); }, + trailing: isLoggingOut.value + ? SizedBox.square( + dimension: 20, + child: const CircularProgressIndicator(strokeWidth: 2), + ) + : null, ); } @@ -238,6 +259,7 @@ class ImmichAppBarDialog extends HookConsumerWidget { } return Dismissible( + behavior: HitTestBehavior.translucent, direction: DismissDirection.down, onDismissed: (_) => Navigator.of(context).pop(), key: const Key('app_bar_dialog'), diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index c3bc3b264c99d..cebb6817ebb9c 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -3,7 +3,7 @@ Immich API This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: -- API version: 1.118.2 +- API version: 1.120.2 - Generator version: 7.8.0 - Build package: org.openapitools.codegen.languages.DartClientCodegen @@ -306,6 +306,7 @@ Class | Method | HTTP request | Description - [CreateAlbumDto](doc//CreateAlbumDto.md) - [CreateLibraryDto](doc//CreateLibraryDto.md) - [CreateProfileImageResponseDto](doc//CreateProfileImageResponseDto.md) + - [DatabaseBackupConfig](doc//DatabaseBackupConfig.md) - [DownloadArchiveInfo](doc//DownloadArchiveInfo.md) - [DownloadInfoDto](doc//DownloadInfoDto.md) - [DownloadResponse](doc//DownloadResponse.md) @@ -407,12 +408,12 @@ Class | Method | HTTP request | Description - [SharedLinkResponseDto](doc//SharedLinkResponseDto.md) - [SharedLinkType](doc//SharedLinkType.md) - [SignUpDto](doc//SignUpDto.md) - - [SmartInfoResponseDto](doc//SmartInfoResponseDto.md) - [SmartSearchDto](doc//SmartSearchDto.md) - [SourceType](doc//SourceType.md) - [StackCreateDto](doc//StackCreateDto.md) - [StackResponseDto](doc//StackResponseDto.md) - [StackUpdateDto](doc//StackUpdateDto.md) + - [SystemConfigBackupsDto](doc//SystemConfigBackupsDto.md) - [SystemConfigDto](doc//SystemConfigDto.md) - [SystemConfigFFmpegDto](doc//SystemConfigFFmpegDto.md) - [SystemConfigFacesDto](doc//SystemConfigFacesDto.md) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 6fb7478d04bf2..e1c343ad50d1d 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -120,6 +120,7 @@ part 'model/colorspace.dart'; part 'model/create_album_dto.dart'; part 'model/create_library_dto.dart'; part 'model/create_profile_image_response_dto.dart'; +part 'model/database_backup_config.dart'; part 'model/download_archive_info.dart'; part 'model/download_info_dto.dart'; part 'model/download_response.dart'; @@ -221,12 +222,12 @@ part 'model/shared_link_edit_dto.dart'; part 'model/shared_link_response_dto.dart'; part 'model/shared_link_type.dart'; part 'model/sign_up_dto.dart'; -part 'model/smart_info_response_dto.dart'; part 'model/smart_search_dto.dart'; part 'model/source_type.dart'; part 'model/stack_create_dto.dart'; part 'model/stack_response_dto.dart'; part 'model/stack_update_dto.dart'; +part 'model/system_config_backups_dto.dart'; part 'model/system_config_dto.dart'; part 'model/system_config_f_fmpeg_dto.dart'; part 'model/system_config_faces_dto.dart'; diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index c1025b0bd4820..b71e6f45f71cb 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -294,6 +294,8 @@ class ApiClient { return CreateLibraryDto.fromJson(value); case 'CreateProfileImageResponseDto': return CreateProfileImageResponseDto.fromJson(value); + case 'DatabaseBackupConfig': + return DatabaseBackupConfig.fromJson(value); case 'DownloadArchiveInfo': return DownloadArchiveInfo.fromJson(value); case 'DownloadInfoDto': @@ -496,8 +498,6 @@ class ApiClient { return SharedLinkTypeTypeTransformer().decode(value); case 'SignUpDto': return SignUpDto.fromJson(value); - case 'SmartInfoResponseDto': - return SmartInfoResponseDto.fromJson(value); case 'SmartSearchDto': return SmartSearchDto.fromJson(value); case 'SourceType': @@ -508,6 +508,8 @@ class ApiClient { return StackResponseDto.fromJson(value); case 'StackUpdateDto': return StackUpdateDto.fromJson(value); + case 'SystemConfigBackupsDto': + return SystemConfigBackupsDto.fromJson(value); case 'SystemConfigDto': return SystemConfigDto.fromJson(value); case 'SystemConfigFFmpegDto': diff --git a/mobile/openapi/lib/model/all_job_status_response_dto.dart b/mobile/openapi/lib/model/all_job_status_response_dto.dart index 6ec248a638e80..787d02dd0e292 100644 --- a/mobile/openapi/lib/model/all_job_status_response_dto.dart +++ b/mobile/openapi/lib/model/all_job_status_response_dto.dart @@ -14,6 +14,7 @@ class AllJobStatusResponseDto { /// Returns a new [AllJobStatusResponseDto] instance. AllJobStatusResponseDto({ required this.backgroundTask, + required this.backupDatabase, required this.duplicateDetection, required this.faceDetection, required this.facialRecognition, @@ -31,6 +32,8 @@ class AllJobStatusResponseDto { JobStatusDto backgroundTask; + JobStatusDto backupDatabase; + JobStatusDto duplicateDetection; JobStatusDto faceDetection; @@ -60,6 +63,7 @@ class AllJobStatusResponseDto { @override bool operator ==(Object other) => identical(this, other) || other is AllJobStatusResponseDto && other.backgroundTask == backgroundTask && + other.backupDatabase == backupDatabase && other.duplicateDetection == duplicateDetection && other.faceDetection == faceDetection && other.facialRecognition == facialRecognition && @@ -78,6 +82,7 @@ class AllJobStatusResponseDto { int get hashCode => // ignore: unnecessary_parenthesis (backgroundTask.hashCode) + + (backupDatabase.hashCode) + (duplicateDetection.hashCode) + (faceDetection.hashCode) + (facialRecognition.hashCode) + @@ -93,11 +98,12 @@ class AllJobStatusResponseDto { (videoConversion.hashCode); @override - String toString() => 'AllJobStatusResponseDto[backgroundTask=$backgroundTask, duplicateDetection=$duplicateDetection, faceDetection=$faceDetection, facialRecognition=$facialRecognition, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion]'; + String toString() => 'AllJobStatusResponseDto[backgroundTask=$backgroundTask, backupDatabase=$backupDatabase, duplicateDetection=$duplicateDetection, faceDetection=$faceDetection, facialRecognition=$facialRecognition, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion]'; Map toJson() { final json = {}; json[r'backgroundTask'] = this.backgroundTask; + json[r'backupDatabase'] = this.backupDatabase; json[r'duplicateDetection'] = this.duplicateDetection; json[r'faceDetection'] = this.faceDetection; json[r'facialRecognition'] = this.facialRecognition; @@ -124,6 +130,7 @@ class AllJobStatusResponseDto { return AllJobStatusResponseDto( backgroundTask: JobStatusDto.fromJson(json[r'backgroundTask'])!, + backupDatabase: JobStatusDto.fromJson(json[r'backupDatabase'])!, duplicateDetection: JobStatusDto.fromJson(json[r'duplicateDetection'])!, faceDetection: JobStatusDto.fromJson(json[r'faceDetection'])!, facialRecognition: JobStatusDto.fromJson(json[r'facialRecognition'])!, @@ -185,6 +192,7 @@ class AllJobStatusResponseDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { 'backgroundTask', + 'backupDatabase', 'duplicateDetection', 'faceDetection', 'facialRecognition', diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index c11dedcbfd230..5f01f84419e4e 100644 --- a/mobile/openapi/lib/model/asset_response_dto.dart +++ b/mobile/openapi/lib/model/asset_response_dto.dart @@ -37,7 +37,6 @@ class AssetResponseDto { required this.ownerId, this.people = const [], this.resized, - this.smartInfo, this.stack, this.tags = const [], required this.thumbhash, @@ -121,14 +120,6 @@ class AssetResponseDto { /// bool? resized; - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - SmartInfoResponseDto? smartInfo; - AssetStackResponseDto? stack; List tags; @@ -167,7 +158,6 @@ class AssetResponseDto { other.ownerId == ownerId && _deepEquality.equals(other.people, people) && other.resized == resized && - other.smartInfo == smartInfo && other.stack == stack && _deepEquality.equals(other.tags, tags) && other.thumbhash == thumbhash && @@ -202,7 +192,6 @@ class AssetResponseDto { (ownerId.hashCode) + (people.hashCode) + (resized == null ? 0 : resized!.hashCode) + - (smartInfo == null ? 0 : smartInfo!.hashCode) + (stack == null ? 0 : stack!.hashCode) + (tags.hashCode) + (thumbhash == null ? 0 : thumbhash!.hashCode) + @@ -211,7 +200,7 @@ class AssetResponseDto { (updatedAt.hashCode); @override - String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isOffline=$isOffline, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalMimeType=$originalMimeType, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, stack=$stack, tags=$tags, thumbhash=$thumbhash, type=$type, unassignedFaces=$unassignedFaces, updatedAt=$updatedAt]'; + String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isOffline=$isOffline, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalMimeType=$originalMimeType, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, stack=$stack, tags=$tags, thumbhash=$thumbhash, type=$type, unassignedFaces=$unassignedFaces, updatedAt=$updatedAt]'; Map toJson() { final json = {}; @@ -267,11 +256,6 @@ class AssetResponseDto { } else { // json[r'resized'] = null; } - if (this.smartInfo != null) { - json[r'smartInfo'] = this.smartInfo; - } else { - // json[r'smartInfo'] = null; - } if (this.stack != null) { json[r'stack'] = this.stack; } else { @@ -322,7 +306,6 @@ class AssetResponseDto { ownerId: mapValueOfType(json, r'ownerId')!, people: PersonWithFacesResponseDto.listFromJson(json[r'people']), resized: mapValueOfType(json, r'resized'), - smartInfo: SmartInfoResponseDto.fromJson(json[r'smartInfo']), stack: AssetStackResponseDto.fromJson(json[r'stack']), tags: TagResponseDto.listFromJson(json[r'tags']), thumbhash: mapValueOfType(json, r'thumbhash'), diff --git a/mobile/openapi/lib/model/database_backup_config.dart b/mobile/openapi/lib/model/database_backup_config.dart new file mode 100644 index 0000000000000..d82128bd4443a --- /dev/null +++ b/mobile/openapi/lib/model/database_backup_config.dart @@ -0,0 +1,116 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class DatabaseBackupConfig { + /// Returns a new [DatabaseBackupConfig] instance. + DatabaseBackupConfig({ + required this.cronExpression, + required this.enabled, + required this.keepLastAmount, + }); + + String cronExpression; + + bool enabled; + + /// Minimum value: 1 + num keepLastAmount; + + @override + bool operator ==(Object other) => identical(this, other) || other is DatabaseBackupConfig && + other.cronExpression == cronExpression && + other.enabled == enabled && + other.keepLastAmount == keepLastAmount; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (cronExpression.hashCode) + + (enabled.hashCode) + + (keepLastAmount.hashCode); + + @override + String toString() => 'DatabaseBackupConfig[cronExpression=$cronExpression, enabled=$enabled, keepLastAmount=$keepLastAmount]'; + + Map toJson() { + final json = {}; + json[r'cronExpression'] = this.cronExpression; + json[r'enabled'] = this.enabled; + json[r'keepLastAmount'] = this.keepLastAmount; + return json; + } + + /// Returns a new [DatabaseBackupConfig] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static DatabaseBackupConfig? fromJson(dynamic value) { + upgradeDto(value, "DatabaseBackupConfig"); + if (value is Map) { + final json = value.cast(); + + return DatabaseBackupConfig( + cronExpression: mapValueOfType(json, r'cronExpression')!, + enabled: mapValueOfType(json, r'enabled')!, + keepLastAmount: num.parse('${json[r'keepLastAmount']}'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = DatabaseBackupConfig.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = DatabaseBackupConfig.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of DatabaseBackupConfig-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = DatabaseBackupConfig.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'cronExpression', + 'enabled', + 'keepLastAmount', + }; +} + diff --git a/mobile/openapi/lib/model/job_name.dart b/mobile/openapi/lib/model/job_name.dart index 072da76d4c222..6b9a002cbece6 100644 --- a/mobile/openapi/lib/model/job_name.dart +++ b/mobile/openapi/lib/model/job_name.dart @@ -37,6 +37,7 @@ class JobName { static const sidecar = JobName._(r'sidecar'); static const library_ = JobName._(r'library'); static const notifications = JobName._(r'notifications'); + static const backupDatabase = JobName._(r'backupDatabase'); /// List of all possible values in this [enum][JobName]. static const values = [ @@ -54,6 +55,7 @@ class JobName { sidecar, library_, notifications, + backupDatabase, ]; static JobName? fromJson(dynamic value) => JobNameTypeTransformer().decode(value); @@ -106,6 +108,7 @@ class JobNameTypeTransformer { case r'sidecar': return JobName.sidecar; case r'library': return JobName.library_; case r'notifications': return JobName.notifications; + case r'backupDatabase': return JobName.backupDatabase; default: if (!allowNull) { throw ArgumentError('Unknown enum value to decode: $data'); diff --git a/mobile/openapi/lib/model/server_stats_response_dto.dart b/mobile/openapi/lib/model/server_stats_response_dto.dart index 654a34ee6b0e7..531fa8f03e16a 100644 --- a/mobile/openapi/lib/model/server_stats_response_dto.dart +++ b/mobile/openapi/lib/model/server_stats_response_dto.dart @@ -16,6 +16,8 @@ class ServerStatsResponseDto { this.photos = 0, this.usage = 0, this.usageByUser = const [], + this.usagePhotos = 0, + this.usageVideos = 0, this.videos = 0, }); @@ -25,6 +27,10 @@ class ServerStatsResponseDto { List usageByUser; + int usagePhotos; + + int usageVideos; + int videos; @override @@ -32,6 +38,8 @@ class ServerStatsResponseDto { other.photos == photos && other.usage == usage && _deepEquality.equals(other.usageByUser, usageByUser) && + other.usagePhotos == usagePhotos && + other.usageVideos == usageVideos && other.videos == videos; @override @@ -40,16 +48,20 @@ class ServerStatsResponseDto { (photos.hashCode) + (usage.hashCode) + (usageByUser.hashCode) + + (usagePhotos.hashCode) + + (usageVideos.hashCode) + (videos.hashCode); @override - String toString() => 'ServerStatsResponseDto[photos=$photos, usage=$usage, usageByUser=$usageByUser, videos=$videos]'; + String toString() => 'ServerStatsResponseDto[photos=$photos, usage=$usage, usageByUser=$usageByUser, usagePhotos=$usagePhotos, usageVideos=$usageVideos, videos=$videos]'; Map toJson() { final json = {}; json[r'photos'] = this.photos; json[r'usage'] = this.usage; json[r'usageByUser'] = this.usageByUser; + json[r'usagePhotos'] = this.usagePhotos; + json[r'usageVideos'] = this.usageVideos; json[r'videos'] = this.videos; return json; } @@ -66,6 +78,8 @@ class ServerStatsResponseDto { photos: mapValueOfType(json, r'photos')!, usage: mapValueOfType(json, r'usage')!, usageByUser: UsageByUserDto.listFromJson(json[r'usageByUser']), + usagePhotos: mapValueOfType(json, r'usagePhotos')!, + usageVideos: mapValueOfType(json, r'usageVideos')!, videos: mapValueOfType(json, r'videos')!, ); } @@ -117,6 +131,8 @@ class ServerStatsResponseDto { 'photos', 'usage', 'usageByUser', + 'usagePhotos', + 'usageVideos', 'videos', }; } diff --git a/mobile/openapi/lib/model/smart_info_response_dto.dart b/mobile/openapi/lib/model/smart_info_response_dto.dart deleted file mode 100644 index 4631eccf2cb1c..0000000000000 --- a/mobile/openapi/lib/model/smart_info_response_dto.dart +++ /dev/null @@ -1,117 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class SmartInfoResponseDto { - /// Returns a new [SmartInfoResponseDto] instance. - SmartInfoResponseDto({ - this.objects = const [], - this.tags = const [], - }); - - List? objects; - - List? tags; - - @override - bool operator ==(Object other) => identical(this, other) || other is SmartInfoResponseDto && - _deepEquality.equals(other.objects, objects) && - _deepEquality.equals(other.tags, tags); - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (objects == null ? 0 : objects!.hashCode) + - (tags == null ? 0 : tags!.hashCode); - - @override - String toString() => 'SmartInfoResponseDto[objects=$objects, tags=$tags]'; - - Map toJson() { - final json = {}; - if (this.objects != null) { - json[r'objects'] = this.objects; - } else { - // json[r'objects'] = null; - } - if (this.tags != null) { - json[r'tags'] = this.tags; - } else { - // json[r'tags'] = null; - } - return json; - } - - /// Returns a new [SmartInfoResponseDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static SmartInfoResponseDto? fromJson(dynamic value) { - upgradeDto(value, "SmartInfoResponseDto"); - if (value is Map) { - final json = value.cast(); - - return SmartInfoResponseDto( - objects: json[r'objects'] is Iterable - ? (json[r'objects'] as Iterable).cast().toList(growable: false) - : const [], - tags: json[r'tags'] is Iterable - ? (json[r'tags'] as Iterable).cast().toList(growable: false) - : const [], - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = SmartInfoResponseDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = SmartInfoResponseDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of SmartInfoResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = SmartInfoResponseDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - }; -} - diff --git a/mobile/openapi/lib/model/system_config_backups_dto.dart b/mobile/openapi/lib/model/system_config_backups_dto.dart new file mode 100644 index 0000000000000..82cd6e59ebdd5 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_backups_dto.dart @@ -0,0 +1,99 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigBackupsDto { + /// Returns a new [SystemConfigBackupsDto] instance. + SystemConfigBackupsDto({ + required this.database, + }); + + DatabaseBackupConfig database; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigBackupsDto && + other.database == database; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (database.hashCode); + + @override + String toString() => 'SystemConfigBackupsDto[database=$database]'; + + Map toJson() { + final json = {}; + json[r'database'] = this.database; + return json; + } + + /// Returns a new [SystemConfigBackupsDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigBackupsDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigBackupsDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigBackupsDto( + database: DatabaseBackupConfig.fromJson(json[r'database'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigBackupsDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigBackupsDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigBackupsDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigBackupsDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'database', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_dto.dart b/mobile/openapi/lib/model/system_config_dto.dart index 5306370d2d1f7..4215953906601 100644 --- a/mobile/openapi/lib/model/system_config_dto.dart +++ b/mobile/openapi/lib/model/system_config_dto.dart @@ -13,6 +13,7 @@ part of openapi.api; class SystemConfigDto { /// Returns a new [SystemConfigDto] instance. SystemConfigDto({ + required this.backup, required this.ffmpeg, required this.image, required this.job, @@ -33,6 +34,8 @@ class SystemConfigDto { required this.user, }); + SystemConfigBackupsDto backup; + SystemConfigFFmpegDto ffmpeg; SystemConfigImageDto image; @@ -71,6 +74,7 @@ class SystemConfigDto { @override bool operator ==(Object other) => identical(this, other) || other is SystemConfigDto && + other.backup == backup && other.ffmpeg == ffmpeg && other.image == image && other.job == job && @@ -93,6 +97,7 @@ class SystemConfigDto { @override int get hashCode => // ignore: unnecessary_parenthesis + (backup.hashCode) + (ffmpeg.hashCode) + (image.hashCode) + (job.hashCode) + @@ -113,10 +118,11 @@ class SystemConfigDto { (user.hashCode); @override - String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, image=$image, job=$job, library_=$library_, logging=$logging, machineLearning=$machineLearning, map=$map, metadata=$metadata, newVersionCheck=$newVersionCheck, notifications=$notifications, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, server=$server, storageTemplate=$storageTemplate, theme=$theme, trash=$trash, user=$user]'; + String toString() => 'SystemConfigDto[backup=$backup, ffmpeg=$ffmpeg, image=$image, job=$job, library_=$library_, logging=$logging, machineLearning=$machineLearning, map=$map, metadata=$metadata, newVersionCheck=$newVersionCheck, notifications=$notifications, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, server=$server, storageTemplate=$storageTemplate, theme=$theme, trash=$trash, user=$user]'; Map toJson() { final json = {}; + json[r'backup'] = this.backup; json[r'ffmpeg'] = this.ffmpeg; json[r'image'] = this.image; json[r'job'] = this.job; @@ -147,6 +153,7 @@ class SystemConfigDto { final json = value.cast(); return SystemConfigDto( + backup: SystemConfigBackupsDto.fromJson(json[r'backup'])!, ffmpeg: SystemConfigFFmpegDto.fromJson(json[r'ffmpeg'])!, image: SystemConfigImageDto.fromJson(json[r'image'])!, job: SystemConfigJobDto.fromJson(json[r'job'])!, @@ -212,6 +219,7 @@ class SystemConfigDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { + 'backup', 'ffmpeg', 'image', 'job', diff --git a/mobile/openapi/lib/model/system_config_f_fmpeg_dto.dart b/mobile/openapi/lib/model/system_config_f_fmpeg_dto.dart index 73f7d35aecc30..0acfc9e8fbf9d 100644 --- a/mobile/openapi/lib/model/system_config_f_fmpeg_dto.dart +++ b/mobile/openapi/lib/model/system_config_f_fmpeg_dto.dart @@ -23,7 +23,6 @@ class SystemConfigFFmpegDto { required this.crf, required this.gopSize, required this.maxBitrate, - required this.npl, required this.preferredHwDevice, required this.preset, required this.refs, @@ -62,9 +61,6 @@ class SystemConfigFFmpegDto { String maxBitrate; - /// Minimum value: 0 - int npl; - String preferredHwDevice; String preset; @@ -102,7 +98,6 @@ class SystemConfigFFmpegDto { other.crf == crf && other.gopSize == gopSize && other.maxBitrate == maxBitrate && - other.npl == npl && other.preferredHwDevice == preferredHwDevice && other.preset == preset && other.refs == refs && @@ -128,7 +123,6 @@ class SystemConfigFFmpegDto { (crf.hashCode) + (gopSize.hashCode) + (maxBitrate.hashCode) + - (npl.hashCode) + (preferredHwDevice.hashCode) + (preset.hashCode) + (refs.hashCode) + @@ -142,7 +136,7 @@ class SystemConfigFFmpegDto { (twoPass.hashCode); @override - String toString() => 'SystemConfigFFmpegDto[accel=$accel, accelDecode=$accelDecode, acceptedAudioCodecs=$acceptedAudioCodecs, acceptedContainers=$acceptedContainers, acceptedVideoCodecs=$acceptedVideoCodecs, bframes=$bframes, cqMode=$cqMode, crf=$crf, gopSize=$gopSize, maxBitrate=$maxBitrate, npl=$npl, preferredHwDevice=$preferredHwDevice, preset=$preset, refs=$refs, targetAudioCodec=$targetAudioCodec, targetResolution=$targetResolution, targetVideoCodec=$targetVideoCodec, temporalAQ=$temporalAQ, threads=$threads, tonemap=$tonemap, transcode=$transcode, twoPass=$twoPass]'; + String toString() => 'SystemConfigFFmpegDto[accel=$accel, accelDecode=$accelDecode, acceptedAudioCodecs=$acceptedAudioCodecs, acceptedContainers=$acceptedContainers, acceptedVideoCodecs=$acceptedVideoCodecs, bframes=$bframes, cqMode=$cqMode, crf=$crf, gopSize=$gopSize, maxBitrate=$maxBitrate, preferredHwDevice=$preferredHwDevice, preset=$preset, refs=$refs, targetAudioCodec=$targetAudioCodec, targetResolution=$targetResolution, targetVideoCodec=$targetVideoCodec, temporalAQ=$temporalAQ, threads=$threads, tonemap=$tonemap, transcode=$transcode, twoPass=$twoPass]'; Map toJson() { final json = {}; @@ -156,7 +150,6 @@ class SystemConfigFFmpegDto { json[r'crf'] = this.crf; json[r'gopSize'] = this.gopSize; json[r'maxBitrate'] = this.maxBitrate; - json[r'npl'] = this.npl; json[r'preferredHwDevice'] = this.preferredHwDevice; json[r'preset'] = this.preset; json[r'refs'] = this.refs; @@ -190,7 +183,6 @@ class SystemConfigFFmpegDto { crf: mapValueOfType(json, r'crf')!, gopSize: mapValueOfType(json, r'gopSize')!, maxBitrate: mapValueOfType(json, r'maxBitrate')!, - npl: mapValueOfType(json, r'npl')!, preferredHwDevice: mapValueOfType(json, r'preferredHwDevice')!, preset: mapValueOfType(json, r'preset')!, refs: mapValueOfType(json, r'refs')!, @@ -259,7 +251,6 @@ class SystemConfigFFmpegDto { 'crf', 'gopSize', 'maxBitrate', - 'npl', 'preferredHwDevice', 'preset', 'refs', diff --git a/mobile/openapi/lib/model/usage_by_user_dto.dart b/mobile/openapi/lib/model/usage_by_user_dto.dart index e6f9216d74572..80235915fea8e 100644 --- a/mobile/openapi/lib/model/usage_by_user_dto.dart +++ b/mobile/openapi/lib/model/usage_by_user_dto.dart @@ -16,6 +16,8 @@ class UsageByUserDto { required this.photos, required this.quotaSizeInBytes, required this.usage, + required this.usagePhotos, + required this.usageVideos, required this.userId, required this.userName, required this.videos, @@ -27,6 +29,10 @@ class UsageByUserDto { int usage; + int usagePhotos; + + int usageVideos; + String userId; String userName; @@ -38,6 +44,8 @@ class UsageByUserDto { other.photos == photos && other.quotaSizeInBytes == quotaSizeInBytes && other.usage == usage && + other.usagePhotos == usagePhotos && + other.usageVideos == usageVideos && other.userId == userId && other.userName == userName && other.videos == videos; @@ -48,12 +56,14 @@ class UsageByUserDto { (photos.hashCode) + (quotaSizeInBytes == null ? 0 : quotaSizeInBytes!.hashCode) + (usage.hashCode) + + (usagePhotos.hashCode) + + (usageVideos.hashCode) + (userId.hashCode) + (userName.hashCode) + (videos.hashCode); @override - String toString() => 'UsageByUserDto[photos=$photos, quotaSizeInBytes=$quotaSizeInBytes, usage=$usage, userId=$userId, userName=$userName, videos=$videos]'; + String toString() => 'UsageByUserDto[photos=$photos, quotaSizeInBytes=$quotaSizeInBytes, usage=$usage, usagePhotos=$usagePhotos, usageVideos=$usageVideos, userId=$userId, userName=$userName, videos=$videos]'; Map toJson() { final json = {}; @@ -64,6 +74,8 @@ class UsageByUserDto { // json[r'quotaSizeInBytes'] = null; } json[r'usage'] = this.usage; + json[r'usagePhotos'] = this.usagePhotos; + json[r'usageVideos'] = this.usageVideos; json[r'userId'] = this.userId; json[r'userName'] = this.userName; json[r'videos'] = this.videos; @@ -82,6 +94,8 @@ class UsageByUserDto { photos: mapValueOfType(json, r'photos')!, quotaSizeInBytes: mapValueOfType(json, r'quotaSizeInBytes'), usage: mapValueOfType(json, r'usage')!, + usagePhotos: mapValueOfType(json, r'usagePhotos')!, + usageVideos: mapValueOfType(json, r'usageVideos')!, userId: mapValueOfType(json, r'userId')!, userName: mapValueOfType(json, r'userName')!, videos: mapValueOfType(json, r'videos')!, @@ -135,6 +149,8 @@ class UsageByUserDto { 'photos', 'quotaSizeInBytes', 'usage', + 'usagePhotos', + 'usageVideos', 'userId', 'userName', 'videos', diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index fe40017ba85ee..8b43cf33de92a 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -548,10 +548,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: dd6676d8c2926537eccdf9f72128bbb2a9d0814689527b17f92c248ff192eaf3 + sha256: "674173fd3c9eda9d4c8528da2ce0ea69f161577495a9cc835a2a4ecd7eadeb35" url: "https://pub.dev" source: hosted - version: "17.2.1+2" + version: "17.2.4" flutter_local_notifications_linux: dependency: transitive description: @@ -622,10 +622,10 @@ packages: dependency: "direct main" description: name: flutter_web_auth - sha256: a69fa8f43b9e4d86ac72176bf747b735e7b977dd7cf215076d95b87cb05affdd + sha256: "95e4856e24fb6ac1678f5ff334743b63f782d839ab324543d29ccbd295176209" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.6.0" flutter_web_plugins: dependency: transitive description: flutter @@ -876,26 +876,26 @@ packages: dependency: "direct main" description: name: isar - sha256: "99165dadb2cf2329d3140198363a7e7bff9bbd441871898a87e26914d25cf1ea" - url: "https://pub.dev" + sha256: e17a9555bc7f22ff26568b8c64d019b4ffa2dc6bd4cb1c8d9b269aefd32e53ad + url: "https://pub.isar-community.dev" source: hosted - version: "3.1.0+1" + version: "3.1.8" isar_flutter_libs: dependency: "direct main" description: name: isar_flutter_libs - sha256: bc6768cc4b9c61aabff77152e7f33b4b17d2fc93134f7af1c3dd51500fe8d5e8 - url: "https://pub.dev" + sha256: "78710781e658ce4bff59b3f38c5b2735e899e627f4e926e1221934e77b95231a" + url: "https://pub.isar-community.dev" source: hosted - version: "3.1.0+1" + version: "3.1.8" isar_generator: dependency: "direct dev" description: name: isar_generator - sha256: "76c121e1295a30423604f2f819bc255bc79f852f3bc8743a24017df6068ad133" - url: "https://pub.dev" + sha256: "484e73d3b7e81dbd816852fe0b9497333118a9aeb646fd2d349a62cc8980ffe1" + url: "https://pub.isar-community.dev" source: hosted - version: "3.1.0+1" + version: "3.1.8" js: dependency: transitive description: @@ -1211,10 +1211,10 @@ packages: dependency: "direct main" description: name: photo_manager - sha256: "70159eee32203e8162d49d588232f0299ed3f383c63eef1e899cb6b83dee6b26" + sha256: f5ef2618870e9a50d8bfeb81a02c242d580ae8614bd5ea9e1b80dbb7e49d4260 url: "https://pub.dev" source: hosted - version: "3.5.1" + version: "3.6.1" photo_manager_image_provider: dependency: "direct main" description: @@ -1712,12 +1712,12 @@ packages: dependency: "direct main" description: name: video_player - sha256: e30df0d226c4ef82e2c150ebf6834b3522cf3f654d8e2f9419d376cdc071425d + sha256: "4a8c3492d734f7c39c2588a3206707a05ee80cef52e8c7f3b2078d430c84bc17" url: "https://pub.dev" source: hosted - version: "2.9.1" + version: "2.9.2" video_player_android: - dependency: transitive + dependency: "direct main" description: name: video_player_android sha256: "4de50df9ee786f5891d3281e1e633d7b142ef1acf47392592eb91cba5d355849" @@ -1861,5 +1861,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.5.0 <4.0.0" - flutter: ">=3.24.3" + dart: ">=3.5.3 <4.0.0" + flutter: ">=3.24.4" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 5177e64bcfe91..f20fb7afdabef 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -2,18 +2,20 @@ name: immich_mobile description: Immich - selfhosted backup media file on mobile phone publish_to: 'none' -version: 1.118.2+163 +version: 1.120.2+167 environment: sdk: '>=3.3.0 <4.0.0' - flutter: 3.24.3 + flutter: 3.24.4 + +isar_version: &isar_version 3.1.8 # define the version to be used dependencies: flutter: sdk: flutter path_provider_ios: - photo_manager: ^3.5.1 + photo_manager: ^3.6.1 photo_manager_image_provider: ^2.2.0 flutter_hooks: ^0.20.4 hooks_riverpod: ^2.4.9 @@ -23,7 +25,8 @@ dependencies: intl: ^0.19.0 auto_route: ^9.2.0 fluttertoast: ^8.2.4 - video_player: ^2.8.2 + video_player: ^2.9.2 + video_player_android: 2.6.0 chewie: ^1.7.4 socket_io_client: ^2.0.3+1 maplibre_gl: 0.19.0+2 @@ -42,10 +45,14 @@ dependencies: path_provider: ^2.1.2 collection: ^1.18.0 http_parser: ^4.0.2 - flutter_web_auth: ^0.5.0 + flutter_web_auth: ^0.6.0 easy_image_viewer: ^1.4.0 - isar: ^3.1.0+1 - isar_flutter_libs: ^3.1.0+1 + isar: + version: *isar_version + hosted: https://pub.isar-community.dev/ + isar_flutter_libs: # contains Isar Core + version: *isar_version + hosted: https://pub.isar-community.dev/ permission_handler: ^11.2.0 device_info_plus: ^11.0.0 connectivity_plus: ^6.0.0 @@ -92,7 +99,9 @@ dev_dependencies: auto_route_generator: ^9.0.0 flutter_launcher_icons: ^0.14.0 flutter_native_splash: ^2.3.9 - isar_generator: ^3.1.0+1 + isar_generator: + version: *isar_version + hosted: https://pub.isar-community.dev/ integration_test: sdk: flutter custom_lint: ^0.6.4 diff --git a/mobile/scripts/fdroid_build_isar.sh b/mobile/scripts/fdroid_build_isar.sh index 41517737c94f5..f42bc51d9a09f 100755 --- a/mobile/scripts/fdroid_build_isar.sh +++ b/mobile/scripts/fdroid_build_isar.sh @@ -8,11 +8,11 @@ bash tool/build_android.sh x64 bash tool/build_android.sh armv7 bash tool/build_android.sh arm64 mv libisar_android_arm64.so libisar.so -mv libisar.so ../.pub-cache/hosted/pub.dev/isar_flutter_libs-*/android/src/main/jniLibs/arm64-v8a/ +mv libisar.so ../.pub-cache/hosted/pub.isar-community.dev/isar_flutter_libs-*/android/src/main/jniLibs/arm64-v8a/ mv libisar_android_armv7.so libisar.so -mv libisar.so ../.pub-cache/hosted/pub.dev/isar_flutter_libs-*/android/src/main/jniLibs/armeabi-v7a/ +mv libisar.so ../.pub-cache/hosted/pub.isar-community.dev/isar_flutter_libs-*/android/src/main/jniLibs/armeabi-v7a/ mv libisar_android_x64.so libisar.so -mv libisar.so ../.pub-cache/hosted/pub.dev/isar_flutter_libs-*/android/src/main/jniLibs/x86_64/ +mv libisar.so ../.pub-cache/hosted/pub.isar-community.dev/isar_flutter_libs-*/android/src/main/jniLibs/x86_64/ mv libisar_android_x86.so libisar.so -mv libisar.so ../.pub-cache/hosted/pub.dev/isar_flutter_libs-*/android/src/main/jniLibs/x86/ +mv libisar.so ../.pub-cache/hosted/pub.isar-community.dev/isar_flutter_libs-*/android/src/main/jniLibs/x86/ ) \ No newline at end of file diff --git a/mobile/test/services/album.service_test.dart b/mobile/test/services/album.service_test.dart index 848d7cfad7078..c0775a1c3e070 100644 --- a/mobile/test/services/album.service_test.dart +++ b/mobile/test/services/album.service_test.dart @@ -54,6 +54,7 @@ void main() { .thenAnswer((_) async => []); when(() => backupRepository.getIdsBySelection(BackupSelection.select)) .thenAnswer((_) async => []); + when(() => albumMediaRepository.getAll()).thenAnswer((_) async => []); when(() => albumRepository.count(local: true)).thenAnswer((_) async => 1); when(() => syncService.removeAllLocalAlbumsAndAssets()) .thenAnswer((_) async => true); diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index b99da367b8665..5bddf2f3d22f7 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -7385,7 +7385,7 @@ "info": { "title": "Immich", "description": "Immich API", - "version": "1.118.2", + "version": "1.120.2", "contact": {} }, "tags": [], @@ -7745,6 +7745,9 @@ "backgroundTask": { "$ref": "#/components/schemas/JobStatusDto" }, + "backupDatabase": { + "$ref": "#/components/schemas/JobStatusDto" + }, "duplicateDetection": { "$ref": "#/components/schemas/JobStatusDto" }, @@ -7787,6 +7790,7 @@ }, "required": [ "backgroundTask", + "backupDatabase", "duplicateDetection", "faceDetection", "facialRecognition", @@ -8398,9 +8402,6 @@ "description": "This property was deprecated in v1.113.0", "type": "boolean" }, - "smartInfo": { - "$ref": "#/components/schemas/SmartInfoResponseDto" - }, "stack": { "allOf": [ { @@ -8754,6 +8755,26 @@ ], "type": "object" }, + "DatabaseBackupConfig": { + "properties": { + "cronExpression": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "keepLastAmount": { + "minimum": 1, + "type": "number" + } + }, + "required": [ + "cronExpression", + "enabled", + "keepLastAmount" + ], + "type": "object" + }, "DownloadArchiveInfo": { "properties": { "assetIds": { @@ -9289,7 +9310,8 @@ "search", "sidecar", "library", - "notifications" + "notifications", + "backupDatabase" ], "type": "string" }, @@ -10944,7 +10966,9 @@ { "photos": 1, "videos": 1, - "diskUsageRaw": 1 + "diskUsageRaw": 2, + "usagePhotos": 1, + "usageVideos": 1 } ], "items": { @@ -10953,6 +10977,16 @@ "title": "Array of usage for each user", "type": "array" }, + "usagePhotos": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "usageVideos": { + "default": 0, + "format": "int64", + "type": "integer" + }, "videos": { "default": 0, "type": "integer" @@ -10962,6 +10996,8 @@ "photos", "usage", "usageByUser", + "usagePhotos", + "usageVideos", "videos" ], "type": "object" @@ -11259,25 +11295,6 @@ ], "type": "object" }, - "SmartInfoResponseDto": { - "properties": { - "objects": { - "items": { - "type": "string" - }, - "nullable": true, - "type": "array" - }, - "tags": { - "items": { - "type": "string" - }, - "nullable": true, - "type": "array" - } - }, - "type": "object" - }, "SmartSearchDto": { "properties": { "city": { @@ -11456,8 +11473,22 @@ }, "type": "object" }, + "SystemConfigBackupsDto": { + "properties": { + "database": { + "$ref": "#/components/schemas/DatabaseBackupConfig" + } + }, + "required": [ + "database" + ], + "type": "object" + }, "SystemConfigDto": { "properties": { + "backup": { + "$ref": "#/components/schemas/SystemConfigBackupsDto" + }, "ffmpeg": { "$ref": "#/components/schemas/SystemConfigFFmpegDto" }, @@ -11514,6 +11545,7 @@ } }, "required": [ + "backup", "ffmpeg", "image", "job", @@ -11581,10 +11613,6 @@ "maxBitrate": { "type": "string" }, - "npl": { - "minimum": 0, - "type": "integer" - }, "preferredHwDevice": { "type": "string" }, @@ -11633,7 +11661,6 @@ "crf", "gopSize", "maxBitrate", - "npl", "preferredHwDevice", "preset", "refs", @@ -12490,6 +12517,14 @@ "format": "int64", "type": "integer" }, + "usagePhotos": { + "format": "int64", + "type": "integer" + }, + "usageVideos": { + "format": "int64", + "type": "integer" + }, "userId": { "type": "string" }, @@ -12504,6 +12539,8 @@ "photos", "quotaSizeInBytes", "usage", + "usagePhotos", + "usageVideos", "userId", "userName", "videos" diff --git a/open-api/typescript-sdk/.nvmrc b/open-api/typescript-sdk/.nvmrc index 2a393af592b8c..7af24b7ddbde0 100644 --- a/open-api/typescript-sdk/.nvmrc +++ b/open-api/typescript-sdk/.nvmrc @@ -1 +1 @@ -20.18.0 +22.11.0 diff --git a/open-api/typescript-sdk/package-lock.json b/open-api/typescript-sdk/package-lock.json index c1e68d967ffd0..da78d70a77f28 100644 --- a/open-api/typescript-sdk/package-lock.json +++ b/open-api/typescript-sdk/package-lock.json @@ -1,18 +1,18 @@ { "name": "@immich/sdk", - "version": "1.118.2", + "version": "1.120.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@immich/sdk", - "version": "1.118.2", + "version": "1.120.2", "license": "GNU Affero General Public License version 3", "dependencies": { "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^20.16.12", + "@types/node": "^22.9.0", "typescript": "^5.3.3" } }, @@ -22,13 +22,13 @@ "integrity": "sha512-8tKiYffhwTGHSHYGnZ3oneLGCjX0po/XAXQ5Ng9fqKkvIdl/xz8+Vh8i+6xjzZqvZ2pLVpUcuSfnvNI/x67L0g==" }, "node_modules/@types/node": { - "version": "20.16.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.13.tgz", - "integrity": "sha512-GjQ7im10B0labo8ZGXDGROUl9k0BNyDgzfGpb4g/cl+4yYDWVKcozANF4FGr4/p0O/rAkQClM6Wiwkije++1Tg==", + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.19.8" } }, "node_modules/typescript": { diff --git a/open-api/typescript-sdk/package.json b/open-api/typescript-sdk/package.json index e323ea15c5776..5cc9cb3e9a8b3 100644 --- a/open-api/typescript-sdk/package.json +++ b/open-api/typescript-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@immich/sdk", - "version": "1.118.2", + "version": "1.120.2", "description": "Auto-generated TypeScript SDK for the Immich API", "type": "module", "main": "./build/index.js", @@ -19,7 +19,7 @@ "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^20.16.12", + "@types/node": "^22.9.0", "typescript": "^5.3.3" }, "repository": { @@ -28,6 +28,6 @@ "directory": "open-api/typescript-sdk" }, "volta": { - "node": "20.18.0" + "node": "22.11.0" } } diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 17079c07c3445..332814424d1d2 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -1,6 +1,6 @@ /** * Immich - * 1.118.2 + * 1.120.2 * DO NOT MODIFY - This file has been generated using oazapfts. * See https://www.npmjs.com/package/oazapfts */ @@ -221,10 +221,6 @@ export type PersonWithFacesResponseDto = { /** This property was added in v1.107.0 */ updatedAt?: string; }; -export type SmartInfoResponseDto = { - objects?: string[] | null; - tags?: string[] | null; -}; export type AssetStackResponseDto = { assetCount: number; id: string; @@ -267,7 +263,6 @@ export type AssetResponseDto = { people?: PersonWithFacesResponseDto[]; /** This property was deprecated in v1.113.0 */ resized?: boolean; - smartInfo?: SmartInfoResponseDto; stack?: (AssetStackResponseDto) | null; tags?: TagResponseDto[]; thumbhash: string | null; @@ -535,6 +530,7 @@ export type JobStatusDto = { }; export type AllJobStatusResponseDto = { backgroundTask: JobStatusDto; + backupDatabase: JobStatusDto; duplicateDetection: JobStatusDto; faceDetection: JobStatusDto; facialRecognition: JobStatusDto; @@ -973,6 +969,8 @@ export type UsageByUserDto = { photos: number; quotaSizeInBytes: number | null; usage: number; + usagePhotos: number; + usageVideos: number; userId: string; userName: string; videos: number; @@ -981,6 +979,8 @@ export type ServerStatsResponseDto = { photos: number; usage: number; usageByUser: UsageByUserDto[]; + usagePhotos: number; + usageVideos: number; videos: number; }; export type ServerStorageResponseDto = { @@ -1084,6 +1084,14 @@ export type AssetFullSyncDto = { updatedUntil: string; userId?: string; }; +export type DatabaseBackupConfig = { + cronExpression: string; + enabled: boolean; + keepLastAmount: number; +}; +export type SystemConfigBackupsDto = { + database: DatabaseBackupConfig; +}; export type SystemConfigFFmpegDto = { accel: TranscodeHWAccel; accelDecode: boolean; @@ -1095,7 +1103,6 @@ export type SystemConfigFFmpegDto = { crf: number; gopSize: number; maxBitrate: string; - npl: number; preferredHwDevice: string; preset: string; refs: number; @@ -1232,6 +1239,7 @@ export type SystemConfigUserDto = { deleteDelay: number; }; export type SystemConfigDto = { + backup: SystemConfigBackupsDto; ffmpeg: SystemConfigFFmpegDto; image: SystemConfigImageDto; job: SystemConfigJobDto; @@ -3445,7 +3453,8 @@ export enum JobName { Search = "search", Sidecar = "sidecar", Library = "library", - Notifications = "notifications" + Notifications = "notifications", + BackupDatabase = "backupDatabase" } export enum JobCommand { Start = "start", diff --git a/readme_i18n/README_de_DE.md b/readme_i18n/README_de_DE.md index d6c69106f3930..70ad472aa1852 100644 --- a/readme_i18n/README_de_DE.md +++ b/readme_i18n/README_de_DE.md @@ -32,6 +32,7 @@ Português Brasileiro Svenska العربية + Tiếng Việt ภาษาไทย

@@ -102,6 +103,8 @@ Für die Handy-App kannst Du `https://demo.immich.app/api` als `Server Endpoint | Offline Unterstützung | Ja | Nein | | Schreibgeschützte Gallerie | Ja | Ja | | Gestapelte Bilder | Ja | Ja | +| Tags | Nein | Ja | +| Ordner-Ansicht | Nein | Ja | ## Übersetzungen diff --git a/server/.nvmrc b/server/.nvmrc index 2a393af592b8c..7af24b7ddbde0 100644 --- a/server/.nvmrc +++ b/server/.nvmrc @@ -1 +1 @@ -20.18.0 +22.11.0 diff --git a/server/Dockerfile b/server/Dockerfile index d1fc0481a1da2..6c8da4e305fd5 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,5 +1,5 @@ # dev build -FROM ghcr.io/immich-app/base-server-dev:20241024@sha256:b15c96837d2da805fd54ac305926f9cbe33317bdafb1d46618133d34ee184af4 AS dev +FROM ghcr.io/immich-app/base-server-dev:20241112@sha256:889647c747b3f999b05e387eff414bcec5e42477958b267930e58ac58dadcfc7 AS dev RUN apt-get install --no-install-recommends -yqq tini WORKDIR /usr/src/app @@ -25,7 +25,7 @@ COPY --from=dev /usr/src/app/node_modules/@img ./node_modules/@img COPY --from=dev /usr/src/app/node_modules/exiftool-vendored.pl ./node_modules/exiftool-vendored.pl # web build -FROM node:20.18.0-alpine3.20@sha256:c13b26e7e602ef2f1074aef304ce6e9b7dd284c419b35d89fcf3cc8e44a8def9 AS web +FROM node:22.11.0-alpine3.20@sha256:dc8ba2f61dd86c44e43eb25a7812ad03c5b1b224a19fc6f77e1eb9e5669f0b82 AS web WORKDIR /usr/src/open-api/typescript-sdk COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./ @@ -42,7 +42,7 @@ RUN npm run build # prod build -FROM ghcr.io/immich-app/base-server-prod:20241024@sha256:5cf70de565d3a4a469c62b0c1b312fe6ed9b41e8a97febec48c2da0e1cd8d077 +FROM ghcr.io/immich-app/base-server-prod:20241112@sha256:26a209563689f52b9a63feeedde9a16a8e0e558483cd3feb5c936423e55c7eea WORKDIR /usr/src/app ENV NODE_ENV=production \ diff --git a/server/package-lock.json b/server/package-lock.json index 8f098542a59f9..08ad8a066f202 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,17 +1,16 @@ { "name": "immich", - "version": "1.118.2", + "version": "1.120.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "immich", - "version": "1.118.2", + "version": "1.120.2", "license": "GNU Affero General Public License version 3", "dependencies": { "@nestjs/bullmq": "^10.0.1", "@nestjs/common": "^10.2.2", - "@nestjs/config": "^3.0.0", "@nestjs/core": "^10.2.2", "@nestjs/event-emitter": "^2.0.4", "@nestjs/platform-express": "^10.2.2", @@ -20,10 +19,10 @@ "@nestjs/swagger": "^7.1.8", "@nestjs/typeorm": "^10.0.0", "@nestjs/websockets": "^10.2.2", - "@opentelemetry/auto-instrumentations-node": "^0.51.0", + "@opentelemetry/auto-instrumentations-node": "^0.52.0", "@opentelemetry/context-async-hooks": "^1.24.0", - "@opentelemetry/exporter-prometheus": "^0.53.0", - "@opentelemetry/sdk-node": "^0.53.0", + "@opentelemetry/exporter-prometheus": "^0.54.0", + "@opentelemetry/sdk-node": "^0.54.0", "@react-email/components": "^0.0.25", "@socket.io/redis-adapter": "^8.3.0", "archiver": "^7.0.0", @@ -63,7 +62,8 @@ "tailwindcss-preset-email": "^1.3.2", "thumbhash": "^0.1.1", "typeorm": "^0.3.17", - "ua-parser-js": "^1.0.35" + "ua-parser-js": "^1.0.35", + "validator": "^13.12.0" }, "devDependencies": { "@eslint/eslintrc": "^3.1.0", @@ -83,7 +83,7 @@ "@types/lodash": "^4.14.197", "@types/mock-fs": "^4.13.1", "@types/multer": "^1.4.7", - "@types/node": "^20.16.12", + "@types/node": "^22.9.0", "@types/nodemailer": "^6.4.14", "@types/picomatch": "^3.0.0", "@types/pngjs": "^6.0.5", @@ -479,17 +479,17 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz", - "integrity": "sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz", - "integrity": "sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "engines": { "node": ">=6.9.0" } @@ -667,13 +667,12 @@ } }, "node_modules/@babel/types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.6.tgz", - "integrity": "sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "dependencies": { - "@babel/helper-string-parser": "^7.24.6", - "@babel/helper-validator-identifier": "^7.24.6", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1141,9 +1140,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", - "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", + "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1209,9 +1208,9 @@ "dev": true }, "node_modules/@eslint/js": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.12.0.tgz", - "integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", + "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1260,9 +1259,9 @@ } }, "node_modules/@grpc/grpc-js": { - "version": "1.10.10", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.10.tgz", - "integrity": "sha512-HPa/K5NX6ahMoeBv15njAc/sfF4/jmiXLar9UlC2UfHFKZzsCVLc3wbe7+7qua7w9VPh2/L6EBxyAV7/E8Wftg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.2.tgz", + "integrity": "sha512-bgxdZmgTrJZX50OjyVwz3+mNEnCTNkh3cIqGPWVNeW9jX6bn1ZkU80uPd+67/ZpIJIjRQ9qaHCjhavyoWYxumg==", "dependencies": { "@grpc/proto-loader": "^0.7.13", "@js-sdsl/ordered-map": "^4.4.2" @@ -1992,11 +1991,11 @@ ] }, "node_modules/@nestjs/bull-shared": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-10.2.1.tgz", - "integrity": "sha512-zvnTvSq6OJ92omcsFUwaUmPbM3PRgWkIusHPB5TE3IFS7nNdM3OwF+kfe56sgKjMtQQMe/56lok0S04OtPMX5Q==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-10.2.2.tgz", + "integrity": "sha512-bMIEILYYovQWfdz6fCSTgqb/zuKyGmNSc7guB56MiZVW84JloUHb8330nNh3VWaamJKGtUzawbEoG2VR3uVeOg==", "dependencies": { - "tslib": "2.6.3" + "tslib": "2.8.0" }, "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", @@ -2004,12 +2003,12 @@ } }, "node_modules/@nestjs/bullmq": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@nestjs/bullmq/-/bullmq-10.2.1.tgz", - "integrity": "sha512-nDR0hDabmtXt5gsb5R786BJsGIJoWh/79sVmRETXf4S45+fvdqG1XkCKAeHF9TO9USodw9m+XBNKysTnkY41gw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@nestjs/bullmq/-/bullmq-10.2.2.tgz", + "integrity": "sha512-1RXhR7+XK6uXaw9uNH5hP9bcW5Vzkpc4lX7t7sUC23N9XH2CMH6uUm0I14T5KkvMKkj0VXj0GY+Ulh3pCtdwbA==", "dependencies": { - "@nestjs/bull-shared": "^10.2.1", - "tslib": "2.6.3" + "@nestjs/bull-shared": "^10.2.2", + "tslib": "2.8.0" }, "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", @@ -2076,9 +2075,9 @@ } }, "node_modules/@nestjs/common": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.5.tgz", - "integrity": "sha512-N/yUyuYCBMb0+H6jHhntR7PURzji0usID/DByhOfooyk/aPGscI0aQKwOA6edlJlT92hHUvXYLJ5p3npj7KcjQ==", + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.6.tgz", + "integrity": "sha512-KkezkZvU9poWaNq4L+lNvx+386hpOxPJkfXBBeSMrcqBOx8kVr36TGN2uYkF4Ta4zNu1KbCjmZbc0rhHSg296g==", "dependencies": { "iterare": "1.2.1", "tslib": "2.7.0", @@ -2108,24 +2107,10 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, - "node_modules/@nestjs/config": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.3.tgz", - "integrity": "sha512-p6yv/CvoBewJ72mBq4NXgOAi2rSQNWx3a+IMJLVKS2uiwFCOQQuiIatGwq6MRjXV3Jr+B41iUO8FIf4xBrZ4/w==", - "dependencies": { - "dotenv": "16.4.5", - "dotenv-expand": "10.0.0", - "lodash": "4.17.21" - }, - "peerDependencies": { - "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", - "rxjs": "^7.1.0" - } - }, "node_modules/@nestjs/core": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.5.tgz", - "integrity": "sha512-wk0KJ+6tuidqAdeemsQ40BCp1BgMsSuSLG577aqXLxXYoa8FQYPrdxoSzd05znYLwJYM55fisZWb3FLF9HT2qw==", + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.6.tgz", + "integrity": "sha512-zXVPxCNRfO6gAy0yvEDjUxE/8gfZICJFpsl2lZAUH31bPb6m+tXuhUq2mVCTEltyMYQ+DYtRe+fEYM2v152N1g==", "hasInstallScript": true, "dependencies": { "@nuxtjs/opencollective": "0.3.2", @@ -2165,9 +2150,9 @@ "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, "node_modules/@nestjs/event-emitter": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@nestjs/event-emitter/-/event-emitter-2.0.4.tgz", - "integrity": "sha512-quMiw8yOwoSul0pp3mOonGz8EyXWHSBTqBy8B0TbYYgpnG1Ix2wGUnuTksLWaaBiiOTDhciaZ41Y5fJZsSJE1Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/event-emitter/-/event-emitter-2.1.1.tgz", + "integrity": "sha512-6L6fBOZTyfFlL7Ih/JDdqlCzZeCW0RjCX28wnzGyg/ncv5F/EOeT1dfopQr1loBRQ3LTgu8OWM7n4zLN4xigsg==", "dependencies": { "eventemitter2": "6.4.9" }, @@ -2196,9 +2181,9 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.5.tgz", - "integrity": "sha512-a629r8R8KC4skhdieQ0aIWH5vDBUFntWnWKFyDXQrll6/CllSchfWm87mWF39seaW6bXYtQtAEZY66JrngdrGA==", + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.6.tgz", + "integrity": "sha512-HcyCpAKccAasrLSGRTGWv5BKRs0rwTIFOSsk6laNyqfqvgvYcJQAedarnm4jmaemtmSJ0PFI9PmtEZADd2ahCg==", "dependencies": { "body-parser": "1.20.3", "cors": "2.8.5", @@ -2221,11 +2206,11 @@ "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, "node_modules/@nestjs/platform-socket.io": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.4.5.tgz", - "integrity": "sha512-dHkHJQArhrpkX6qBdTW2ghuja3i3cCslwy4QHY6d46u+9UyANQlsNK9wt/lZnmXfCMaci8xAJvUpyODa6YtV7g==", + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.4.6.tgz", + "integrity": "sha512-lGv99O7C00wtnGq9M0mcwrOpH2qmuqAXQyvo/d/I7rmaf3OO1Sg8qWDLAnPKHdaumwOL2mnET3kvCJ06MaL6WA==", "dependencies": { - "socket.io": "4.7.5", + "socket.io": "4.8.0", "tslib": "2.7.0" }, "funding": { @@ -2269,13 +2254,13 @@ } }, "node_modules/@nestjs/schematics": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.2.2.tgz", - "integrity": "sha512-D4pJ46E8llCA7WPr3cV6sfRqDlvnTjQWnF1fLyKYD3Ldl+KPtlLyIcxaqlLTB0YR9ItKNKIZTJzUehRxR7UUsQ==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.2.3.tgz", + "integrity": "sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==", "dev": true, "dependencies": { - "@angular-devkit/core": "17.3.10", - "@angular-devkit/schematics": "17.3.10", + "@angular-devkit/core": "17.3.11", + "@angular-devkit/schematics": "17.3.11", "comment-json": "4.2.5", "jsonc-parser": "3.3.1", "pluralize": "8.0.0" @@ -2285,9 +2270,9 @@ } }, "node_modules/@nestjs/schematics/node_modules/@angular-devkit/core": { - "version": "17.3.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.10.tgz", - "integrity": "sha512-czdl54yxU5DOAGy/uUPNjJruoBDTgwi/V+eOgLNybYhgrc+TsY0f7uJ11yEk/pz5sCov7xIiS7RdRv96waS7vg==", + "version": "17.3.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.11.tgz", + "integrity": "sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==", "dev": true, "dependencies": { "ajv": "8.12.0", @@ -2318,12 +2303,12 @@ "dev": true }, "node_modules/@nestjs/schematics/node_modules/@angular-devkit/schematics": { - "version": "17.3.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.10.tgz", - "integrity": "sha512-FHcNa1ktYRd0SKExCsNJpR75RffsyuPIV8kvBXzXnLHmXMqvl25G2te3yYJ9yYqy9OLy/58HZznZTxWRyUdHOg==", + "version": "17.3.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.11.tgz", + "integrity": "sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==", "dev": true, "dependencies": { - "@angular-devkit/core": "17.3.10", + "@angular-devkit/core": "17.3.11", "jsonc-parser": "3.2.1", "magic-string": "0.30.8", "ora": "5.4.1", @@ -2392,9 +2377,9 @@ } }, "node_modules/@nestjs/testing": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.5.tgz", - "integrity": "sha512-3NhmztE+fK3MuuOZhXihvMIhxm0QuDM2BneHvM5A0oVLG+STsAeGBqbDr/Ef2qsvqH5HaqvfGbVJ4N1DQnZE5A==", + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.6.tgz", + "integrity": "sha512-aiDicKhlGibVGNYuew399H5qZZXaseOBT/BS+ERJxxCmco7ZdAqaujsNjSaSbTK9ojDPf27crLT0C4opjqJe3A==", "dev": true, "dependencies": { "tslib": "2.7.0" @@ -2440,9 +2425,9 @@ } }, "node_modules/@nestjs/websockets": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.4.5.tgz", - "integrity": "sha512-LbL/HRLWQUBTUPY7swojOHdvokyVGINIiuP/VmRdhob4T751r+9i09z2RqRpP71psuom9mnRHYI1+vT2FABrAw==", + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.4.6.tgz", + "integrity": "sha512-53YqDQylPAOudNFiiBvrN8QrRl/sZ9oEjKbD3wBVgrFREbaiuTySoyyy6HwVs60HW29uQwck+Bp7qkKGjhtQKg==", "dependencies": { "iterare": "1.2.1", "object-hash": "3.0.0", @@ -2467,14 +2452,14 @@ "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, "node_modules/@next/env": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.3.tgz", - "integrity": "sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==" + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.10.tgz", + "integrity": "sha512-dZIu93Bf5LUtluBXIv4woQw2cZVZ2DJTjax5/5DOs3lzEOeKLy7GxRSr4caK9/SCPdaW6bCgpye6+n4Dh9oJPw==" }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.3.tgz", - "integrity": "sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.10.tgz", + "integrity": "sha512-V3z10NV+cvMAfxQUMhKgfQnPbjw+Ew3cnr64b0lr8MDiBJs3eLnM6RpGC46nhfMZsiXgQngCJKWGTC/yDcgrDQ==", "cpu": [ "arm64" ], @@ -2487,9 +2472,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz", - "integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.10.tgz", + "integrity": "sha512-Y0TC+FXbFUQ2MQgimJ/7Ina2mXIKhE7F+GUe1SgnzRmwFY3hX2z8nyVCxE82I2RicspdkZnSWMn4oTjIKz4uzA==", "cpu": [ "x64" ], @@ -2502,9 +2487,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz", - "integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.10.tgz", + "integrity": "sha512-ZfQ7yOy5zyskSj9rFpa0Yd7gkrBnJTkYVSya95hX3zeBG9E55Z6OTNPn1j2BTFWvOVVj65C3T+qsjOyVI9DQpA==", "cpu": [ "arm64" ], @@ -2517,9 +2502,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz", - "integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.10.tgz", + "integrity": "sha512-n2i5o3y2jpBfXFRxDREr342BGIQCJbdAUi/K4q6Env3aSx8erM9VuKXHw5KNROK9ejFSPf0LhoSkU/ZiNdacpQ==", "cpu": [ "arm64" ], @@ -2532,9 +2517,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz", - "integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.10.tgz", + "integrity": "sha512-GXvajAWh2woTT0GKEDlkVhFNxhJS/XdDmrVHrPOA83pLzlGPQnixqxD8u3bBB9oATBKB//5e4vpACnx5Vaxdqg==", "cpu": [ "x64" ], @@ -2547,9 +2532,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz", - "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.10.tgz", + "integrity": "sha512-opFFN5B0SnO+HTz4Wq4HaylXGFV+iHrVxd3YvREUX9K+xfc4ePbRrxqOuPOFjtSuiVouwe6uLeDtabjEIbkmDA==", "cpu": [ "x64" ], @@ -2562,9 +2547,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz", - "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.10.tgz", + "integrity": "sha512-9NUzZuR8WiXTvv+EiU/MXdcQ1XUvFixbLIMNQiVHuzs7ZIFrJDLJDaOF1KaqttoTujpcxljM/RNAOmw1GhPPQQ==", "cpu": [ "arm64" ], @@ -2577,9 +2562,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz", - "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.10.tgz", + "integrity": "sha512-fr3aEbSd1GeW3YUMBkWAu4hcdjZ6g4NBl1uku4gAn661tcxd1bHs1THWYzdsbTRLcCKLjrDZlNp6j2HTfrw+Bg==", "cpu": [ "ia32" ], @@ -2592,9 +2577,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz", - "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.10.tgz", + "integrity": "sha512-UjeVoRGKNL2zfbcQ6fscmgjBAS/inHBh63mjIlfPg/NG8Yn2ztqylXt5qilYb6hoHIwaU2ogHknHWWmahJjgZQ==", "cpu": [ "x64" ], @@ -2658,7 +2643,8 @@ "node_modules/@one-ini/wasm": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", - "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==" + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "license": "MIT" }, "node_modules/@opentelemetry/api": { "version": "1.8.0", @@ -2669,68 +2655,68 @@ } }, "node_modules/@opentelemetry/api-logs": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", - "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.54.0.tgz", + "integrity": "sha512-9HhEh5GqFrassUndqJsyW7a0PzfyWr2eV2xwzHLIS+wX3125+9HE9FMRAKmJRwxZhgZGwH3HNQQjoMGZqmOeVA==", "dependencies": { - "@opentelemetry/api": "^1.0.0" + "@opentelemetry/api": "^1.3.0" }, "engines": { "node": ">=14" } }, "node_modules/@opentelemetry/auto-instrumentations-node": { - "version": "0.51.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.51.0.tgz", - "integrity": "sha512-xsgydgtJiToxvFsDcmLDrHiFfHOmdomqk4KCnr40YZdsfw7KO4RJEU0om2f7pFh6WUI5q8nSQ53QgZ+DAz6TzA==", - "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/instrumentation-amqplib": "^0.42.0", - "@opentelemetry/instrumentation-aws-lambda": "^0.45.0", - "@opentelemetry/instrumentation-aws-sdk": "^0.44.0", - "@opentelemetry/instrumentation-bunyan": "^0.41.0", - "@opentelemetry/instrumentation-cassandra-driver": "^0.41.0", - "@opentelemetry/instrumentation-connect": "^0.39.0", - "@opentelemetry/instrumentation-cucumber": "^0.9.0", - "@opentelemetry/instrumentation-dataloader": "^0.12.0", - "@opentelemetry/instrumentation-dns": "^0.39.0", - "@opentelemetry/instrumentation-express": "^0.43.0", - "@opentelemetry/instrumentation-fastify": "^0.40.0", - "@opentelemetry/instrumentation-fs": "^0.15.0", - "@opentelemetry/instrumentation-generic-pool": "^0.39.0", - "@opentelemetry/instrumentation-graphql": "^0.43.0", - "@opentelemetry/instrumentation-grpc": "^0.53.0", - "@opentelemetry/instrumentation-hapi": "^0.41.0", - "@opentelemetry/instrumentation-http": "^0.53.0", - "@opentelemetry/instrumentation-ioredis": "^0.43.0", - "@opentelemetry/instrumentation-kafkajs": "^0.3.0", - "@opentelemetry/instrumentation-knex": "^0.40.0", - "@opentelemetry/instrumentation-koa": "^0.43.0", - "@opentelemetry/instrumentation-lru-memoizer": "^0.40.0", - "@opentelemetry/instrumentation-memcached": "^0.39.0", - "@opentelemetry/instrumentation-mongodb": "^0.47.0", - "@opentelemetry/instrumentation-mongoose": "^0.42.0", - "@opentelemetry/instrumentation-mysql": "^0.41.0", - "@opentelemetry/instrumentation-mysql2": "^0.41.0", - "@opentelemetry/instrumentation-nestjs-core": "^0.40.0", - "@opentelemetry/instrumentation-net": "^0.39.0", - "@opentelemetry/instrumentation-pg": "^0.46.0", - "@opentelemetry/instrumentation-pino": "^0.42.0", - "@opentelemetry/instrumentation-redis": "^0.42.0", - "@opentelemetry/instrumentation-redis-4": "^0.42.1", - "@opentelemetry/instrumentation-restify": "^0.41.0", - "@opentelemetry/instrumentation-router": "^0.40.0", - "@opentelemetry/instrumentation-socket.io": "^0.42.0", - "@opentelemetry/instrumentation-tedious": "^0.14.0", - "@opentelemetry/instrumentation-undici": "^0.6.0", - "@opentelemetry/instrumentation-winston": "^0.40.0", - "@opentelemetry/resource-detector-alibaba-cloud": "^0.29.3", - "@opentelemetry/resource-detector-aws": "^1.6.2", - "@opentelemetry/resource-detector-azure": "^0.2.11", - "@opentelemetry/resource-detector-container": "^0.4.4", - "@opentelemetry/resource-detector-gcp": "^0.29.12", + "version": "0.52.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.52.0.tgz", + "integrity": "sha512-J9SgX7NOpTvQ7itvlOlHP3lTlsMWtVh5WQSHUSTlg2m3A9HlZBri2DtZ8QgNj8rYWe0EQxQ3TQ3H6vabfun4vw==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/instrumentation-amqplib": "^0.43.0", + "@opentelemetry/instrumentation-aws-lambda": "^0.46.0", + "@opentelemetry/instrumentation-aws-sdk": "^0.45.0", + "@opentelemetry/instrumentation-bunyan": "^0.42.0", + "@opentelemetry/instrumentation-cassandra-driver": "^0.42.0", + "@opentelemetry/instrumentation-connect": "^0.40.0", + "@opentelemetry/instrumentation-cucumber": "^0.10.0", + "@opentelemetry/instrumentation-dataloader": "^0.13.0", + "@opentelemetry/instrumentation-dns": "^0.40.0", + "@opentelemetry/instrumentation-express": "^0.44.0", + "@opentelemetry/instrumentation-fastify": "^0.41.0", + "@opentelemetry/instrumentation-fs": "^0.16.0", + "@opentelemetry/instrumentation-generic-pool": "^0.40.0", + "@opentelemetry/instrumentation-graphql": "^0.44.0", + "@opentelemetry/instrumentation-grpc": "^0.54.0", + "@opentelemetry/instrumentation-hapi": "^0.42.0", + "@opentelemetry/instrumentation-http": "^0.54.0", + "@opentelemetry/instrumentation-ioredis": "^0.44.0", + "@opentelemetry/instrumentation-kafkajs": "^0.4.0", + "@opentelemetry/instrumentation-knex": "^0.41.0", + "@opentelemetry/instrumentation-koa": "^0.44.0", + "@opentelemetry/instrumentation-lru-memoizer": "^0.41.0", + "@opentelemetry/instrumentation-memcached": "^0.40.0", + "@opentelemetry/instrumentation-mongodb": "^0.48.0", + "@opentelemetry/instrumentation-mongoose": "^0.43.0", + "@opentelemetry/instrumentation-mysql": "^0.42.0", + "@opentelemetry/instrumentation-mysql2": "^0.42.0", + "@opentelemetry/instrumentation-nestjs-core": "^0.41.0", + "@opentelemetry/instrumentation-net": "^0.40.0", + "@opentelemetry/instrumentation-pg": "^0.47.0", + "@opentelemetry/instrumentation-pino": "^0.43.0", + "@opentelemetry/instrumentation-redis": "^0.43.0", + "@opentelemetry/instrumentation-redis-4": "^0.43.0", + "@opentelemetry/instrumentation-restify": "^0.42.0", + "@opentelemetry/instrumentation-router": "^0.41.0", + "@opentelemetry/instrumentation-socket.io": "^0.43.0", + "@opentelemetry/instrumentation-tedious": "^0.15.0", + "@opentelemetry/instrumentation-undici": "^0.7.0", + "@opentelemetry/instrumentation-winston": "^0.41.0", + "@opentelemetry/resource-detector-alibaba-cloud": "^0.29.4", + "@opentelemetry/resource-detector-aws": "^1.7.0", + "@opentelemetry/resource-detector-azure": "^0.2.12", + "@opentelemetry/resource-detector-container": "^0.5.0", + "@opentelemetry/resource-detector-gcp": "^0.29.13", "@opentelemetry/resources": "^1.24.0", - "@opentelemetry/sdk-node": "^0.53.0" + "@opentelemetry/sdk-node": "^0.54.0" }, "engines": { "node": ">=14" @@ -2740,9 +2726,9 @@ } }, "node_modules/@opentelemetry/context-async-hooks": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.26.0.tgz", - "integrity": "sha512-HedpXXYzzbaoutw6DFLWLDket2FwLkLpil4hGCZ1xYEIMTcivdfwEOISgdbLEWyG3HW52gTq2V9mOVJrONgiwg==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.27.0.tgz", + "integrity": "sha512-CdZ3qmHCwNhFAzjTgHqrDQ44Qxcpz43cVxZRhOs+Ns/79ug+Mr84Bkb626bkJLkA3+BLimA5YAEVRlJC6pFb7g==", "engines": { "node": ">=14" }, @@ -2751,9 +2737,9 @@ } }, "node_modules/@opentelemetry/core": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.26.0.tgz", - "integrity": "sha512-1iKxXXE8415Cdv0yjG3G6hQnB5eVEsJce3QaawX8SjDn0mAS0ZM8fAbZZJD4ajvhC15cePvosSCut404KrIIvQ==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.27.0.tgz", + "integrity": "sha512-yQPKnK5e+76XuiqUH/gKyS8wv/7qITd5ln56QkBTf3uggr0VkXOXfcaAuG330UfdYu83wsyoBwqwxigpIG+Jkg==", "dependencies": { "@opentelemetry/semantic-conventions": "1.27.0" }, @@ -2765,67 +2751,15 @@ } }, "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.53.0.tgz", - "integrity": "sha512-x5ygAQgWAQOI+UOhyV3z9eW7QU2dCfnfOuIBiyYmC2AWr74f6x/3JBnP27IAcEx6aihpqBYWKnpoUTztkVPAZw==", - "dependencies": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/sdk-logs": "0.53.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.53.0.tgz", - "integrity": "sha512-UCWPreGQEhD6FjBaeDuXhiMf6kkBODF0ZQzrk/tuQcaVDJ+dDQ/xhJp192H9yWnKxVpEjFrSSLnpqmX4VwX+eA==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-transformer": "0.53.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/otlp-grpc-exporter-base": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.53.0.tgz", - "integrity": "sha512-F7RCN8VN+lzSa4fGjewit8Z5fEUpY/lmMVy5EWn2ZpbAabg3EE3sCLuTNfOiooNGnmvzimUPruoeqeko/5/TzQ==", + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.54.0.tgz", + "integrity": "sha512-CQC9xl9p8EIvx2KggdM7yffbpmUArKjiqAcjTTTEvqE8kOOf71NSuBU0FXs14FU8vBGTUlsr3oI4vGeWF8FakA==", "dependencies": { "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/otlp-transformer": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.53.0.tgz", - "integrity": "sha512-rM0sDA9HD8dluwuBxLetUmoqGJKSAbWenwD65KY9iZhUxdBHRLrIdrABfNDP7aiTjcgK8XFyTn5fhDz7N+W6DA==", - "dependencies": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-metrics": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "protobufjs": "^7.3.0" + "@opentelemetry/core": "1.27.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.54.0", + "@opentelemetry/otlp-transformer": "0.54.0", + "@opentelemetry/sdk-logs": "0.54.0" }, "engines": { "node": ">=14" @@ -2834,113 +2768,16 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/resources": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.26.0.tgz", - "integrity": "sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/sdk-logs": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.53.0.tgz", - "integrity": "sha512-dhSisnEgIj/vJZXZV6f6KcTnyLDx/VuQ6l3ejuZpMpPlh9S1qMHiZU9NMmOkVkwwHkMy3G6mEBwdP23vUZVr4g==", - "dependencies": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.4.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/sdk-metrics": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.26.0.tgz", - "integrity": "sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz", - "integrity": "sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, "node_modules/@opentelemetry/exporter-logs-otlp-http": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.53.0.tgz", - "integrity": "sha512-cSRKgD/n8rb+Yd+Cif6EnHEL/VZg1o8lEcEwFji1lwene6BdH51Zh3feAD9p2TyVoBKrl6Q9Zm2WltSp2k9gWQ==", + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.54.0.tgz", + "integrity": "sha512-EX/5YPtFw5hugURWSmOtJEGsjphkwTRAiv2yay40ADCLEzajhI/tM3v/7hFCj+rm37sGFMNawpi3mGLvfKGexQ==", "dependencies": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/sdk-logs": "0.53.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.53.0.tgz", - "integrity": "sha512-UCWPreGQEhD6FjBaeDuXhiMf6kkBODF0ZQzrk/tuQcaVDJ+dDQ/xhJp192H9yWnKxVpEjFrSSLnpqmX4VwX+eA==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-transformer": "0.53.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/otlp-transformer": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.53.0.tgz", - "integrity": "sha512-rM0sDA9HD8dluwuBxLetUmoqGJKSAbWenwD65KY9iZhUxdBHRLrIdrABfNDP7aiTjcgK8XFyTn5fhDz7N+W6DA==", - "dependencies": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-metrics": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "protobufjs": "^7.3.0" + "@opentelemetry/api-logs": "0.54.0", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/otlp-exporter-base": "0.54.0", + "@opentelemetry/otlp-transformer": "0.54.0", + "@opentelemetry/sdk-logs": "0.54.0" }, "engines": { "node": ">=14" @@ -2949,115 +2786,18 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/resources": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.26.0.tgz", - "integrity": "sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/sdk-logs": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.53.0.tgz", - "integrity": "sha512-dhSisnEgIj/vJZXZV6f6KcTnyLDx/VuQ6l3ejuZpMpPlh9S1qMHiZU9NMmOkVkwwHkMy3G6mEBwdP23vUZVr4g==", - "dependencies": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.4.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/sdk-metrics": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.26.0.tgz", - "integrity": "sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz", - "integrity": "sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, "node_modules/@opentelemetry/exporter-logs-otlp-proto": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.53.0.tgz", - "integrity": "sha512-jhEcVL1deeWNmTUP05UZMriZPSWUBcfg94ng7JuBb1q2NExgnADQFl1VQQ+xo62/JepK+MxQe4xAwlsDQFbISA==", + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.54.0.tgz", + "integrity": "sha512-Q8p1eLP6BGu26VdiR8qBiyufXTZimUl2kv6EwZZPLRU0CJWAFR562UOyUtDxbwQioQFq57DVjCd6mQWBvydAlg==", "dependencies": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-trace-base": "1.26.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.53.0.tgz", - "integrity": "sha512-UCWPreGQEhD6FjBaeDuXhiMf6kkBODF0ZQzrk/tuQcaVDJ+dDQ/xhJp192H9yWnKxVpEjFrSSLnpqmX4VwX+eA==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-transformer": "0.53.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/otlp-transformer": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.53.0.tgz", - "integrity": "sha512-rM0sDA9HD8dluwuBxLetUmoqGJKSAbWenwD65KY9iZhUxdBHRLrIdrABfNDP7aiTjcgK8XFyTn5fhDz7N+W6DA==", - "dependencies": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-metrics": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "protobufjs": "^7.3.0" + "@opentelemetry/api-logs": "0.54.0", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/otlp-exporter-base": "0.54.0", + "@opentelemetry/otlp-transformer": "0.54.0", + "@opentelemetry/resources": "1.27.0", + "@opentelemetry/sdk-logs": "0.54.0", + "@opentelemetry/sdk-trace-base": "1.27.0" }, "engines": { "node": ">=14" @@ -3066,76 +2806,69 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/resources": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.26.0.tgz", - "integrity": "sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/sdk-logs": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.53.0.tgz", - "integrity": "sha512-dhSisnEgIj/vJZXZV6f6KcTnyLDx/VuQ6l3ejuZpMpPlh9S1qMHiZU9NMmOkVkwwHkMy3G6mEBwdP23vUZVr4g==", + "node_modules/@opentelemetry/exporter-prometheus": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.54.0.tgz", + "integrity": "sha512-httb+/c36hZvkIR9SqwXj+fLeE2XDdWfZqGO24MboNMHihmnvjE0/LN29I9CjsJqC2jEi8FErfQha/JeOfsFaA==", "dependencies": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" + "@opentelemetry/core": "1.27.0", + "@opentelemetry/resources": "1.27.0", + "@opentelemetry/sdk-metrics": "1.27.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.4.0 <1.10.0" + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/sdk-metrics": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.26.0.tgz", - "integrity": "sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ==", + "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.54.0.tgz", + "integrity": "sha512-DOoK7yk/L/RHoyuYTxIyGY7PLFSTS7OGNks9htMS7eAFkm4Lsa6EtPlGANCT39NXWP4XIQR1c+Y+YIQ7lJdI+w==", "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.54.0", + "@opentelemetry/otlp-transformer": "0.54.0", + "@opentelemetry/resources": "1.27.0", + "@opentelemetry/sdk-trace-base": "1.27.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz", - "integrity": "sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw==", + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.54.0.tgz", + "integrity": "sha512-00X6rtr6Ew59+MM9pPSH7Ww5ScpWKBLiBA49awbPqQuVL/Bp0qp7O1cTxKHgjWdNkhsELzJxAEYwuRnDGrMXyA==", "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" + "@opentelemetry/core": "1.27.0", + "@opentelemetry/otlp-exporter-base": "0.54.0", + "@opentelemetry/otlp-transformer": "0.54.0", + "@opentelemetry/resources": "1.27.0", + "@opentelemetry/sdk-trace-base": "1.27.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/exporter-prometheus": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.53.0.tgz", - "integrity": "sha512-STP2FZQOykUByPnibbouTirNxnG69Ph8TiMXDsaZuWxGDJ7wsYsRQydJkAVpvG+p0hTMP/hIfZp9zT/1iHpIkQ==", + "node_modules/@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.54.0.tgz", + "integrity": "sha512-cpDQj5wl7G8pLu3lW94SnMpn0C85A9Ehe7+JBow2IL5DGPWXTkynFngMtCC3PpQzQgzlyOVe0MVZfoBB3M5ECA==", "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-metrics": "1.26.0" + "@opentelemetry/core": "1.27.0", + "@opentelemetry/otlp-exporter-base": "0.54.0", + "@opentelemetry/otlp-transformer": "0.54.0", + "@opentelemetry/resources": "1.27.0", + "@opentelemetry/sdk-trace-base": "1.27.0" }, "engines": { "node": ">=14" @@ -3144,34 +2877,21 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/resources": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.26.0.tgz", - "integrity": "sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw==", + "node_modules/@opentelemetry/exporter-zipkin": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.27.0.tgz", + "integrity": "sha512-eGMY3s4QprspFZojqsuQyQpWNFpo+oNVE/aosTbtvAlrJBAlvXcwwsOROOHOd8Y9lkU4i0FpQW482rcXkgwCSw==", "dependencies": { - "@opentelemetry/core": "1.26.0", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/resources": "1.27.0", + "@opentelemetry/sdk-trace-base": "1.27.0", "@opentelemetry/semantic-conventions": "1.27.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/sdk-metrics": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.26.0.tgz", - "integrity": "sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "@opentelemetry/api": "^1.0.0" } }, "node_modules/@opentelemetry/host-metrics": { @@ -3190,11 +2910,11 @@ } }, "node_modules/@opentelemetry/instrumentation": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", - "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.54.0.tgz", + "integrity": "sha512-B0Ydo9g9ehgNHwtpc97XivEzjz0XBKR6iQ83NTENIxEEf5NHE0otZQuZLgDdey1XNk+bP1cfRpIkSFWM5YlSyg==", "dependencies": { - "@opentelemetry/api-logs": "0.53.0", + "@opentelemetry/api-logs": "0.54.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", @@ -3209,12 +2929,12 @@ } }, "node_modules/@opentelemetry/instrumentation-amqplib": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.42.0.tgz", - "integrity": "sha512-fiuU6OKsqHJiydHWgTRQ7MnIrJ2lEqsdgFtNIH4LbAUJl/5XmrIeoDzDnox+hfkgWK65jsleFuQDtYb5hW1koQ==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.43.0.tgz", + "integrity": "sha512-ALjfQC+0dnIEcvNYsbZl/VLh7D2P1HhFF4vicRKHhHFIUV3Shpg4kXgiek5PLhmeKSIPiUB25IYH5RIneclL4A==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3225,13 +2945,12 @@ } }, "node_modules/@opentelemetry/instrumentation-aws-lambda": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.45.0.tgz", - "integrity": "sha512-22ZnmYftKjFoiqC1k3tu2AVKiXSZv+ohuHWk4V4MdJpPuNkadY624aDkv5BmwDeavDxVFgqE9nGgDM9s3Q94mg==", + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.46.0.tgz", + "integrity": "sha512-rNmhTC1e1qQD4jw+TZSHlpLYNhrkbKA0P5rlqPpTVHqZXHQctu9+dity2lLBh4DlFKt4p/ibVDLVDoBqjvetKA==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/propagator-aws-xray": "^1.3.1", - "@opentelemetry/resources": "^1.8.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/aws-lambda": "8.10.143" }, @@ -3243,13 +2962,13 @@ } }, "node_modules/@opentelemetry/instrumentation-aws-sdk": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.44.0.tgz", - "integrity": "sha512-HIWFg4TDQsayceiikOnruMmyQ0SZYW6WiR+wknWwWVLHC3lHTCpAnqzp5V42ckArOdlwHZu2Jvq2GMSM4Myx3w==", + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.45.0.tgz", + "integrity": "sha512-3EGgC0LFZuFfXcOeslhXHhsiInVhhN046YQsYIPflsicAk7v0wN946sZKWuerEfmqx/kFXOsbOeI1SkkTRmqWQ==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/propagation-utils": "^0.30.11", + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/propagation-utils": "^0.30.12", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3260,12 +2979,12 @@ } }, "node_modules/@opentelemetry/instrumentation-bunyan": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.41.0.tgz", - "integrity": "sha512-NoQS+gcwQ7pzb2PZFyra6bAxDAVXBMmpKxBblEuXJWirGrAksQllg9XTdmqhrwT/KxUYrbVca/lMams7e51ysg==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.42.0.tgz", + "integrity": "sha512-GBh6ybwKmFZjc86SyHVx72jHg+4pFPaXT3IZgJ4QtnMsMf0/q5m2aHAjid+yakmEkApsnRWX8pJ8nkl1e+6mag==", "dependencies": { - "@opentelemetry/api-logs": "^0.53.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/api-logs": "^0.54.0", + "@opentelemetry/instrumentation": "^0.54.0", "@types/bunyan": "1.8.9" }, "engines": { @@ -3276,11 +2995,11 @@ } }, "node_modules/@opentelemetry/instrumentation-cassandra-driver": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cassandra-driver/-/instrumentation-cassandra-driver-0.41.0.tgz", - "integrity": "sha512-hvTNcC8qjCQEHZTLAlTmDptjsEGqCKpN+90hHH8Nn/GwilGr5TMSwGrlfstdJuZWyw8HAnRUed6bcjvmHHk2Xw==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cassandra-driver/-/instrumentation-cassandra-driver-0.42.0.tgz", + "integrity": "sha512-35I9Gw4BeSs9NPe7fugu9e/mWKaapc/N1wounHnGt259/Q3ISGMOQRrOwIBw+x/XJygJvn4Ss1c+r5h89TsVAw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3291,12 +3010,12 @@ } }, "node_modules/@opentelemetry/instrumentation-connect": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.39.0.tgz", - "integrity": "sha512-pGBiKevLq7NNglMgqzmeKczF4XQMTOUOTkK8afRHMZMnrK3fcETyTH7lVaSozwiOM3Ws+SuEmXZT7DYrrhxGlg==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.40.0.tgz", + "integrity": "sha512-3aR/3YBQ160siitwwRLjwqrv2KBT16897+bo6yz8wIfel6nWOxTZBJudcbsK3p42pTC7qrbotJ9t/1wRLpv79Q==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/connect": "3.4.36" }, @@ -3308,11 +3027,11 @@ } }, "node_modules/@opentelemetry/instrumentation-cucumber": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.9.0.tgz", - "integrity": "sha512-4PQNFnIqnA2WM3ZHpr0xhZpHSqJ5xJ6ppTIzZC7wPqe+ZBpj41vG8B6ieqiPfq+im4QdqbYnzLb3rj48GDEN9g==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.10.0.tgz", + "integrity": "sha512-5sT6Ap3W7StEL0Oax/vd1YTEcTPTefx+9myzkKrr72hxzFzSooGRCxlU3sfPwZqWptUV7+QWTMd7SqGEEPnE/w==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3323,11 +3042,11 @@ } }, "node_modules/@opentelemetry/instrumentation-dataloader": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.12.0.tgz", - "integrity": "sha512-pnPxatoFE0OXIZDQhL2okF//dmbiWFzcSc8pUg9TqofCLYZySSxDCgQc69CJBo5JnI3Gz1KP+mOjS4WAeRIH4g==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.13.0.tgz", + "integrity": "sha512-wbU3WdgUAXljEIY2nfpkqID/VH70ThnES8mZZHKCZlV/Pl5T4+qmrVdT7U9/WUzz8flwsXfER6T6jl48Wbl+LQ==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.54.0" }, "engines": { "node": ">=14" @@ -3337,12 +3056,11 @@ } }, "node_modules/@opentelemetry/instrumentation-dns": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.39.0.tgz", - "integrity": "sha512-+iPzvXqVdJa67QBuz2tuP0UI3LS1/cMMo6dS7360DDtOQX+sQzkiN+mo3Omn4T6ZRhkTDw6c7uwsHBcmL31+1g==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.40.0.tgz", + "integrity": "sha512-tLNR8XLPiYRKKk3/UqifXnPP2TVt1RcwvHU0R1ETL1xkZ1ZHMTmSC4x6TignnHOFtRixtJ05EgMGejnffaBXkQ==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "semver": "^7.5.4" + "@opentelemetry/instrumentation": "^0.54.0" }, "engines": { "node": ">=14" @@ -3352,12 +3070,12 @@ } }, "node_modules/@opentelemetry/instrumentation-express": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.43.0.tgz", - "integrity": "sha512-bxTIlzn9qPXJgrhz8/Do5Q3jIlqfpoJrSUtVGqH+90eM1v2PkPHc+SdE+zSqe4q9Y1UQJosmZ4N4bm7Zj/++MA==", + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.44.0.tgz", + "integrity": "sha512-GWgibp6Q0wxyFaaU8ERIgMMYgzcHmGrw3ILUtGchLtLncHNOKk0SNoWGqiylXWWT4HTn5XdV8MGawUgpZh80cA==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3368,12 +3086,12 @@ } }, "node_modules/@opentelemetry/instrumentation-fastify": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.40.0.tgz", - "integrity": "sha512-74qj4nG3zPtU7g2x4sm2T4R3/pBMyrYstTsqSZwdlhQk1SD4l8OSY9sPRX1qkhfxOuW3U4KZQAV/Cymb3fB6hg==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.41.0.tgz", + "integrity": "sha512-pNRjFvf0mvqfJueaeL/qEkuGJwgtE5pgjIHGYwjc2rMViNCrtY9/Sf+Nu8ww6dDd/Oyk2fwZZP7i0XZfCnETrA==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3384,12 +3102,12 @@ } }, "node_modules/@opentelemetry/instrumentation-fs": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.15.0.tgz", - "integrity": "sha512-JWVKdNLpu1skqZQA//jKOcKdJC66TWKqa2FUFq70rKohvaSq47pmXlnabNO+B/BvLfmidfiaN35XakT5RyMl2Q==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.16.0.tgz", + "integrity": "sha512-hMDRUxV38ln1R3lNz6osj3YjlO32ykbHqVrzG7gEhGXFQfu7LJUx8t9tEwE4r2h3CD4D0Rw4YGDU4yF4mP3ilg==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.54.0" }, "engines": { "node": ">=14" @@ -3399,11 +3117,11 @@ } }, "node_modules/@opentelemetry/instrumentation-generic-pool": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.39.0.tgz", - "integrity": "sha512-y4v8Y+tSfRB3NNBvHjbjrn7rX/7sdARG7FuK6zR8PGb28CTa0kHpEGCJqvL9L8xkTNvTXo+lM36ajFGUaK1aNw==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.40.0.tgz", + "integrity": "sha512-k+/JlNDHN3bPi/Cir+Ew6tKHFVCa1ZFeQyGUw5HQkRX/twCRaN3kJFXJW+rDAN90XwK3RtC9AWwBihDGh/oSlQ==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.54.0" }, "engines": { "node": ">=14" @@ -3413,11 +3131,11 @@ } }, "node_modules/@opentelemetry/instrumentation-graphql": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.43.0.tgz", - "integrity": "sha512-aI3YMmC2McGd8KW5du1a2gBA0iOMOGLqg4s9YjzwbjFwjlmMNFSK1P3AIg374GWg823RPUGfVTIgZ/juk9CVOA==", + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.44.0.tgz", + "integrity": "sha512-FYXTe3Bv96aNpYktqm86BFUTpjglKD0kWI5T5bxYkLUPEPvFn38vWGMJTGrDMVou/i55E4jlWvcm6hFIqLsMbg==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.54.0" }, "engines": { "node": ">=14" @@ -3427,11 +3145,11 @@ } }, "node_modules/@opentelemetry/instrumentation-grpc": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.53.0.tgz", - "integrity": "sha512-Ss338T92yE1UCgr9zXSY3cPuaAy27uQw+wAC5IwsQKCXL5wwkiOgkd+2Ngksa9EGsgUEMwGeHi76bDdHFJ5Rrw==", + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.54.0.tgz", + "integrity": "sha512-IwLwAf1uC6I5lYjUxfvG0jFuppqNuaBIiaDxYFHMWeRX1Rejh4eqtQi2u+VVtSOHsCn2sRnS9hOxQ2w7+zzPLw==", "dependencies": { - "@opentelemetry/instrumentation": "0.53.0", + "@opentelemetry/instrumentation": "0.54.0", "@opentelemetry/semantic-conventions": "1.27.0" }, "engines": { @@ -3442,12 +3160,12 @@ } }, "node_modules/@opentelemetry/instrumentation-hapi": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.41.0.tgz", - "integrity": "sha512-jKDrxPNXDByPlYcMdZjNPYCvw0SQJjN+B1A+QH+sx+sAHsKSAf9hwFiJSrI6C4XdOls43V/f/fkp9ITkHhKFbQ==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.42.0.tgz", + "integrity": "sha512-TQC0BtIWLHrp6nKsYdZ5t5B7aiZ16BwbRqZtYYQxeJVsq/HQTANWpknjtA7KMxv5tAUMCrU/eDo8F3qioUOSZg==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3458,13 +3176,14 @@ } }, "node_modules/@opentelemetry/instrumentation-http": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.53.0.tgz", - "integrity": "sha512-H74ErMeDuZfj7KgYCTOFGWF5W9AfaPnqLQQxeFq85+D29wwV2yqHbz2IKLYpkOh7EI6QwDEl7rZCIxjJLyc/CQ==", + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.54.0.tgz", + "integrity": "sha512-ovl0UrL+vGpi0O7fdZ1mHRdiQkuv6NGMRBRKZZygVCUFNXdoqTpvJRRbTYih5U5FC+PHIFssEordmlblRCaGUg==", "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/instrumentation": "0.53.0", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/instrumentation": "0.54.0", "@opentelemetry/semantic-conventions": "1.27.0", + "forwarded-parse": "2.1.2", "semver": "^7.5.2" }, "engines": { @@ -3475,11 +3194,11 @@ } }, "node_modules/@opentelemetry/instrumentation-ioredis": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.43.0.tgz", - "integrity": "sha512-i3Dke/LdhZbiUAEImmRG3i7Dimm/BD7t8pDDzwepSvIQ6s2X6FPia7561gw+64w+nx0+G9X14D7rEfaMEmmjig==", + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.44.0.tgz", + "integrity": "sha512-312pE2xc0ihX9haTf9WC4OF9in5EfVO1y5I8Ef9aMQKJNhuSe3IgzQAqGoLfaYajC+ig0IZ9SQKU8mRbFwHU+A==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/redis-common": "^0.36.2", "@opentelemetry/semantic-conventions": "^1.27.0" }, @@ -3491,11 +3210,11 @@ } }, "node_modules/@opentelemetry/instrumentation-kafkajs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.3.0.tgz", - "integrity": "sha512-UnkZueYK1ise8FXQeKlpBd7YYUtC7mM8J0wzUSccEfc/G8UqHQqAzIyYCUOUPUKp8GsjLnWOOK/3hJc4owb7Jg==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.4.0.tgz", + "integrity": "sha512-I9VwDG314g7SDL4t8kD/7+1ytaDBRbZQjhVaQaVIDR8K+mlsoBhLsWH79yHxhHQKvwCSZwqXF+TiTOhoQVUt7A==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3506,11 +3225,11 @@ } }, "node_modules/@opentelemetry/instrumentation-knex": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.40.0.tgz", - "integrity": "sha512-6jka2jfX8+fqjEbCn6hKWHVWe++mHrIkLQtaJqUkBt3ZBs2xn1+y0khxiDS0v/mNb0bIKDJWwtpKFfsQDM1Geg==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.41.0.tgz", + "integrity": "sha512-OhI1SlLv5qnsnm2dOVrian/x3431P75GngSpnR7c4fcVFv7prXGYu29Z6ILRWJf/NJt6fkbySmwdfUUnFnHCTg==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3521,12 +3240,12 @@ } }, "node_modules/@opentelemetry/instrumentation-koa": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.43.0.tgz", - "integrity": "sha512-lDAhSnmoTIN6ELKmLJBplXzT/Jqs5jGZehuG22EdSMaTwgjMpxMDI1YtlKEhiWPWkrz5LUsd0aOO0ZRc9vn3AQ==", + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.44.0.tgz", + "integrity": "sha512-ryPqGIQ4hpMGd85bAGjRMDAy/ic+Qdh1GtFGJo9KaXdzbcvZoF1ZgXVsKTYDxbD1n5C0BoQy6rcWg8Lu68iCJA==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3537,11 +3256,11 @@ } }, "node_modules/@opentelemetry/instrumentation-lru-memoizer": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.40.0.tgz", - "integrity": "sha512-21xRwZsEdMPnROu/QsaOIODmzw59IYpGFmuC4aFWvMj6stA8+Ei1tX67nkarJttlNjoM94um0N4X26AD7ff54A==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.41.0.tgz", + "integrity": "sha512-6OePkk4RYCPVsnS0TroEK6UZzxxxjVWaE6EPdOn2qxGHMtm+Qb80tiBQ6BbmC+f7bjc27O85JY8gxeTybhHZXw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.54.0" }, "engines": { "node": ">=14" @@ -3551,11 +3270,11 @@ } }, "node_modules/@opentelemetry/instrumentation-memcached": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-memcached/-/instrumentation-memcached-0.39.0.tgz", - "integrity": "sha512-WfwvKAZ9I1qILRP5EUd88HQjwAAL+trXpCpozjBi4U6a0A07gB3fZ5PFAxbXemSjF5tHk9KVoROnqHvQ+zzFSQ==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-memcached/-/instrumentation-memcached-0.40.0.tgz", + "integrity": "sha512-VzJUUH6cVz8yrb25RvvjhxCpwu4vUk28I0m5nnnhebULOo8p9lda5PgQeVde2+jQAd977C/vN714fkbYOmwb+A==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/memcached": "^2.2.6" }, @@ -3567,12 +3286,11 @@ } }, "node_modules/@opentelemetry/instrumentation-mongodb": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.47.0.tgz", - "integrity": "sha512-yqyXRx2SulEURjgOQyJzhCECSh5i1uM49NUaq9TqLd6fA7g26OahyJfsr9NE38HFqGRHpi4loyrnfYGdrsoVjQ==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.48.0.tgz", + "integrity": "sha512-9YWvaGvrrcrydMsYGLu0w+RgmosLMKe3kv/UNlsPy8RLnCkN2z+bhhbjjjuxtUmvEuKZMCoXFluABVuBr1yhjw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/sdk-metrics": "^1.9.1", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3583,12 +3301,12 @@ } }, "node_modules/@opentelemetry/instrumentation-mongoose": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.42.0.tgz", - "integrity": "sha512-AnWv+RaR86uG3qNEMwt3plKX1ueRM7AspfszJYVkvkehiicC3bHQA6vWdb6Zvy5HAE14RyFbu9+2hUUjR2NSyg==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.43.0.tgz", + "integrity": "sha512-y1mWuL/zb6IKi199HkROgmStxF/ybEsnKYgx+/lpLATd57oZHOqrXP9tLmp9qRVI5c6P5XEWfe7ZCvrj07iDMQ==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3599,11 +3317,11 @@ } }, "node_modules/@opentelemetry/instrumentation-mysql": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.41.0.tgz", - "integrity": "sha512-jnvrV6BsQWyHS2qb2fkfbfSb1R/lmYwqEZITwufuRl37apTopswu9izc0b1CYRp/34tUG/4k/V39PND6eyiNvw==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.42.0.tgz", + "integrity": "sha512-1GN2EBGVSZABGQ25MSz3faeBW/DwhzmE10aNW1/A2mvQAxF1CvpMk17YmNUzwapVt29iKsiU3SXQG7vjh/019A==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/mysql": "2.15.26" }, @@ -3615,11 +3333,11 @@ } }, "node_modules/@opentelemetry/instrumentation-mysql2": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.41.0.tgz", - "integrity": "sha512-REQB0x+IzVTpoNgVmy5b+UnH1/mDByrneimP6sbDHkp1j8QOl1HyWOrBH/6YWR0nrbU3l825Em5PlybjT3232g==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.42.0.tgz", + "integrity": "sha512-CQqOjCbHwEnaC+Bd6Sms+82iJkSbPpd7jD7Jwif7q8qXo6yrKLVDYDVK+zKbfnmQtu2xHaHj+xiq4tyjb3sMfg==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@opentelemetry/sql-common": "^0.40.1" }, @@ -3631,11 +3349,11 @@ } }, "node_modules/@opentelemetry/instrumentation-nestjs-core": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.40.0.tgz", - "integrity": "sha512-WF1hCUed07vKmf5BzEkL0wSPinqJgH7kGzOjjMAiTGacofNXjb/y4KQ8loj2sNsh5C/NN7s1zxQuCgbWbVTGKg==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.41.0.tgz", + "integrity": "sha512-XCqtghFktpcJ2BOaJtFfqtTMsHffJADxfYhJl28WT6ygCChS2uZVxMKKLsy+i9VtPaw/i1IumPICL6mbhwq+Vw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3646,11 +3364,11 @@ } }, "node_modules/@opentelemetry/instrumentation-net": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-net/-/instrumentation-net-0.39.0.tgz", - "integrity": "sha512-rixHoODfI/Cx1B0mH1BpxCT0bRSxktuBDrt9IvpT2KSEutK5hR0RsRdgdz/GKk+BQ4u+IG6godgMSGwNQCueEA==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-net/-/instrumentation-net-0.40.0.tgz", + "integrity": "sha512-abErnVRxTmtiF7EvBISW81Se2nj/j3Xtpfy//9++dgvDOXwbcD1Xz1via6ZHOm/VamboGhqPlYiO7ABzluPLwg==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3661,12 +3379,12 @@ } }, "node_modules/@opentelemetry/instrumentation-pg": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.46.0.tgz", - "integrity": "sha512-PLbYYC7EIoigh9uzhBrDjyL4yhH9akjV2Mln3ci9+lD7p9HE5nUUgYCgcUasyr4bz99c8xy9ErzKLt38Y7Kodg==", + "version": "0.47.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.47.0.tgz", + "integrity": "sha512-aKu5PCeUv3S8s1wq60JZ2o3DWV2wqvO7WAktjmkx5wXd2+tZRfyDCKFHbP90QuDG1HDzjJ138Ob4d4rJdPETCQ==", "dependencies": { "@opentelemetry/core": "^1.26.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "1.27.0", "@opentelemetry/sql-common": "^0.40.1", "@types/pg": "8.6.1", @@ -3690,13 +3408,13 @@ } }, "node_modules/@opentelemetry/instrumentation-pino": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.42.0.tgz", - "integrity": "sha512-SoX6FzucBfTuFNMZjdurJhcYWq2ve8/LkhmyVLUW31HpIB45RF1JNum0u4MkGisosDmXlK4njomcgUovShI+WA==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.43.0.tgz", + "integrity": "sha512-jlOOgbODWRRNknWXY1VLgmqgG0SO4kLgU3XnejjO/3De4OisroAsMGk+1cRB5AQ6WZ8WLAMkMyTShaOe6j2Asw==", "dependencies": { - "@opentelemetry/api-logs": "^0.53.0", + "@opentelemetry/api-logs": "^0.54.0", "@opentelemetry/core": "^1.25.0", - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.54.0" }, "engines": { "node": ">=14" @@ -3706,11 +3424,11 @@ } }, "node_modules/@opentelemetry/instrumentation-redis": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.42.0.tgz", - "integrity": "sha512-jZBoqve0rEC51q0HuhjtZVq1DtUvJHzEJ3YKGvzGar2MU1J4Yt5+pQAQYh1W4jSoDyKeaI4hyeUdWM5N0c2lqA==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.43.0.tgz", + "integrity": "sha512-dufe08W3sCOjutbTJmV6tg2Y3+7IBe59oQrnIW2RCgjRhsW0Jjaenezt490eawO0MdXjUfFyrIUg8WetKhE4xA==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/redis-common": "^0.36.2", "@opentelemetry/semantic-conventions": "^1.27.0" }, @@ -3722,11 +3440,11 @@ } }, "node_modules/@opentelemetry/instrumentation-redis-4": { - "version": "0.42.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.42.1.tgz", - "integrity": "sha512-xm17LJhDfQzQo4wkM/zFwh6wk3SNN/FBFGkscI9Kj4efrb/o5p8Z3yE6ldBPNdIZ6RAwg2p3DL7fvE3DuUDJWA==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.43.0.tgz", + "integrity": "sha512-6B2+CFRY9xRnkeZrSvlTyY2yB/zAgxjbXS5EwXhE3ZAKR1hWWoUzaTADIKT5xe9/VbDW42U3UoOPCcaCmeAXww==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/redis-common": "^0.36.2", "@opentelemetry/semantic-conventions": "^1.27.0" }, @@ -3738,12 +3456,12 @@ } }, "node_modules/@opentelemetry/instrumentation-restify": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-restify/-/instrumentation-restify-0.41.0.tgz", - "integrity": "sha512-gKEo+X/wVKUBuD2WDDlF7SlDNBHMWjSQoLxFCsGqeKgHR0MGtwMel8uaDGg9LJ83nKqYy+7Vl/cDFxjba6H+/w==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-restify/-/instrumentation-restify-0.42.0.tgz", + "integrity": "sha512-ApDD9HNy6de6xrHmISEfkQHwwX1f1JrBj0ADnlk6tVdJ0j/vNmsZNLwaU2IA2K3mHqbp2YLarLgxAZp6rjcfWg==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3754,11 +3472,11 @@ } }, "node_modules/@opentelemetry/instrumentation-router": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-router/-/instrumentation-router-0.40.0.tgz", - "integrity": "sha512-bRo4RaclGFiKtmv/N1D0MuzO7DuxbeqMkMCbPPng6mDwzpHAMpHz/K/IxJmF+H1Hi/NYXVjCKvHGClageLe9eA==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-router/-/instrumentation-router-0.41.0.tgz", + "integrity": "sha512-IbvzgaoylMqStOOtwucEvSu5CDbfQN+H1ZZ2p6c9Kmvzptqh6G441GFy0FFVVqxOAHNhQm2w6n0Ag8trdBjCfw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3769,11 +3487,11 @@ } }, "node_modules/@opentelemetry/instrumentation-socket.io": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-socket.io/-/instrumentation-socket.io-0.42.0.tgz", - "integrity": "sha512-xB5tdsBzuZyicQTO3hDzJIpHQ7V1BYJ6vWPWgl19gWZDBdjEGc3HOupjkd3BUJyDoDhbMEHGk2nNlkUU99EfkA==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-socket.io/-/instrumentation-socket.io-0.43.0.tgz", + "integrity": "sha512-HAQoIZ6N/ey1L4jF69gmqo7RyeSv5rc4sZZAd1v6SVaB8ZolTEyWEzGlu1NRZZTnqfWNxDkX6J1/omWpDd9k0w==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3784,11 +3502,11 @@ } }, "node_modules/@opentelemetry/instrumentation-tedious": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.14.0.tgz", - "integrity": "sha512-ofq7pPhSqvRDvD2FVx3RIWPj76wj4QubfrbqJtEx0A+fWoaYxJOCIQ92tYJh28elAmjMmgF/XaYuJuBhBv5J3A==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.15.0.tgz", + "integrity": "sha512-Kb7yo8Zsq2TUwBbmwYgTAMPK0VbhoS8ikJ6Bup9KrDtCx2JC01nCb+M0VJWXt7tl0+5jARUbKWh5jRSoImxdCw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/tedious": "^4.0.14" }, @@ -3800,12 +3518,12 @@ } }, "node_modules/@opentelemetry/instrumentation-undici": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.6.0.tgz", - "integrity": "sha512-ABJBhm5OdhGmbh0S/fOTE4N69IZ00CsHC5ijMYfzbw3E5NwLgpQk5xsljaECrJ8wz1SfXbO03FiSuu5AyRAkvQ==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.7.0.tgz", + "integrity": "sha512-1AAqbVt1QOLgnc9DEkHS2R/0FIPI74ud5qgitwP9sVYzRg6e66bPSoAIARCyuANJrWCUrfgI69vLTfRxhBM+3A==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.54.0" }, "engines": { "node": ">=14" @@ -3815,12 +3533,12 @@ } }, "node_modules/@opentelemetry/instrumentation-winston": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.40.0.tgz", - "integrity": "sha512-eMk2tKl86YJ8/yHvtDbyhrE35/R0InhO9zuHTflPx8T0+IvKVUhPV71MsJr32sImftqeOww92QHt4Jd+a5db4g==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.41.0.tgz", + "integrity": "sha512-qtqGDx2Plu71s9xaeXut0YgZFG/y68ENG9vvo/SODeEC+4/APiS/htQ5YNJIxxjOuxYowdFYRqV9Kmef2EUzmw==", "dependencies": { - "@opentelemetry/api-logs": "^0.53.0", - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/api-logs": "^0.54.0", + "@opentelemetry/instrumentation": "^0.54.0" }, "engines": { "node": ">=14" @@ -3829,96 +3547,62 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/propagation-utils": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagation-utils/-/propagation-utils-0.30.11.tgz", - "integrity": "sha512-rY4L/2LWNk5p/22zdunpqVmgz6uN419DsRTw5KFMa6u21tWhXS8devlMy4h8m8nnS20wM7r6yYweCNNKjgLYJw==", - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/propagator-aws-xray": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-aws-xray/-/propagator-aws-xray-1.3.1.tgz", - "integrity": "sha512-6fDMzFlt5r6VWv7MUd0eOpglXPFqykW8CnOuUxJ1VZyLy6mV1bzBlzpsqEmhx1bjvZYvH93vhGkQZqrm95mlrQ==", - "dependencies": { - "@opentelemetry/core": "^1.0.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/redis-common": { - "version": "0.36.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz", - "integrity": "sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/resource-detector-alibaba-cloud": { - "version": "0.29.3", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.29.3.tgz", - "integrity": "sha512-jdnG/cYItxwKGRj2n3YsJn1+j3QsMRUfaH/mQSj2b6yULo7bKO4turvASwxy3GuSDH55VwrK+F8oIbanJk69ng==", + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.54.0.tgz", + "integrity": "sha512-g+H7+QleVF/9lz4zhaR9Dt4VwApjqG5WWupy5CTMpWJfHB/nLxBbX73GBZDgdiNfh08nO3rNa6AS7fK8OhgF5g==", "dependencies": { - "@opentelemetry/core": "^1.26.0", - "@opentelemetry/resources": "^1.10.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/core": "1.27.0", + "@opentelemetry/otlp-transformer": "0.54.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/resource-detector-aws": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-1.6.2.tgz", - "integrity": "sha512-xxT6PVmcBTtCo0rf4Bv/mnDMpVRITVt13bDX8mOqKVb0kr5EwIMabZS5EGJhXjP4nljrOIA7ZlOJgSX0Kehfkw==", + "node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.54.0.tgz", + "integrity": "sha512-Yl2Dw0jlRWisEia9Hv/N8u2JLITCvzA6gAIKEvxpEu6nwHEftD2WhTJMIclkTtfmSW0rLmEEXymwmboG4xDN0Q==", "dependencies": { - "@opentelemetry/core": "^1.0.0", - "@opentelemetry/resources": "^1.10.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/otlp-exporter-base": "0.54.0", + "@opentelemetry/otlp-transformer": "0.54.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/resource-detector-azure": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-azure/-/resource-detector-azure-0.2.11.tgz", - "integrity": "sha512-XepvQfTXWyHAoAziCfXGwYbSZL0LHtFk5iuKKN2VE2vzcoiw5Tepi0Qafuwb7CCtpQRReao4H7E29MFbCmh47g==", + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.54.0.tgz", + "integrity": "sha512-jRexIASQQzdK4AjfNIBfn94itAq4Q8EXR9d3b/OVbhd3kKQKvMr7GkxYDjbeTbY7hHCOLcLfJ3dpYQYGOe8qOQ==", "dependencies": { - "@opentelemetry/core": "^1.25.1", - "@opentelemetry/resources": "^1.10.1", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/api-logs": "0.54.0", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/resources": "1.27.0", + "@opentelemetry/sdk-logs": "0.54.0", + "@opentelemetry/sdk-metrics": "1.27.0", + "@opentelemetry/sdk-trace-base": "1.27.0", + "protobufjs": "^7.3.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/resource-detector-container": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.4.4.tgz", - "integrity": "sha512-ZEN2mq7lIjQWJ8NTt1umtr6oT/Kb89856BOmESLSvgSHbIwOFYs7cSfSRH5bfiVw6dXTQAVbZA/wLgCHKrebJA==", - "dependencies": { - "@opentelemetry/core": "^1.26.0", - "@opentelemetry/resources": "^1.10.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, + "node_modules/@opentelemetry/propagation-utils": { + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagation-utils/-/propagation-utils-0.30.12.tgz", + "integrity": "sha512-bgab3q/4dYUutUpQCEaSDa+mLoQJG3vJKeSiGuhM4iZaSpkz8ov0fs1MGil5PfxCo6Hhw3bB3bFYhUtnsfT/Pg==", "engines": { "node": ">=14" }, @@ -3926,15 +3610,12 @@ "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@opentelemetry/resource-detector-gcp": { - "version": "0.29.12", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.29.12.tgz", - "integrity": "sha512-liFG9XTaFVyY9YdFy4r2qVNa4a+Mg3k+XoWzzq2F+/xR0hFLfL0H4k7CsMW+T4Vl+1Cvc9W9WEVt5VCF4u/SYw==", + "node_modules/@opentelemetry/propagator-aws-xray": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-aws-xray/-/propagator-aws-xray-1.3.1.tgz", + "integrity": "sha512-6fDMzFlt5r6VWv7MUd0eOpglXPFqykW8CnOuUxJ1VZyLy6mV1bzBlzpsqEmhx1bjvZYvH93vhGkQZqrm95mlrQ==", "dependencies": { - "@opentelemetry/core": "^1.0.0", - "@opentelemetry/resources": "^1.10.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "gcp-metadata": "^6.0.0" + "@opentelemetry/core": "^1.0.0" }, "engines": { "node": ">=14" @@ -3943,27 +3624,12 @@ "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@opentelemetry/resources": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.25.1.tgz", - "integrity": "sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==", - "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/semantic-conventions": "1.25.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/core": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.25.1.tgz", - "integrity": "sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==", + "node_modules/@opentelemetry/propagator-b3": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.27.0.tgz", + "integrity": "sha512-pTsko3gnMioe3FeWcwTQR3omo5C35tYsKKwjgTCTVCgd3EOWL9BZrMfgLBmszrwXABDfUrlAEFN/0W0FfQGynQ==", "dependencies": { - "@opentelemetry/semantic-conventions": "1.25.1" + "@opentelemetry/core": "1.27.0" }, "engines": { "node": ">=14" @@ -3972,36 +3638,12 @@ "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", - "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/sdk-metrics": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.25.1.tgz", - "integrity": "sha512-9Mb7q5ioFL4E4dDrc4wC/A3NTHDat44v4I3p2pLPSxRvqUbDIQyMVr9uK+EU69+HWhlET1VaSrRzwdckWqY15Q==", - "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/resources": "1.25.1", - "lodash.merge": "^4.6.2" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/core": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.25.1.tgz", - "integrity": "sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==", + "node_modules/@opentelemetry/propagator-jaeger": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.27.0.tgz", + "integrity": "sha512-EI1bbK0wn0yIuKlc2Qv2LKBRw6LiUWevrjCF80fn/rlaB+7StAi8Y5s8DBqAYNpY7v1q86+NjU18v7hj2ejU3A==", "dependencies": { - "@opentelemetry/semantic-conventions": "1.25.1" + "@opentelemetry/core": "1.27.0" }, "engines": { "node": ">=14" @@ -4010,107 +3652,22 @@ "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", - "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/sdk-node": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.53.0.tgz", - "integrity": "sha512-0hsxfq3BKy05xGktwG8YdGdxV978++x40EAKyKr1CaHZRh8uqVlXnclnl7OMi9xLMJEcXUw7lGhiRlArFcovyg==", - "dependencies": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/exporter-logs-otlp-grpc": "0.53.0", - "@opentelemetry/exporter-logs-otlp-http": "0.53.0", - "@opentelemetry/exporter-logs-otlp-proto": "0.53.0", - "@opentelemetry/exporter-trace-otlp-grpc": "0.53.0", - "@opentelemetry/exporter-trace-otlp-http": "0.53.0", - "@opentelemetry/exporter-trace-otlp-proto": "0.53.0", - "@opentelemetry/exporter-zipkin": "1.26.0", - "@opentelemetry/instrumentation": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-metrics": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "@opentelemetry/sdk-trace-node": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/exporter-trace-otlp-grpc": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.53.0.tgz", - "integrity": "sha512-m6KSh6OBDwfDjpzPVbuJbMgMbkoZfpxYH2r262KckgX9cMYvooWXEKzlJYsNDC6ADr28A1rtRoUVRwNfIN4tUg==", - "dependencies": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/exporter-trace-otlp-http": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.53.0.tgz", - "integrity": "sha512-m7F5ZTq+V9mKGWYpX8EnZ7NjoqAU7VemQ1E2HAG+W/u0wpY1x0OmbxAXfGKFHCspdJk8UKlwPGrpcB8nay3P8A==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/exporter-trace-otlp-proto": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.53.0.tgz", - "integrity": "sha512-T/bdXslwRKj23S96qbvGtaYOdfyew3TjPEKOk5mHjkCmkVl1O9C/YMdejwSsdLdOq2YW30KjR9kVi0YMxZushQ==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0" - }, + "node_modules/@opentelemetry/redis-common": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz", + "integrity": "sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==", "engines": { "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/exporter-zipkin": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.26.0.tgz", - "integrity": "sha512-PW5R34n3SJHO4t0UetyHKiXL6LixIqWN6lWncg3eRXhKuT30x+b7m5sDJS0kEWRfHeS+kG7uCw2vBzmB2lk3Dw==", + "node_modules/@opentelemetry/resource-detector-alibaba-cloud": { + "version": "0.29.4", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.29.4.tgz", + "integrity": "sha512-U3sWPoBXiEE51jJGhRrW19hLvrRbBbZWTp3Yc7IaRVFODNNzmibOolyi2ow1XN68UgRT4BRuwgwbnM5GbG/E5Q==", "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" + "@opentelemetry/core": "^1.26.0", + "@opentelemetry/resources": "^1.10.0", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { "node": ">=14" @@ -4119,13 +3676,14 @@ "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.53.0.tgz", - "integrity": "sha512-UCWPreGQEhD6FjBaeDuXhiMf6kkBODF0ZQzrk/tuQcaVDJ+dDQ/xhJp192H9yWnKxVpEjFrSSLnpqmX4VwX+eA==", + "node_modules/@opentelemetry/resource-detector-aws": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-1.7.0.tgz", + "integrity": "sha512-VxrwUi/9QcVIV+40d/jOKQthfD/E4/ppQ9FsYpDH7qy16cOO5519QOdihCQJYpVNbgDqf6q3hVrCy1f8UuG8YA==", "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-transformer": "0.53.0" + "@opentelemetry/core": "^1.0.0", + "@opentelemetry/resources": "^1.10.0", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { "node": ">=14" @@ -4134,15 +3692,14 @@ "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/otlp-grpc-exporter-base": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.53.0.tgz", - "integrity": "sha512-F7RCN8VN+lzSa4fGjewit8Z5fEUpY/lmMVy5EWn2ZpbAabg3EE3sCLuTNfOiooNGnmvzimUPruoeqeko/5/TzQ==", + "node_modules/@opentelemetry/resource-detector-azure": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-azure/-/resource-detector-azure-0.2.12.tgz", + "integrity": "sha512-iIarQu6MiCjEEp8dOzmBvCSlRITPFTinFB2oNKAjU6xhx8d7eUcjNOKhBGQTvuCriZrxrEvDaEEY9NfrPQ6uYQ==", "dependencies": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0" + "@opentelemetry/core": "^1.25.1", + "@opentelemetry/resources": "^1.10.1", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { "node": ">=14" @@ -4151,46 +3708,46 @@ "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/otlp-transformer": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.53.0.tgz", - "integrity": "sha512-rM0sDA9HD8dluwuBxLetUmoqGJKSAbWenwD65KY9iZhUxdBHRLrIdrABfNDP7aiTjcgK8XFyTn5fhDz7N+W6DA==", + "node_modules/@opentelemetry/resource-detector-container": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.5.0.tgz", + "integrity": "sha512-ozp+ggcbl17xFfL91+DFgP8nmfzthNLxVTDOQUVgQgngVsSaBb5/I1Tnt63ZX2GCMdBJTxUBbFsqFvO0CjfGLg==", "dependencies": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-metrics": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "protobufjs": "^7.3.0" + "@opentelemetry/core": "^1.26.0", + "@opentelemetry/resources": "^1.10.0", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/propagator-b3": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.26.0.tgz", - "integrity": "sha512-vvVkQLQ/lGGyEy9GT8uFnI047pajSOVnZI2poJqVGD3nJ+B9sFGdlHNnQKophE3lHfnIH0pw2ubrCTjZCgIj+Q==", + "node_modules/@opentelemetry/resource-detector-gcp": { + "version": "0.29.13", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.29.13.tgz", + "integrity": "sha512-vdotx+l3Q+89PeyXMgKEGnZ/CwzwMtuMi/ddgD9/5tKZ08DfDGB2Npz9m2oXPHRCjc4Ro6ifMqFlRyzIvgOjhg==", "dependencies": { - "@opentelemetry/core": "1.26.0" + "@opentelemetry/core": "^1.0.0", + "@opentelemetry/resources": "^1.10.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "gcp-metadata": "^6.0.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/propagator-jaeger": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.26.0.tgz", - "integrity": "sha512-DelFGkCdaxA1C/QA0Xilszfr0t4YbGd3DjxiCDPh34lfnFr+VkkrjV9S8ZTJvAzfdKERXhfOxIKBoGPJwoSz7Q==", + "node_modules/@opentelemetry/resources": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.27.0.tgz", + "integrity": "sha512-jOwt2VJ/lUD5BLc+PMNymDrUCpm5PKi1E9oSVYAvz01U/VdndGmrtV3DU1pG4AwlYhJRHbHfOUIlpBeXCPw6QQ==", "dependencies": { - "@opentelemetry/core": "1.26.0" + "@opentelemetry/core": "1.27.0", + "@opentelemetry/semantic-conventions": "1.27.0" }, "engines": { "node": ">=14" @@ -4199,44 +3756,58 @@ "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/resources": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.26.0.tgz", - "integrity": "sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw==", + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.54.0.tgz", + "integrity": "sha512-HeWvOPiWhEw6lWvg+lCIi1WhJnIPbI4/OFZgHq9tKfpwF3LX6/kk3+GR8sGUGAEZfbjPElkkngzvd2s03zbD7Q==", "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" + "@opentelemetry/api-logs": "0.54.0", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/resources": "1.27.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-logs": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.53.0.tgz", - "integrity": "sha512-dhSisnEgIj/vJZXZV6f6KcTnyLDx/VuQ6l3ejuZpMpPlh9S1qMHiZU9NMmOkVkwwHkMy3G6mEBwdP23vUZVr4g==", + "node_modules/@opentelemetry/sdk-metrics": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.27.0.tgz", + "integrity": "sha512-JzWgzlutoXCydhHWIbLg+r76m+m3ncqvkCcsswXAQ4gqKS+LOHKhq+t6fx1zNytvLuaOUBur7EvWxECc4jPQKg==", "dependencies": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" + "@opentelemetry/core": "1.27.0", + "@opentelemetry/resources": "1.27.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.4.0 <1.10.0" + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-metrics": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.26.0.tgz", - "integrity": "sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" + "node_modules/@opentelemetry/sdk-node": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.54.0.tgz", + "integrity": "sha512-F0mdwb4WPFJNypcmkxQnj3sIfh/73zkBgYePXMK8ghsBwYw4+PgM3/85WT6NzNUeOvWtiXacx5CFft2o7rGW3w==", + "dependencies": { + "@opentelemetry/api-logs": "0.54.0", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/exporter-logs-otlp-grpc": "0.54.0", + "@opentelemetry/exporter-logs-otlp-http": "0.54.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.54.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.54.0", + "@opentelemetry/exporter-trace-otlp-http": "0.54.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.54.0", + "@opentelemetry/exporter-zipkin": "1.27.0", + "@opentelemetry/instrumentation": "0.54.0", + "@opentelemetry/resources": "1.27.0", + "@opentelemetry/sdk-logs": "0.54.0", + "@opentelemetry/sdk-metrics": "1.27.0", + "@opentelemetry/sdk-trace-base": "1.27.0", + "@opentelemetry/sdk-trace-node": "1.27.0", + "@opentelemetry/semantic-conventions": "1.27.0" }, "engines": { "node": ">=14" @@ -4245,13 +3816,13 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz", - "integrity": "sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw==", + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.27.0.tgz", + "integrity": "sha512-btz6XTQzwsyJjombpeqCX6LhiMQYpzt2pIYNPnw0IPO/3AhT6yjnf8Mnv3ZC2A4eRYOjqrg+bfaXg9XHDRJDWQ==", "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/resources": "1.27.0", "@opentelemetry/semantic-conventions": "1.27.0" }, "engines": { @@ -4261,16 +3832,16 @@ "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-trace-node": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.26.0.tgz", - "integrity": "sha512-Fj5IVKrj0yeUwlewCRwzOVcr5avTuNnMHWf7GPc1t6WaT78J6CJyF3saZ/0RkZfdeNO8IcBl/bNcWMVZBMRW8Q==", - "dependencies": { - "@opentelemetry/context-async-hooks": "1.26.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/propagator-b3": "1.26.0", - "@opentelemetry/propagator-jaeger": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.27.0.tgz", + "integrity": "sha512-dWZp/dVGdUEfRBjBq2BgNuBlFqHCxyyMc8FsN0NX15X07mxSUO0SZRLyK/fdAVrde8nqFI/FEdMH4rgU9fqJfQ==", + "dependencies": { + "@opentelemetry/context-async-hooks": "1.27.0", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/propagator-b3": "1.27.0", + "@opentelemetry/propagator-jaeger": "1.27.0", + "@opentelemetry/sdk-trace-base": "1.27.0", "semver": "^7.5.2" }, "engines": { @@ -4399,6 +3970,7 @@ "version": "0.0.17", "resolved": "https://registry.npmjs.org/@react-email/button/-/button-0.0.17.tgz", "integrity": "sha512-ioHdsk+BpGS/PqjU6JS7tUrVy9yvbUx92Z+Cem2+MbYp55oEwQ9VHf7u4f5NoM0gdhfKSehBwRdYlHt/frEMcg==", + "license": "MIT", "engines": { "node": ">=18.0.0" }, @@ -4446,6 +4018,7 @@ "version": "0.0.25", "resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.25.tgz", "integrity": "sha512-lnfVVrThEcET5NPoeaXvrz9UxtWpGRcut2a07dLbyKgNbP7vj/cXTI5TuHtanCvhCddFpMDnElNRghDOfPzwUg==", + "license": "MIT", "dependencies": { "@react-email/body": "0.0.10", "@react-email/button": "0.0.17", @@ -4553,6 +4126,7 @@ "version": "0.0.10", "resolved": "https://registry.npmjs.org/@react-email/link/-/link-0.0.10.tgz", "integrity": "sha512-tva3wvAWSR10lMJa9fVA09yRn7pbEki0ZZpHE6GD1jKbFhmzt38VgLO9B797/prqoDZdAr4rVK7LJFcdPx3GwA==", + "license": "MIT", "engines": { "node": ">=18.0.0" }, @@ -4589,6 +4163,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.1.tgz", "integrity": "sha512-W3gTrcmLOVYnG80QuUp22ReIT/xfLsVJ+n7ghSlG2BITB8evNABn1AO2rGQoXuK84zKtDAlxCdm3hRyIpZdGSA==", + "license": "MIT", "dependencies": { "html-to-text": "9.0.5", "js-beautify": "^1.14.11", @@ -4606,6 +4181,7 @@ "version": "0.0.10", "resolved": "https://registry.npmjs.org/@react-email/row/-/row-0.0.10.tgz", "integrity": "sha512-jPyEhG3gsLX+Eb9U+A30fh0gK6hXJwF4ghJ+ZtFQtlKAKqHX+eCpWlqB3Xschd/ARJLod8WAswg0FB+JD9d0/A==", + "license": "MIT", "engines": { "node": ">=18.0.0" }, @@ -4617,6 +4193,7 @@ "version": "0.0.14", "resolved": "https://registry.npmjs.org/@react-email/section/-/section-0.0.14.tgz", "integrity": "sha512-+fYWLb4tPU1A/+GE5J1+SEMA7/wR3V30lQ+OR9t2kAJqNrARDbMx0bLnYnR1QL5TiFRz0pCF05SQUobk6gHEDQ==", + "license": "MIT", "engines": { "node": ">=18.0.0" }, @@ -4628,6 +4205,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/@react-email/tailwind/-/tailwind-0.1.0.tgz", "integrity": "sha512-qysVUEY+M3SKUvu35XDpzn7yokhqFOT3tPU6Mj/pgc62TL5tQFj6msEbBtwoKs2qO3WZvai0DIHdLhaOxBQSow==", + "license": "MIT", "engines": { "node": ">=18.0.0" }, @@ -4898,6 +4476,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", "integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==", + "license": "MIT", "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" @@ -4951,9 +4530,9 @@ "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" }, "node_modules/@swc/core": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.36.tgz", - "integrity": "sha512-bu7ymMX+LCJOSSrKank25Jaq66ymLVA9fOUuy4ck3/6rbXdLw+pIJPnIDKQ9uNcxww8KDxOuJk9Ui9pqR+aGFw==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.42.tgz", + "integrity": "sha512-iQrRk3SKndQZ4ptJv1rzeQSiCYQIhMjiO97QXOlCcCoaazOLKPnLnXzU4Kv0FuBFyYfG2FE94BoR0XI2BN02qw==", "devOptional": true, "hasInstallScript": true, "dependencies": { @@ -4968,16 +4547,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.7.36", - "@swc/core-darwin-x64": "1.7.36", - "@swc/core-linux-arm-gnueabihf": "1.7.36", - "@swc/core-linux-arm64-gnu": "1.7.36", - "@swc/core-linux-arm64-musl": "1.7.36", - "@swc/core-linux-x64-gnu": "1.7.36", - "@swc/core-linux-x64-musl": "1.7.36", - "@swc/core-win32-arm64-msvc": "1.7.36", - "@swc/core-win32-ia32-msvc": "1.7.36", - "@swc/core-win32-x64-msvc": "1.7.36" + "@swc/core-darwin-arm64": "1.7.42", + "@swc/core-darwin-x64": "1.7.42", + "@swc/core-linux-arm-gnueabihf": "1.7.42", + "@swc/core-linux-arm64-gnu": "1.7.42", + "@swc/core-linux-arm64-musl": "1.7.42", + "@swc/core-linux-x64-gnu": "1.7.42", + "@swc/core-linux-x64-musl": "1.7.42", + "@swc/core-win32-arm64-msvc": "1.7.42", + "@swc/core-win32-ia32-msvc": "1.7.42", + "@swc/core-win32-x64-msvc": "1.7.42" }, "peerDependencies": { "@swc/helpers": "*" @@ -4989,9 +4568,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.36.tgz", - "integrity": "sha512-8vDczXzCgv3ceTPhEivlpGprN44YlrCK1nbfU9g2TrhV/Aiqi09W/eM5zLesdoM1Z3mJl492gc/8nlTkpDdusw==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.42.tgz", + "integrity": "sha512-fWhaCs2+8GDRIcjExVDEIfbptVrxDqG8oHkESnXgymmvqTWzWei5SOnPNMS8Q+MYsn/b++Y2bDxkcwmq35Bvxg==", "cpu": [ "arm64" ], @@ -5005,9 +4584,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.7.36.tgz", - "integrity": "sha512-Pa2Gao7+Wf5m3SsK4abKRtd48AtoUnJInvaC3d077swBfgZjbjUbQvcpdc2dOeQtWwo49rFqUZJonMsL0jnPgQ==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.7.42.tgz", + "integrity": "sha512-ZaVHD2bijrlkCyD7NDzLmSK849Jgcx+6DdL4x1dScoz1slJ8GTvLtEu0JOUaaScQwA+cVlhmrmlmi9ssjbRLGQ==", "cpu": [ "x64" ], @@ -5021,9 +4600,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.36.tgz", - "integrity": "sha512-3YsMWd7V+WZEjbfBnLkkz/olcRBa8nyoK0iIOnNARJBMcYaJxjkJSMZpmSojCnIVwvjA1N83CPAbUL+W+fCnHg==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.42.tgz", + "integrity": "sha512-iF0BJj7hVTbY/vmbvyzVTh/0W80+Q4fbOYschdUM3Bsud39TA+lSaPOefOHywkNH58EQ1z3EAxYcJOWNES7GFQ==", "cpu": [ "arm" ], @@ -5037,9 +4616,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.36.tgz", - "integrity": "sha512-lqM3aBB7kJazJYOwHeA5OGNLqXoQPZ/76b3dV+XcjN1GhD0CcXz6mW5PRYVin6OSN1eKrKBKJjtDA1mqADDEvw==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.42.tgz", + "integrity": "sha512-xGu8j+DOLYTLkVmsfZPJbNPW1EkiWgSucT0nOlz77bLxImukt/0+HVm2hOwHSKuArQ8C3cjahAMY3b/s4VH2ww==", "cpu": [ "arm64" ], @@ -5053,9 +4632,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.36.tgz", - "integrity": "sha512-bqei2YDzvUfG0pth5W2xJaj0eG4XWYk0d/NJ75vBX6bkIzK6dC8iuKQ41jOfUWonnrAs7rTDDJW0sTn/evvRdw==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.42.tgz", + "integrity": "sha512-qtW3JNO7i1yHEko59xxz+jY38+tYmB96JGzj6XzygMbYJYZDYbrOpXQvKbMGNG3YeTDan7Fp2jD0dlKf7NgDPA==", "cpu": [ "arm64" ], @@ -5069,9 +4648,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.36.tgz", - "integrity": "sha512-03maXTUyaBjeCxlDltmdzHje1ryQt1C4OWmmNgSSRXjLb+GNnAenwOJMSrcvHP/aNClD2pwsFCnYKDGy+sYE6w==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.42.tgz", + "integrity": "sha512-F9WY1TN+hhhtiEzZjRQziNLt36M5YprMeOBHjsLVNqwgflzleSI7ulgnlQECS8c8zESaXj3ksGduAoJYtPC1cA==", "cpu": [ "x64" ], @@ -5085,9 +4664,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.36.tgz", - "integrity": "sha512-XXysqLkvjtQnXm1zHqLhy00UYPv/gk5OtwR732X+piNisnEbcJBqI8Qp9O7YvLWllRcoP8IMBGDWLGdGLSpViA==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.42.tgz", + "integrity": "sha512-7YMdOaYKLMQ8JGfnmRDwidpLFs/6ka+80zekeM0iCVO48yLrJR36G0QGXzMjKsXI0BPhq+mboZRRENK4JfQnEA==", "cpu": [ "x64" ], @@ -5101,9 +4680,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.36.tgz", - "integrity": "sha512-k7+dmb13a/zPw+E4XYfPmLZFWJgcOcBRKIjYl9nQErtYsgsg3Ji6TBbsvJVETy23lNHyewZ17V5Vq6NzaG0hzg==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.42.tgz", + "integrity": "sha512-C5CYWaIZEyqPl5W/EwcJ/mLBJFHVoUEa/IwWi0b4q2fCXcSCktQGwKXOQ+d67GneiZoiq0HasgcdMmMpGS9YRQ==", "cpu": [ "arm64" ], @@ -5117,9 +4696,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.36.tgz", - "integrity": "sha512-ridD3ay6YM2PEYHZXXFN+edYEv0FOynaqOBP+NSnGNHA35azItIjoIe+KNi4WltGtAjpKCHSpjGCNfna12wdYQ==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.42.tgz", + "integrity": "sha512-3j47seZ5pO62mbrqvPe1iwhe2BXnM5q7iB+n2xgA38PCGYt0mnaJafqmpCXm/uYZOCMqSNynaoOWCMMZm4sqtA==", "cpu": [ "ia32" ], @@ -5133,9 +4712,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.36.tgz", - "integrity": "sha512-j1z2Z1Ln9d0E3dHsPkC1K9XDh0ojhRPwV+GfRTu4D61PE+aYhYLvbJC6xPvL4/204QrStRS7eDu3m+BcDp3rgQ==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.42.tgz", + "integrity": "sha512-FXl9MdeUogZLGDcLr6QIRdDVkpG0dkN4MLM4dwQ5kcAk+XfKPrQibX6M2kcfhsCx+jtBqtK7hRFReRXPWJZGbA==", "cpu": [ "x64" ], @@ -5163,9 +4742,9 @@ } }, "node_modules/@swc/types": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.13.tgz", - "integrity": "sha512-JL7eeCk6zWCbiYQg2xQSdLXQJl8Qoc9rXmG2cEKvHe3CKwMHwHGpfOb8frzNLmbycOo6I51qxnLnn9ESf4I20Q==", + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.14.tgz", + "integrity": "sha512-PbSmTiYCN+GMrvfjrMo9bdY+f2COnwbdnoMw7rqU/PI5jXpKjxOGZ0qqZCImxnT81NkNsKnmEpvu+hRXLBeCJg==", "devOptional": true, "dependencies": { "@swc/counter": "^0.1.3" @@ -5249,9 +4828,9 @@ } }, "node_modules/@types/archiver": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.2.tgz", - "integrity": "sha512-KmROQqbQzKGuaAbmK+ZcytkJ51+YqDa7NmbXjmtC5YBLSyQYo21YaUnQ3HbaPFKL1ooo6RQ6OPYPIDyxfpDDXw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.3.tgz", + "integrity": "sha512-a6wUll6k3zX6qs5KlxIggs1P1JcYJaTCx2gnlr+f0S1yd2DoaEwoIK10HmBaLnZwWneBz+JBm0dwcZu0zECBcQ==", "dev": true, "dependencies": { "@types/readdir-glob": "*" @@ -5324,9 +4903,9 @@ "dev": true }, "node_modules/@types/cors": { - "version": "2.8.14", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.14.tgz", - "integrity": "sha512-RXHUvNWYICtbP6s18PnOCaqToK8y14DnLd75c6HfyKf228dxy7pHNOQkxPtvXKp/hINFMDjbYzsj63nnpPMSRQ==", + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", "dependencies": { "@types/node": "*" } @@ -5395,9 +4974,9 @@ } }, "node_modules/@types/fluent-ffmpeg": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.26.tgz", - "integrity": "sha512-0JVF3wdQG+pN0ImwWD0bNgJiKF2OHg/7CDBHw5UIbRTvlnkgGHK6V5doE54ltvhud4o31/dEiHm23CAlxFiUQg==", + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.27.tgz", + "integrity": "sha512-QiDWjihpUhriISNoBi2hJBRUUmoj/BMTYcfz+F+ZM9hHWBYABFAE6hjP/TbCZC0GWwlpa3FzvHH9RzFeRusZ7A==", "dev": true, "dependencies": { "@types/node": "*" @@ -5437,9 +5016,9 @@ "dev": true }, "node_modules/@types/lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ==", + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", + "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==", "dev": true }, "node_modules/@types/luxon": { @@ -5494,11 +5073,11 @@ } }, "node_modules/@types/node": { - "version": "20.16.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.13.tgz", - "integrity": "sha512-GjQ7im10B0labo8ZGXDGROUl9k0BNyDgzfGpb4g/cl+4yYDWVKcozANF4FGr4/p0O/rAkQClM6Wiwkije++1Tg==", + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.19.8" } }, "node_modules/@types/nodemailer": { @@ -5621,9 +5200,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.3.11", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", - "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==", + "version": "18.3.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", "dev": true, "dependencies": { "@types/prop-types": "*", @@ -5740,16 +5319,16 @@ "integrity": "sha512-c/hzNDBh7eRF+KbCf+OoZxKbnkpaK/cKp9iLQWqB7muXtM+MtL9SUUH8vCFcLn6dH1Qm05jiexK0ofWY7TfOhQ==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.10.0.tgz", - "integrity": "sha512-phuB3hoP7FFKbRXxjl+DRlQDuJqhpOnm5MmtROXyWi3uS/Xg2ZXqiQfcG2BJHiN4QKyzdOJi3NEn/qTnjUlkmQ==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.12.2.tgz", + "integrity": "sha512-gQxbxM8mcxBwaEmWdtLCIGLfixBMHhQjBqR8sVWNTPpcj45WlYL2IObS/DNMLH1DBP0n8qz+aiiLTGfopPEebw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.10.0", - "@typescript-eslint/type-utils": "8.10.0", - "@typescript-eslint/utils": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0", + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/type-utils": "8.12.2", + "@typescript-eslint/utils": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -5773,15 +5352,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.10.0.tgz", - "integrity": "sha512-E24l90SxuJhytWJ0pTQydFT46Nk0Z+bsLKo/L8rtQSL93rQ6byd1V/QbDpHUTdLPOMsBCcYXZweADNCfOCmOAg==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.12.2.tgz", + "integrity": "sha512-MrvlXNfGPLH3Z+r7Tk+Z5moZAc0dzdVjTgUgwsdGweH7lydysQsnSww3nAmsq8blFuRD5VRlAr9YdEFw3e6PBw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.10.0", - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/typescript-estree": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0", + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/typescript-estree": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "debug": "^4.3.4" }, "engines": { @@ -5801,13 +5380,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.10.0.tgz", - "integrity": "sha512-AgCaEjhfql9MDKjMUxWvH7HjLeBqMCBfIaBbzzIcBbQPZE7CPh1m6FF+L75NUMJFMLYhCywJXIDEMa3//1A0dw==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.12.2.tgz", + "integrity": "sha512-gPLpLtrj9aMHOvxJkSbDBmbRuYdtiEbnvO25bCMza3DhMjTQw0u7Y1M+YR5JPbMsXXnSPuCf5hfq0nEkQDL/JQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0" + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5818,13 +5397,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.10.0.tgz", - "integrity": "sha512-PCpUOpyQSpxBn230yIcK+LeCQaXuxrgCm2Zk1S+PTIRJsEfU6nJ0TtwyH8pIwPK/vJoA+7TZtzyAJSGBz+s/dg==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.12.2.tgz", + "integrity": "sha512-bwuU4TAogPI+1q/IJSKuD4shBLc/d2vGcRT588q+jzayQyjVK2X6v/fbR4InY2U2sgf8MEvVCqEWUzYzgBNcGQ==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.10.0", - "@typescript-eslint/utils": "8.10.0", + "@typescript-eslint/typescript-estree": "8.12.2", + "@typescript-eslint/utils": "8.12.2", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -5842,9 +5421,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.10.0.tgz", - "integrity": "sha512-k/E48uzsfJCRRbGLapdZgrX52csmWJ2rcowwPvOZ8lwPUv3xW6CcFeJAXgx4uJm+Ge4+a4tFOkdYvSpxhRhg1w==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.12.2.tgz", + "integrity": "sha512-VwDwMF1SZ7wPBUZwmMdnDJ6sIFk4K4s+ALKLP6aIQsISkPv8jhiw65sAK6SuWODN/ix+m+HgbYDkH+zLjrzvOA==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5855,13 +5434,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.10.0.tgz", - "integrity": "sha512-3OE0nlcOHaMvQ8Xu5gAfME3/tWVDpb/HxtpUZ1WeOAksZ/h/gwrBzCklaGzwZT97/lBbbxJ16dMA98JMEngW4w==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.12.2.tgz", + "integrity": "sha512-mME5MDwGe30Pq9zKPvyduyU86PH7aixwqYR2grTglAdB+AN8xXQ1vFGpYaUSJ5o5P/5znsSBeNcs5g5/2aQwow==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -5907,15 +5486,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.10.0.tgz", - "integrity": "sha512-Oq4uZ7JFr9d1ZunE/QKy5egcDRXT/FrS2z/nlxzPua2VHFtmMvFNDvpq1m/hq0ra+T52aUezfcjGRIB7vNJF9w==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.12.2.tgz", + "integrity": "sha512-UTTuDIX3fkfAz6iSVa5rTuSfWIYZ6ATtEocQ/umkRSyC9O919lbZ8dcH7mysshrCdrAM03skJOEYaBugxN+M6A==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.10.0", - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/typescript-estree": "8.10.0" + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/typescript-estree": "8.12.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5929,12 +5508,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.10.0.tgz", - "integrity": "sha512-k8nekgqwr7FadWk548Lfph6V3r9OVqjzAIVskE7orMZR23cGJjAOVazsZSJW+ElyjfTM4wx/1g88Mi70DDtG9A==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.12.2.tgz", + "integrity": "sha512-PChz8UaKQAVNHghsHcPyx1OMHoFRUEA7rJSK/mDhdq85bk+PLsUHUBqTQTFt18VJZbmxBovM65fezlheQRsSDA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.10.0", + "@typescript-eslint/types": "8.12.2", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -5946,20 +5525,20 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.3.tgz", - "integrity": "sha512-2OJ3c7UPoFSmBZwqD2VEkUw6A/tzPF0LmW0ZZhhB8PFxuc+9IBG/FaSM+RLEenc7ljzFvGN+G0nGQoZnh7sy2A==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.4.tgz", + "integrity": "sha512-FPKQuJfR6VTfcNMcGpqInmtJuVXFSCd9HQltYncfR01AzXhLucMEtQ5SinPdZxsT5x/5BK7I5qFJ5/ApGCmyTQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.6", + "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.11", - "magicast": "^0.3.4", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", "std-env": "^3.7.0", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" @@ -5968,8 +5547,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.3", - "vitest": "2.1.3" + "@vitest/browser": "2.1.4", + "vitest": "2.1.4" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -5987,14 +5566,14 @@ } }, "node_modules/@vitest/expect": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.3.tgz", - "integrity": "sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.4.tgz", + "integrity": "sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==", "dev": true, "dependencies": { - "@vitest/spy": "2.1.3", - "@vitest/utils": "2.1.3", - "chai": "^5.1.1", + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -6002,21 +5581,20 @@ } }, "node_modules/@vitest/mocker": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.3.tgz", - "integrity": "sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.4.tgz", + "integrity": "sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==", "dev": true, "dependencies": { - "@vitest/spy": "2.1.3", + "@vitest/spy": "2.1.4", "estree-walker": "^3.0.3", - "magic-string": "^0.30.11" + "magic-string": "^0.30.12" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/spy": "2.1.3", - "msw": "^2.3.5", + "msw": "^2.4.9", "vite": "^5.0.0" }, "peerDependenciesMeta": { @@ -6029,18 +5607,18 @@ } }, "node_modules/@vitest/mocker/node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.3.tgz", - "integrity": "sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.4.tgz", + "integrity": "sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==", "dev": true, "dependencies": { "tinyrainbow": "^1.2.0" @@ -6050,12 +5628,12 @@ } }, "node_modules/@vitest/runner": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.3.tgz", - "integrity": "sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.4.tgz", + "integrity": "sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==", "dev": true, "dependencies": { - "@vitest/utils": "2.1.3", + "@vitest/utils": "2.1.4", "pathe": "^1.1.2" }, "funding": { @@ -6063,13 +5641,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.3.tgz", - "integrity": "sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.4.tgz", + "integrity": "sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==", "dev": true, "dependencies": { - "@vitest/pretty-format": "2.1.3", - "magic-string": "^0.30.11", + "@vitest/pretty-format": "2.1.4", + "magic-string": "^0.30.12", "pathe": "^1.1.2" }, "funding": { @@ -6077,34 +5655,34 @@ } }, "node_modules/@vitest/snapshot/node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/@vitest/spy": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.3.tgz", - "integrity": "sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.4.tgz", + "integrity": "sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==", "dev": true, "dependencies": { - "tinyspy": "^3.0.0" + "tinyspy": "^3.0.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.3.tgz", - "integrity": "sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.4.tgz", + "integrity": "sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==", "dev": true, "dependencies": { - "@vitest/pretty-format": "2.1.3", - "loupe": "^3.1.1", + "@vitest/pretty-format": "2.1.4", + "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -6948,9 +6526,9 @@ } }, "node_modules/bullmq": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-4.18.0.tgz", - "integrity": "sha512-TJiS8YaO3GpaiSum3EufaXHnZc7Q5+BjJP1+kM8pD/DAB7BzI44RHZh9yEHgFHI0ikwmHzULIA9zUqkF3L53Ag==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-4.18.2.tgz", + "integrity": "sha512-Cx0O98IlGiFw7UBa+zwGz+nH0Pcl1wfTvMVBlsMna3s0219hXroVovh1xPRgomyUcbyciHiugGCkW0RRNZDHYQ==", "dependencies": { "cron-parser": "^4.6.0", "glob": "^8.0.3", @@ -7093,9 +6671,9 @@ ] }, "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, "dependencies": { "assertion-error": "^2.0.1", @@ -7490,6 +7068,7 @@ "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "license": "MIT", "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" @@ -7530,9 +7109,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "engines": { "node": ">= 0.6" } @@ -7549,14 +7128,6 @@ "node": ">= 0.8.0" } }, - "node_modules/cookie-parser/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -7992,6 +7563,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -8010,12 +7582,14 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ] + ], + "license": "BSD-2-Clause" }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" }, @@ -8030,6 +7604,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -8050,14 +7625,6 @@ "url": "https://dotenvx.com" } }, - "node_modules/dotenv-expand": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", - "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", - "engines": { - "node": ">=12" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -8067,6 +7634,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "license": "MIT", "dependencies": { "@one-ini/wasm": "0.1.1", "commander": "^10.0.0", @@ -8084,6 +7652,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -8092,6 +7661,7 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", "engines": { "node": ">=14" } @@ -8100,6 +7670,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -8143,16 +7714,16 @@ } }, "node_modules/engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "~0.4.1", + "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", @@ -8163,9 +7734,9 @@ } }, "node_modules/engine.io-parser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", - "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "engines": { "node": ">=10.0.0" } @@ -8187,6 +7758,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -8291,17 +7863,17 @@ } }, "node_modules/eslint": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz", - "integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz", + "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.6.0", + "@eslint/core": "^0.7.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.12.0", + "@eslint/js": "9.13.0", "@eslint/plugin-kit": "^0.2.0", "@humanfs/node": "^0.16.5", "@humanwhocodes/module-importer": "^1.0.1", @@ -8476,9 +8048,9 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", - "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8628,9 +8200,9 @@ } }, "node_modules/exiftool-vendored": { - "version": "28.6.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-28.6.0.tgz", - "integrity": "sha512-Cx8/8ov1tKEacHhsi7FNYdisIhKq/SeQfprYSpYzwBuJwkPmCV8w7tTIvUJRQX9rvopXhBA4eBf1FPXqTZW5vA==", + "version": "28.7.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-28.7.0.tgz", + "integrity": "sha512-0zoq6kBS1yPjzJs+p0qZDinWEA72PTKoRk5ETYKfmeRcZAkhv83Y3HCpbb/LdgJJywfm8BcIJGezrBHvL7dVnQ==", "dependencies": { "@photostructure/tz-lookup": "^11.0.0", "@types/luxon": "^3.4.2", @@ -8639,28 +8211,37 @@ "luxon": "^3.5.0" }, "optionalDependencies": { - "exiftool-vendored.exe": "12.97.0", - "exiftool-vendored.pl": "12.97.0" + "exiftool-vendored.exe": "12.99.0", + "exiftool-vendored.pl": "12.99.0" } }, "node_modules/exiftool-vendored.exe": { - "version": "12.97.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.97.0.tgz", - "integrity": "sha512-+HxyFigEJOtwRjP7PhEslhZKuVW2V0hvmHPHtbVtNKGfAUGcfc95xNTjASQfKJvc+9ZuvzdEBPkEQmyA/ZYdIw==", + "version": "12.99.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.99.0.tgz", + "integrity": "sha512-ffpJHCzC9OYJqw4JlPNtCwRy02jwhmnSJEF/QqEjpuIWDEnlRBQP/yWRh1Nw21K1R4FB4yG5PlCgEDu09VQz/w==", "optional": true, "os": [ "win32" ] }, "node_modules/exiftool-vendored.pl": { - "version": "12.97.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.97.0.tgz", - "integrity": "sha512-mXe9JEH3csfyPWcC7+H6IpfaokDMMr4S45n7MtiobGPdeeh+kFnf1SQ9cxg4sF403P6IKVeYYPbzgKMlpro9eQ==", + "version": "12.99.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.99.0.tgz", + "integrity": "sha512-qRVEPQxtoerXF+izJ0O7jGAr5o0Uyvnyu7ao5DTKzF+V7Fv3SurE0l43oCeZPFKo/Ld4V7vEylhFCm4IHVZKWA==", "optional": true, "os": [ "!win32" ] }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "4.21.1", "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", @@ -9000,6 +8581,11 @@ "node": ">= 0.6" } }, + "node_modules/forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==" + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -9505,6 +9091,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz", "integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==", + "license": "MIT", "dependencies": { "@selderee/plugin-htmlparser2": "^0.11.0", "deepmerge": "^4.3.1", @@ -9527,6 +9114,7 @@ "url": "https://github.com/sponsors/fb55" } ], + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", @@ -9672,7 +9260,8 @@ "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" }, "node_modules/inquirer": { "version": "8.2.6", @@ -9970,6 +9559,7 @@ "version": "1.15.1", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", + "license": "MIT", "dependencies": { "config-chain": "^1.1.13", "editorconfig": "^1.0.4", @@ -9990,14 +9580,16 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/js-beautify/node_modules/nopt": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", - "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "license": "ISC", "dependencies": { "abbrev": "^2.0.0" }, @@ -10012,6 +9604,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", "engines": { "node": ">=14" } @@ -10155,6 +9748,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz", "integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==", + "license": "MIT", "funding": { "url": "https://ko-fi.com/killymxi" } @@ -10247,7 +9841,8 @@ "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true }, "node_modules/log-symbols": { "version": "4.1.0", @@ -10315,16 +9910,31 @@ } }, "node_modules/magicast": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", - "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, "dependencies": { - "@babel/parser": "^7.24.4", - "@babel/types": "^7.24.0", + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, + "node_modules/magicast/node_modules/@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.26.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -10569,9 +10179,9 @@ "dev": true }, "node_modules/mock-fs": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.4.0.tgz", - "integrity": "sha512-3ROPnEMgBOkusBMYQUW2rnT3wZwsgfOKzJDLvx/TZ7FL1WmWvwSwn3j4aDR5fLDGtgcc1WF0Z1y0di7c9L4FKw==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.4.1.tgz", + "integrity": "sha512-sz/Q8K1gXXXHR+qr0GZg2ysxCRr323kuN10O7CtQjraJsFDJ4SJ+0I5MzALz7aRp9lHk8Cc/YdsT95h9Ka1aFw==", "dev": true, "engines": { "node": ">=12.0.0" @@ -10839,11 +10449,11 @@ } }, "node_modules/next": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.3.tgz", - "integrity": "sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.10.tgz", + "integrity": "sha512-sDDExXnh33cY3RkS9JuFEKaS4HmlWmDKP1VJioucCG6z5KuA008DPsDZOzi8UfqEk3Ii+2NCQSJrfbEWtZZfww==", "dependencies": { - "@next/env": "14.2.3", + "@next/env": "14.2.10", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -10858,15 +10468,15 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.3", - "@next/swc-darwin-x64": "14.2.3", - "@next/swc-linux-arm64-gnu": "14.2.3", - "@next/swc-linux-arm64-musl": "14.2.3", - "@next/swc-linux-x64-gnu": "14.2.3", - "@next/swc-linux-x64-musl": "14.2.3", - "@next/swc-win32-arm64-msvc": "14.2.3", - "@next/swc-win32-ia32-msvc": "14.2.3", - "@next/swc-win32-x64-msvc": "14.2.3" + "@next/swc-darwin-arm64": "14.2.10", + "@next/swc-darwin-x64": "14.2.10", + "@next/swc-linux-arm64-gnu": "14.2.10", + "@next/swc-linux-arm64-musl": "14.2.10", + "@next/swc-linux-x64-gnu": "14.2.10", + "@next/swc-linux-x64-musl": "14.2.10", + "@next/swc-win32-arm64-msvc": "14.2.10", + "@next/swc-win32-ia32-msvc": "14.2.10", + "@next/swc-win32-x64-msvc": "14.2.10" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -10964,9 +10574,9 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/nodemailer": { - "version": "6.9.15", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.15.tgz", - "integrity": "sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==", + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", + "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==", "engines": { "node": ">=6.0.0" } @@ -11290,6 +10900,7 @@ "version": "0.12.1", "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", "integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==", + "license": "MIT", "dependencies": { "leac": "^0.6.0", "peberminta": "^0.9.0" @@ -11412,14 +11023,15 @@ "version": "0.9.0", "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz", "integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==", + "license": "MIT", "funding": { "url": "https://ko-fi.com/killymxi" } }, "node_modules/pg": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.0.tgz", - "integrity": "sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw==", + "version": "8.13.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.1.tgz", + "integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==", "dependencies": { "pg-connection-string": "^2.7.0", "pg-pool": "^3.7.0", @@ -11872,12 +11484,13 @@ "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "license": "ISC" }, "node_modules/protobufjs": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.2.tgz", - "integrity": "sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", "hasInstallScript": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", @@ -12046,24 +11659,24 @@ } }, "node_modules/react-email": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/react-email/-/react-email-3.0.1.tgz", - "integrity": "sha512-G4Bkx2ULIScy/0Z8nnWywHt0W1iTkaYCdh9rWNuQ3eVZ6B3ttTUDE9uUy3VNQ8dtQbmG0cpt8+XmImw7mMBW6Q==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/react-email/-/react-email-3.0.2.tgz", + "integrity": "sha512-R7Doynb6NbnDvHx+9dWxkiWN2eaq9hj4MxRdkS94cVD/WDaIzESSLm62GtAAyLJ65xA2ROJydFlcYsDq4hGi4Q==", "dependencies": { "@babel/core": "7.24.5", "@babel/parser": "7.24.5", "chalk": "4.1.2", - "chokidar": "3.6.0", + "chokidar": "^4.0.1", "commander": "11.1.0", "debounce": "2.0.0", "esbuild": "0.19.11", "glob": "10.3.4", "log-symbols": "4.1.0", "mime-types": "2.1.35", - "next": "14.2.3", + "next": "14.2.10", "normalize-path": "3.0.0", "ora": "5.4.1", - "socket.io": "4.7.5" + "socket.io": "4.8.0" }, "bin": { "email": "dist/cli/index.js" @@ -12425,6 +12038,20 @@ "balanced-match": "^1.0.0" } }, + "node_modules/react-email/node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/react-email/node_modules/commander": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", @@ -12505,10 +12132,23 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/react-email/node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/react-promise-suspense": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/react-promise-suspense/-/react-promise-suspense-0.3.4.tgz", "integrity": "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^2.0.1" } @@ -12516,7 +12156,8 @@ "node_modules/react-promise-suspense/node_modules/fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==" + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", + "license": "MIT" }, "node_modules/read-cache": { "version": "1.0.0", @@ -13156,6 +12797,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz", "integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==", + "license": "MIT", "dependencies": { "parseley": "^0.12.0" }, @@ -13429,15 +13071,15 @@ "integrity": "sha512-YiuPbxpCj4hD9Qs06hGAz/OZhQ0eDuALN0lRWJez0eD/RevzKqGdUx1IOMUnXgpr+sXZLq3g8ERwbAH0bCb8vg==" }, "node_modules/socket.io": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", - "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", + "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.5.2", + "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" }, @@ -13549,9 +13191,9 @@ } }, "node_modules/sql-formatter": { - "version": "15.4.3", - "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.4.3.tgz", - "integrity": "sha512-RnYhnCojj9jlaVr04Vol2E0aUnZuunUq3gArnzwagsyV5mBXeX6r1rRfHdDzyDkO1NcsPiHCs9ik00Kf9AUMfQ==", + "version": "15.4.5", + "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.4.5.tgz", + "integrity": "sha512-dxYn0OzEmB19/9Y+yh8bqD8kJx2S/4pOTM4QLKxQDh7K6lp1Sx9MhmiF9RUJHSVjfV72KihW5R1h6Kecy6O5qA==", "dev": true, "dependencies": { "argparse": "^2.0.1", @@ -14216,9 +13858,9 @@ "dev": true }, "node_modules/tinyexec": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", - "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", "dev": true }, "node_modules/tinypool": { @@ -14259,14 +13901,6 @@ "node": ">=0.6.0" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -14436,9 +14070,9 @@ } }, "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==" }, "node_modules/tweetnacl": { "version": "0.14.5", @@ -14901,9 +14535,10 @@ } }, "node_modules/validator": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", - "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "license": "MIT", "engines": { "node": ">= 0.10" } @@ -14976,13 +14611,13 @@ } }, "node_modules/vite-node": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.3.tgz", - "integrity": "sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.4.tgz", + "integrity": "sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==", "dev": true, "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.6", + "debug": "^4.3.7", "pathe": "^1.1.2", "vite": "^5.0.0" }, @@ -15016,29 +14651,30 @@ } }, "node_modules/vitest": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.3.tgz", - "integrity": "sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==", - "dev": true, - "dependencies": { - "@vitest/expect": "2.1.3", - "@vitest/mocker": "2.1.3", - "@vitest/pretty-format": "^2.1.3", - "@vitest/runner": "2.1.3", - "@vitest/snapshot": "2.1.3", - "@vitest/spy": "2.1.3", - "@vitest/utils": "2.1.3", - "chai": "^5.1.1", - "debug": "^4.3.6", - "magic-string": "^0.30.11", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.4.tgz", + "integrity": "sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==", + "dev": true, + "dependencies": { + "@vitest/expect": "2.1.4", + "@vitest/mocker": "2.1.4", + "@vitest/pretty-format": "^2.1.4", + "@vitest/runner": "2.1.4", + "@vitest/snapshot": "2.1.4", + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.7.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.0", - "tinypool": "^1.0.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.1.3", + "vite-node": "2.1.4", "why-is-node-running": "^2.3.0" }, "bin": { @@ -15053,8 +14689,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.3", - "@vitest/ui": "2.1.3", + "@vitest/browser": "2.1.4", + "@vitest/ui": "2.1.4", "happy-dom": "*", "jsdom": "*" }, @@ -15708,14 +15344,14 @@ } }, "@babel/helper-string-parser": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz", - "integrity": "sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q==" + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==" }, "@babel/helper-validator-identifier": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz", - "integrity": "sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw==" + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==" }, "@babel/helper-validator-option": { "version": "7.24.6", @@ -15845,13 +15481,12 @@ } }, "@babel/types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.6.tgz", - "integrity": "sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "requires": { - "@babel/helper-string-parser": "^7.24.6", - "@babel/helper-validator-identifier": "^7.24.6", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" } }, "@balena/dockerignore": { @@ -16093,9 +15728,9 @@ } }, "@eslint/core": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", - "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", + "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", "dev": true }, "@eslint/eslintrc": { @@ -16142,9 +15777,9 @@ } }, "@eslint/js": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.12.0.tgz", - "integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", + "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", "dev": true }, "@eslint/object-schema": { @@ -16177,9 +15812,9 @@ } }, "@grpc/grpc-js": { - "version": "1.10.10", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.10.tgz", - "integrity": "sha512-HPa/K5NX6ahMoeBv15njAc/sfF4/jmiXLar9UlC2UfHFKZzsCVLc3wbe7+7qua7w9VPh2/L6EBxyAV7/E8Wftg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.2.tgz", + "integrity": "sha512-bgxdZmgTrJZX50OjyVwz3+mNEnCTNkh3cIqGPWVNeW9jX6bn1ZkU80uPd+67/ZpIJIjRQ9qaHCjhavyoWYxumg==", "requires": { "@grpc/proto-loader": "^0.7.13", "@js-sdsl/ordered-map": "^4.4.2" @@ -16597,20 +16232,20 @@ "optional": true }, "@nestjs/bull-shared": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-10.2.1.tgz", - "integrity": "sha512-zvnTvSq6OJ92omcsFUwaUmPbM3PRgWkIusHPB5TE3IFS7nNdM3OwF+kfe56sgKjMtQQMe/56lok0S04OtPMX5Q==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-10.2.2.tgz", + "integrity": "sha512-bMIEILYYovQWfdz6fCSTgqb/zuKyGmNSc7guB56MiZVW84JloUHb8330nNh3VWaamJKGtUzawbEoG2VR3uVeOg==", "requires": { - "tslib": "2.6.3" + "tslib": "2.8.0" } }, "@nestjs/bullmq": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@nestjs/bullmq/-/bullmq-10.2.1.tgz", - "integrity": "sha512-nDR0hDabmtXt5gsb5R786BJsGIJoWh/79sVmRETXf4S45+fvdqG1XkCKAeHF9TO9USodw9m+XBNKysTnkY41gw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@nestjs/bullmq/-/bullmq-10.2.2.tgz", + "integrity": "sha512-1RXhR7+XK6uXaw9uNH5hP9bcW5Vzkpc4lX7t7sUC23N9XH2CMH6uUm0I14T5KkvMKkj0VXj0GY+Ulh3pCtdwbA==", "requires": { - "@nestjs/bull-shared": "^10.2.1", - "tslib": "2.6.3" + "@nestjs/bull-shared": "^10.2.2", + "tslib": "2.8.0" } }, "@nestjs/cli": { @@ -16649,9 +16284,9 @@ } }, "@nestjs/common": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.5.tgz", - "integrity": "sha512-N/yUyuYCBMb0+H6jHhntR7PURzji0usID/DByhOfooyk/aPGscI0aQKwOA6edlJlT92hHUvXYLJ5p3npj7KcjQ==", + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.6.tgz", + "integrity": "sha512-KkezkZvU9poWaNq4L+lNvx+386hpOxPJkfXBBeSMrcqBOx8kVr36TGN2uYkF4Ta4zNu1KbCjmZbc0rhHSg296g==", "requires": { "iterare": "1.2.1", "tslib": "2.7.0", @@ -16665,20 +16300,10 @@ } } }, - "@nestjs/config": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.3.tgz", - "integrity": "sha512-p6yv/CvoBewJ72mBq4NXgOAi2rSQNWx3a+IMJLVKS2uiwFCOQQuiIatGwq6MRjXV3Jr+B41iUO8FIf4xBrZ4/w==", - "requires": { - "dotenv": "16.4.5", - "dotenv-expand": "10.0.0", - "lodash": "4.17.21" - } - }, "@nestjs/core": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.5.tgz", - "integrity": "sha512-wk0KJ+6tuidqAdeemsQ40BCp1BgMsSuSLG577aqXLxXYoa8FQYPrdxoSzd05znYLwJYM55fisZWb3FLF9HT2qw==", + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.6.tgz", + "integrity": "sha512-zXVPxCNRfO6gAy0yvEDjUxE/8gfZICJFpsl2lZAUH31bPb6m+tXuhUq2mVCTEltyMYQ+DYtRe+fEYM2v152N1g==", "requires": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.1.1", @@ -16696,9 +16321,9 @@ } }, "@nestjs/event-emitter": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@nestjs/event-emitter/-/event-emitter-2.0.4.tgz", - "integrity": "sha512-quMiw8yOwoSul0pp3mOonGz8EyXWHSBTqBy8B0TbYYgpnG1Ix2wGUnuTksLWaaBiiOTDhciaZ41Y5fJZsSJE1Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/event-emitter/-/event-emitter-2.1.1.tgz", + "integrity": "sha512-6L6fBOZTyfFlL7Ih/JDdqlCzZeCW0RjCX28wnzGyg/ncv5F/EOeT1dfopQr1loBRQ3LTgu8OWM7n4zLN4xigsg==", "requires": { "eventemitter2": "6.4.9" } @@ -16710,9 +16335,9 @@ "requires": {} }, "@nestjs/platform-express": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.5.tgz", - "integrity": "sha512-a629r8R8KC4skhdieQ0aIWH5vDBUFntWnWKFyDXQrll6/CllSchfWm87mWF39seaW6bXYtQtAEZY66JrngdrGA==", + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.6.tgz", + "integrity": "sha512-HcyCpAKccAasrLSGRTGWv5BKRs0rwTIFOSsk6laNyqfqvgvYcJQAedarnm4jmaemtmSJ0PFI9PmtEZADd2ahCg==", "requires": { "body-parser": "1.20.3", "cors": "2.8.5", @@ -16729,11 +16354,11 @@ } }, "@nestjs/platform-socket.io": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.4.5.tgz", - "integrity": "sha512-dHkHJQArhrpkX6qBdTW2ghuja3i3cCslwy4QHY6d46u+9UyANQlsNK9wt/lZnmXfCMaci8xAJvUpyODa6YtV7g==", + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.4.6.tgz", + "integrity": "sha512-lGv99O7C00wtnGq9M0mcwrOpH2qmuqAXQyvo/d/I7rmaf3OO1Sg8qWDLAnPKHdaumwOL2mnET3kvCJ06MaL6WA==", "requires": { - "socket.io": "4.7.5", + "socket.io": "4.8.0", "tslib": "2.7.0" }, "dependencies": { @@ -16761,22 +16386,22 @@ } }, "@nestjs/schematics": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.2.2.tgz", - "integrity": "sha512-D4pJ46E8llCA7WPr3cV6sfRqDlvnTjQWnF1fLyKYD3Ldl+KPtlLyIcxaqlLTB0YR9ItKNKIZTJzUehRxR7UUsQ==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.2.3.tgz", + "integrity": "sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==", "dev": true, "requires": { - "@angular-devkit/core": "17.3.10", - "@angular-devkit/schematics": "17.3.10", + "@angular-devkit/core": "17.3.11", + "@angular-devkit/schematics": "17.3.11", "comment-json": "4.2.5", "jsonc-parser": "3.3.1", "pluralize": "8.0.0" }, "dependencies": { "@angular-devkit/core": { - "version": "17.3.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.10.tgz", - "integrity": "sha512-czdl54yxU5DOAGy/uUPNjJruoBDTgwi/V+eOgLNybYhgrc+TsY0f7uJ11yEk/pz5sCov7xIiS7RdRv96waS7vg==", + "version": "17.3.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.11.tgz", + "integrity": "sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==", "dev": true, "requires": { "ajv": "8.12.0", @@ -16796,12 +16421,12 @@ } }, "@angular-devkit/schematics": { - "version": "17.3.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.10.tgz", - "integrity": "sha512-FHcNa1ktYRd0SKExCsNJpR75RffsyuPIV8kvBXzXnLHmXMqvl25G2te3yYJ9yYqy9OLy/58HZznZTxWRyUdHOg==", + "version": "17.3.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.11.tgz", + "integrity": "sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==", "dev": true, "requires": { - "@angular-devkit/core": "17.3.10", + "@angular-devkit/core": "17.3.11", "jsonc-parser": "3.2.1", "magic-string": "0.30.8", "ora": "5.4.1", @@ -16844,9 +16469,9 @@ } }, "@nestjs/testing": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.5.tgz", - "integrity": "sha512-3NhmztE+fK3MuuOZhXihvMIhxm0QuDM2BneHvM5A0oVLG+STsAeGBqbDr/Ef2qsvqH5HaqvfGbVJ4N1DQnZE5A==", + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.6.tgz", + "integrity": "sha512-aiDicKhlGibVGNYuew399H5qZZXaseOBT/BS+ERJxxCmco7ZdAqaujsNjSaSbTK9ojDPf27crLT0C4opjqJe3A==", "dev": true, "requires": { "tslib": "2.7.0" @@ -16869,9 +16494,9 @@ } }, "@nestjs/websockets": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.4.5.tgz", - "integrity": "sha512-LbL/HRLWQUBTUPY7swojOHdvokyVGINIiuP/VmRdhob4T751r+9i09z2RqRpP71psuom9mnRHYI1+vT2FABrAw==", + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.4.6.tgz", + "integrity": "sha512-53YqDQylPAOudNFiiBvrN8QrRl/sZ9oEjKbD3wBVgrFREbaiuTySoyyy6HwVs60HW29uQwck+Bp7qkKGjhtQKg==", "requires": { "iterare": "1.2.1", "object-hash": "3.0.0", @@ -16886,62 +16511,62 @@ } }, "@next/env": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.3.tgz", - "integrity": "sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==" + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.10.tgz", + "integrity": "sha512-dZIu93Bf5LUtluBXIv4woQw2cZVZ2DJTjax5/5DOs3lzEOeKLy7GxRSr4caK9/SCPdaW6bCgpye6+n4Dh9oJPw==" }, "@next/swc-darwin-arm64": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.3.tgz", - "integrity": "sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.10.tgz", + "integrity": "sha512-V3z10NV+cvMAfxQUMhKgfQnPbjw+Ew3cnr64b0lr8MDiBJs3eLnM6RpGC46nhfMZsiXgQngCJKWGTC/yDcgrDQ==", "optional": true }, "@next/swc-darwin-x64": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz", - "integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.10.tgz", + "integrity": "sha512-Y0TC+FXbFUQ2MQgimJ/7Ina2mXIKhE7F+GUe1SgnzRmwFY3hX2z8nyVCxE82I2RicspdkZnSWMn4oTjIKz4uzA==", "optional": true }, "@next/swc-linux-arm64-gnu": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz", - "integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.10.tgz", + "integrity": "sha512-ZfQ7yOy5zyskSj9rFpa0Yd7gkrBnJTkYVSya95hX3zeBG9E55Z6OTNPn1j2BTFWvOVVj65C3T+qsjOyVI9DQpA==", "optional": true }, "@next/swc-linux-arm64-musl": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz", - "integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.10.tgz", + "integrity": "sha512-n2i5o3y2jpBfXFRxDREr342BGIQCJbdAUi/K4q6Env3aSx8erM9VuKXHw5KNROK9ejFSPf0LhoSkU/ZiNdacpQ==", "optional": true }, "@next/swc-linux-x64-gnu": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz", - "integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.10.tgz", + "integrity": "sha512-GXvajAWh2woTT0GKEDlkVhFNxhJS/XdDmrVHrPOA83pLzlGPQnixqxD8u3bBB9oATBKB//5e4vpACnx5Vaxdqg==", "optional": true }, "@next/swc-linux-x64-musl": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz", - "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.10.tgz", + "integrity": "sha512-opFFN5B0SnO+HTz4Wq4HaylXGFV+iHrVxd3YvREUX9K+xfc4ePbRrxqOuPOFjtSuiVouwe6uLeDtabjEIbkmDA==", "optional": true }, "@next/swc-win32-arm64-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz", - "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.10.tgz", + "integrity": "sha512-9NUzZuR8WiXTvv+EiU/MXdcQ1XUvFixbLIMNQiVHuzs7ZIFrJDLJDaOF1KaqttoTujpcxljM/RNAOmw1GhPPQQ==", "optional": true }, "@next/swc-win32-ia32-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz", - "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.10.tgz", + "integrity": "sha512-fr3aEbSd1GeW3YUMBkWAu4hcdjZ6g4NBl1uku4gAn661tcxd1bHs1THWYzdsbTRLcCKLjrDZlNp6j2HTfrw+Bg==", "optional": true }, "@next/swc-win32-x64-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz", - "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.10.tgz", + "integrity": "sha512-UjeVoRGKNL2zfbcQ6fscmgjBAS/inHBh63mjIlfPg/NG8Yn2ztqylXt5qilYb6hoHIwaU2ogHknHWWmahJjgZQ==", "optional": true }, "@nodelib/fs.scandir": { @@ -16988,347 +16613,175 @@ "integrity": "sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==" }, "@opentelemetry/api-logs": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", - "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.54.0.tgz", + "integrity": "sha512-9HhEh5GqFrassUndqJsyW7a0PzfyWr2eV2xwzHLIS+wX3125+9HE9FMRAKmJRwxZhgZGwH3HNQQjoMGZqmOeVA==", "requires": { - "@opentelemetry/api": "^1.0.0" + "@opentelemetry/api": "^1.3.0" } }, "@opentelemetry/auto-instrumentations-node": { - "version": "0.51.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.51.0.tgz", - "integrity": "sha512-xsgydgtJiToxvFsDcmLDrHiFfHOmdomqk4KCnr40YZdsfw7KO4RJEU0om2f7pFh6WUI5q8nSQ53QgZ+DAz6TzA==", - "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/instrumentation-amqplib": "^0.42.0", - "@opentelemetry/instrumentation-aws-lambda": "^0.45.0", - "@opentelemetry/instrumentation-aws-sdk": "^0.44.0", - "@opentelemetry/instrumentation-bunyan": "^0.41.0", - "@opentelemetry/instrumentation-cassandra-driver": "^0.41.0", - "@opentelemetry/instrumentation-connect": "^0.39.0", - "@opentelemetry/instrumentation-cucumber": "^0.9.0", - "@opentelemetry/instrumentation-dataloader": "^0.12.0", - "@opentelemetry/instrumentation-dns": "^0.39.0", - "@opentelemetry/instrumentation-express": "^0.43.0", - "@opentelemetry/instrumentation-fastify": "^0.40.0", - "@opentelemetry/instrumentation-fs": "^0.15.0", - "@opentelemetry/instrumentation-generic-pool": "^0.39.0", - "@opentelemetry/instrumentation-graphql": "^0.43.0", - "@opentelemetry/instrumentation-grpc": "^0.53.0", - "@opentelemetry/instrumentation-hapi": "^0.41.0", - "@opentelemetry/instrumentation-http": "^0.53.0", - "@opentelemetry/instrumentation-ioredis": "^0.43.0", - "@opentelemetry/instrumentation-kafkajs": "^0.3.0", - "@opentelemetry/instrumentation-knex": "^0.40.0", - "@opentelemetry/instrumentation-koa": "^0.43.0", - "@opentelemetry/instrumentation-lru-memoizer": "^0.40.0", - "@opentelemetry/instrumentation-memcached": "^0.39.0", - "@opentelemetry/instrumentation-mongodb": "^0.47.0", - "@opentelemetry/instrumentation-mongoose": "^0.42.0", - "@opentelemetry/instrumentation-mysql": "^0.41.0", - "@opentelemetry/instrumentation-mysql2": "^0.41.0", - "@opentelemetry/instrumentation-nestjs-core": "^0.40.0", - "@opentelemetry/instrumentation-net": "^0.39.0", - "@opentelemetry/instrumentation-pg": "^0.46.0", - "@opentelemetry/instrumentation-pino": "^0.42.0", - "@opentelemetry/instrumentation-redis": "^0.42.0", - "@opentelemetry/instrumentation-redis-4": "^0.42.1", - "@opentelemetry/instrumentation-restify": "^0.41.0", - "@opentelemetry/instrumentation-router": "^0.40.0", - "@opentelemetry/instrumentation-socket.io": "^0.42.0", - "@opentelemetry/instrumentation-tedious": "^0.14.0", - "@opentelemetry/instrumentation-undici": "^0.6.0", - "@opentelemetry/instrumentation-winston": "^0.40.0", - "@opentelemetry/resource-detector-alibaba-cloud": "^0.29.3", - "@opentelemetry/resource-detector-aws": "^1.6.2", - "@opentelemetry/resource-detector-azure": "^0.2.11", - "@opentelemetry/resource-detector-container": "^0.4.4", - "@opentelemetry/resource-detector-gcp": "^0.29.12", + "version": "0.52.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.52.0.tgz", + "integrity": "sha512-J9SgX7NOpTvQ7itvlOlHP3lTlsMWtVh5WQSHUSTlg2m3A9HlZBri2DtZ8QgNj8rYWe0EQxQ3TQ3H6vabfun4vw==", + "requires": { + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/instrumentation-amqplib": "^0.43.0", + "@opentelemetry/instrumentation-aws-lambda": "^0.46.0", + "@opentelemetry/instrumentation-aws-sdk": "^0.45.0", + "@opentelemetry/instrumentation-bunyan": "^0.42.0", + "@opentelemetry/instrumentation-cassandra-driver": "^0.42.0", + "@opentelemetry/instrumentation-connect": "^0.40.0", + "@opentelemetry/instrumentation-cucumber": "^0.10.0", + "@opentelemetry/instrumentation-dataloader": "^0.13.0", + "@opentelemetry/instrumentation-dns": "^0.40.0", + "@opentelemetry/instrumentation-express": "^0.44.0", + "@opentelemetry/instrumentation-fastify": "^0.41.0", + "@opentelemetry/instrumentation-fs": "^0.16.0", + "@opentelemetry/instrumentation-generic-pool": "^0.40.0", + "@opentelemetry/instrumentation-graphql": "^0.44.0", + "@opentelemetry/instrumentation-grpc": "^0.54.0", + "@opentelemetry/instrumentation-hapi": "^0.42.0", + "@opentelemetry/instrumentation-http": "^0.54.0", + "@opentelemetry/instrumentation-ioredis": "^0.44.0", + "@opentelemetry/instrumentation-kafkajs": "^0.4.0", + "@opentelemetry/instrumentation-knex": "^0.41.0", + "@opentelemetry/instrumentation-koa": "^0.44.0", + "@opentelemetry/instrumentation-lru-memoizer": "^0.41.0", + "@opentelemetry/instrumentation-memcached": "^0.40.0", + "@opentelemetry/instrumentation-mongodb": "^0.48.0", + "@opentelemetry/instrumentation-mongoose": "^0.43.0", + "@opentelemetry/instrumentation-mysql": "^0.42.0", + "@opentelemetry/instrumentation-mysql2": "^0.42.0", + "@opentelemetry/instrumentation-nestjs-core": "^0.41.0", + "@opentelemetry/instrumentation-net": "^0.40.0", + "@opentelemetry/instrumentation-pg": "^0.47.0", + "@opentelemetry/instrumentation-pino": "^0.43.0", + "@opentelemetry/instrumentation-redis": "^0.43.0", + "@opentelemetry/instrumentation-redis-4": "^0.43.0", + "@opentelemetry/instrumentation-restify": "^0.42.0", + "@opentelemetry/instrumentation-router": "^0.41.0", + "@opentelemetry/instrumentation-socket.io": "^0.43.0", + "@opentelemetry/instrumentation-tedious": "^0.15.0", + "@opentelemetry/instrumentation-undici": "^0.7.0", + "@opentelemetry/instrumentation-winston": "^0.41.0", + "@opentelemetry/resource-detector-alibaba-cloud": "^0.29.4", + "@opentelemetry/resource-detector-aws": "^1.7.0", + "@opentelemetry/resource-detector-azure": "^0.2.12", + "@opentelemetry/resource-detector-container": "^0.5.0", + "@opentelemetry/resource-detector-gcp": "^0.29.13", "@opentelemetry/resources": "^1.24.0", - "@opentelemetry/sdk-node": "^0.53.0" + "@opentelemetry/sdk-node": "^0.54.0" } }, "@opentelemetry/context-async-hooks": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.26.0.tgz", - "integrity": "sha512-HedpXXYzzbaoutw6DFLWLDket2FwLkLpil4hGCZ1xYEIMTcivdfwEOISgdbLEWyG3HW52gTq2V9mOVJrONgiwg==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.27.0.tgz", + "integrity": "sha512-CdZ3qmHCwNhFAzjTgHqrDQ44Qxcpz43cVxZRhOs+Ns/79ug+Mr84Bkb626bkJLkA3+BLimA5YAEVRlJC6pFb7g==", "requires": {} }, "@opentelemetry/core": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.26.0.tgz", - "integrity": "sha512-1iKxXXE8415Cdv0yjG3G6hQnB5eVEsJce3QaawX8SjDn0mAS0ZM8fAbZZJD4ajvhC15cePvosSCut404KrIIvQ==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.27.0.tgz", + "integrity": "sha512-yQPKnK5e+76XuiqUH/gKyS8wv/7qITd5ln56QkBTf3uggr0VkXOXfcaAuG330UfdYu83wsyoBwqwxigpIG+Jkg==", "requires": { "@opentelemetry/semantic-conventions": "1.27.0" } }, "@opentelemetry/exporter-logs-otlp-grpc": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.53.0.tgz", - "integrity": "sha512-x5ygAQgWAQOI+UOhyV3z9eW7QU2dCfnfOuIBiyYmC2AWr74f6x/3JBnP27IAcEx6aihpqBYWKnpoUTztkVPAZw==", + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.54.0.tgz", + "integrity": "sha512-CQC9xl9p8EIvx2KggdM7yffbpmUArKjiqAcjTTTEvqE8kOOf71NSuBU0FXs14FU8vBGTUlsr3oI4vGeWF8FakA==", "requires": { "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/sdk-logs": "0.53.0" - }, - "dependencies": { - "@opentelemetry/otlp-exporter-base": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.53.0.tgz", - "integrity": "sha512-UCWPreGQEhD6FjBaeDuXhiMf6kkBODF0ZQzrk/tuQcaVDJ+dDQ/xhJp192H9yWnKxVpEjFrSSLnpqmX4VwX+eA==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-transformer": "0.53.0" - } - }, - "@opentelemetry/otlp-grpc-exporter-base": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.53.0.tgz", - "integrity": "sha512-F7RCN8VN+lzSa4fGjewit8Z5fEUpY/lmMVy5EWn2ZpbAabg3EE3sCLuTNfOiooNGnmvzimUPruoeqeko/5/TzQ==", - "requires": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0" - } - }, - "@opentelemetry/otlp-transformer": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.53.0.tgz", - "integrity": "sha512-rM0sDA9HD8dluwuBxLetUmoqGJKSAbWenwD65KY9iZhUxdBHRLrIdrABfNDP7aiTjcgK8XFyTn5fhDz7N+W6DA==", - "requires": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-metrics": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "protobufjs": "^7.3.0" - } - }, - "@opentelemetry/resources": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.26.0.tgz", - "integrity": "sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - } - }, - "@opentelemetry/sdk-logs": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.53.0.tgz", - "integrity": "sha512-dhSisnEgIj/vJZXZV6f6KcTnyLDx/VuQ6l3ejuZpMpPlh9S1qMHiZU9NMmOkVkwwHkMy3G6mEBwdP23vUZVr4g==", - "requires": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" - } - }, - "@opentelemetry/sdk-metrics": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.26.0.tgz", - "integrity": "sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" - } - }, - "@opentelemetry/sdk-trace-base": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz", - "integrity": "sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - } - } + "@opentelemetry/core": "1.27.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.54.0", + "@opentelemetry/otlp-transformer": "0.54.0", + "@opentelemetry/sdk-logs": "0.54.0" } }, "@opentelemetry/exporter-logs-otlp-http": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.53.0.tgz", - "integrity": "sha512-cSRKgD/n8rb+Yd+Cif6EnHEL/VZg1o8lEcEwFji1lwene6BdH51Zh3feAD9p2TyVoBKrl6Q9Zm2WltSp2k9gWQ==", + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.54.0.tgz", + "integrity": "sha512-EX/5YPtFw5hugURWSmOtJEGsjphkwTRAiv2yay40ADCLEzajhI/tM3v/7hFCj+rm37sGFMNawpi3mGLvfKGexQ==", "requires": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/sdk-logs": "0.53.0" - }, - "dependencies": { - "@opentelemetry/otlp-exporter-base": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.53.0.tgz", - "integrity": "sha512-UCWPreGQEhD6FjBaeDuXhiMf6kkBODF0ZQzrk/tuQcaVDJ+dDQ/xhJp192H9yWnKxVpEjFrSSLnpqmX4VwX+eA==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-transformer": "0.53.0" - } - }, - "@opentelemetry/otlp-transformer": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.53.0.tgz", - "integrity": "sha512-rM0sDA9HD8dluwuBxLetUmoqGJKSAbWenwD65KY9iZhUxdBHRLrIdrABfNDP7aiTjcgK8XFyTn5fhDz7N+W6DA==", - "requires": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-metrics": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "protobufjs": "^7.3.0" - } - }, - "@opentelemetry/resources": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.26.0.tgz", - "integrity": "sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - } - }, - "@opentelemetry/sdk-logs": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.53.0.tgz", - "integrity": "sha512-dhSisnEgIj/vJZXZV6f6KcTnyLDx/VuQ6l3ejuZpMpPlh9S1qMHiZU9NMmOkVkwwHkMy3G6mEBwdP23vUZVr4g==", - "requires": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" - } - }, - "@opentelemetry/sdk-metrics": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.26.0.tgz", - "integrity": "sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" - } - }, - "@opentelemetry/sdk-trace-base": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz", - "integrity": "sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - } - } + "@opentelemetry/api-logs": "0.54.0", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/otlp-exporter-base": "0.54.0", + "@opentelemetry/otlp-transformer": "0.54.0", + "@opentelemetry/sdk-logs": "0.54.0" } - }, - "@opentelemetry/exporter-logs-otlp-proto": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.53.0.tgz", - "integrity": "sha512-jhEcVL1deeWNmTUP05UZMriZPSWUBcfg94ng7JuBb1q2NExgnADQFl1VQQ+xo62/JepK+MxQe4xAwlsDQFbISA==", - "requires": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-trace-base": "1.26.0" - }, - "dependencies": { - "@opentelemetry/otlp-exporter-base": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.53.0.tgz", - "integrity": "sha512-UCWPreGQEhD6FjBaeDuXhiMf6kkBODF0ZQzrk/tuQcaVDJ+dDQ/xhJp192H9yWnKxVpEjFrSSLnpqmX4VwX+eA==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-transformer": "0.53.0" - } - }, - "@opentelemetry/otlp-transformer": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.53.0.tgz", - "integrity": "sha512-rM0sDA9HD8dluwuBxLetUmoqGJKSAbWenwD65KY9iZhUxdBHRLrIdrABfNDP7aiTjcgK8XFyTn5fhDz7N+W6DA==", - "requires": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-metrics": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "protobufjs": "^7.3.0" - } - }, - "@opentelemetry/resources": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.26.0.tgz", - "integrity": "sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - } - }, - "@opentelemetry/sdk-logs": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.53.0.tgz", - "integrity": "sha512-dhSisnEgIj/vJZXZV6f6KcTnyLDx/VuQ6l3ejuZpMpPlh9S1qMHiZU9NMmOkVkwwHkMy3G6mEBwdP23vUZVr4g==", - "requires": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" - } - }, - "@opentelemetry/sdk-metrics": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.26.0.tgz", - "integrity": "sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" - } - }, - "@opentelemetry/sdk-trace-base": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz", - "integrity": "sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - } - } + }, + "@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.54.0.tgz", + "integrity": "sha512-Q8p1eLP6BGu26VdiR8qBiyufXTZimUl2kv6EwZZPLRU0CJWAFR562UOyUtDxbwQioQFq57DVjCd6mQWBvydAlg==", + "requires": { + "@opentelemetry/api-logs": "0.54.0", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/otlp-exporter-base": "0.54.0", + "@opentelemetry/otlp-transformer": "0.54.0", + "@opentelemetry/resources": "1.27.0", + "@opentelemetry/sdk-logs": "0.54.0", + "@opentelemetry/sdk-trace-base": "1.27.0" } }, "@opentelemetry/exporter-prometheus": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.53.0.tgz", - "integrity": "sha512-STP2FZQOykUByPnibbouTirNxnG69Ph8TiMXDsaZuWxGDJ7wsYsRQydJkAVpvG+p0hTMP/hIfZp9zT/1iHpIkQ==", + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.54.0.tgz", + "integrity": "sha512-httb+/c36hZvkIR9SqwXj+fLeE2XDdWfZqGO24MboNMHihmnvjE0/LN29I9CjsJqC2jEi8FErfQha/JeOfsFaA==", "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-metrics": "1.26.0" - }, - "dependencies": { - "@opentelemetry/resources": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.26.0.tgz", - "integrity": "sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - } - }, - "@opentelemetry/sdk-metrics": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.26.0.tgz", - "integrity": "sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" - } - } + "@opentelemetry/core": "1.27.0", + "@opentelemetry/resources": "1.27.0", + "@opentelemetry/sdk-metrics": "1.27.0" + } + }, + "@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.54.0.tgz", + "integrity": "sha512-DOoK7yk/L/RHoyuYTxIyGY7PLFSTS7OGNks9htMS7eAFkm4Lsa6EtPlGANCT39NXWP4XIQR1c+Y+YIQ7lJdI+w==", + "requires": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.54.0", + "@opentelemetry/otlp-transformer": "0.54.0", + "@opentelemetry/resources": "1.27.0", + "@opentelemetry/sdk-trace-base": "1.27.0" + } + }, + "@opentelemetry/exporter-trace-otlp-http": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.54.0.tgz", + "integrity": "sha512-00X6rtr6Ew59+MM9pPSH7Ww5ScpWKBLiBA49awbPqQuVL/Bp0qp7O1cTxKHgjWdNkhsELzJxAEYwuRnDGrMXyA==", + "requires": { + "@opentelemetry/core": "1.27.0", + "@opentelemetry/otlp-exporter-base": "0.54.0", + "@opentelemetry/otlp-transformer": "0.54.0", + "@opentelemetry/resources": "1.27.0", + "@opentelemetry/sdk-trace-base": "1.27.0" + } + }, + "@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.54.0.tgz", + "integrity": "sha512-cpDQj5wl7G8pLu3lW94SnMpn0C85A9Ehe7+JBow2IL5DGPWXTkynFngMtCC3PpQzQgzlyOVe0MVZfoBB3M5ECA==", + "requires": { + "@opentelemetry/core": "1.27.0", + "@opentelemetry/otlp-exporter-base": "0.54.0", + "@opentelemetry/otlp-transformer": "0.54.0", + "@opentelemetry/resources": "1.27.0", + "@opentelemetry/sdk-trace-base": "1.27.0" + } + }, + "@opentelemetry/exporter-zipkin": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.27.0.tgz", + "integrity": "sha512-eGMY3s4QprspFZojqsuQyQpWNFpo+oNVE/aosTbtvAlrJBAlvXcwwsOROOHOd8Y9lkU4i0FpQW482rcXkgwCSw==", + "requires": { + "@opentelemetry/core": "1.27.0", + "@opentelemetry/resources": "1.27.0", + "@opentelemetry/sdk-trace-base": "1.27.0", + "@opentelemetry/semantic-conventions": "1.27.0" } }, "@opentelemetry/host-metrics": { @@ -17341,11 +16794,11 @@ } }, "@opentelemetry/instrumentation": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", - "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.54.0.tgz", + "integrity": "sha512-B0Ydo9g9ehgNHwtpc97XivEzjz0XBKR6iQ83NTENIxEEf5NHE0otZQuZLgDdey1XNk+bP1cfRpIkSFWM5YlSyg==", "requires": { - "@opentelemetry/api-logs": "0.53.0", + "@opentelemetry/api-logs": "0.54.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", @@ -17354,290 +16807,288 @@ } }, "@opentelemetry/instrumentation-amqplib": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.42.0.tgz", - "integrity": "sha512-fiuU6OKsqHJiydHWgTRQ7MnIrJ2lEqsdgFtNIH4LbAUJl/5XmrIeoDzDnox+hfkgWK65jsleFuQDtYb5hW1koQ==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.43.0.tgz", + "integrity": "sha512-ALjfQC+0dnIEcvNYsbZl/VLh7D2P1HhFF4vicRKHhHFIUV3Shpg4kXgiek5PLhmeKSIPiUB25IYH5RIneclL4A==", "requires": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-aws-lambda": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.45.0.tgz", - "integrity": "sha512-22ZnmYftKjFoiqC1k3tu2AVKiXSZv+ohuHWk4V4MdJpPuNkadY624aDkv5BmwDeavDxVFgqE9nGgDM9s3Q94mg==", + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.46.0.tgz", + "integrity": "sha512-rNmhTC1e1qQD4jw+TZSHlpLYNhrkbKA0P5rlqPpTVHqZXHQctu9+dity2lLBh4DlFKt4p/ibVDLVDoBqjvetKA==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/propagator-aws-xray": "^1.3.1", - "@opentelemetry/resources": "^1.8.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/aws-lambda": "8.10.143" } }, "@opentelemetry/instrumentation-aws-sdk": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.44.0.tgz", - "integrity": "sha512-HIWFg4TDQsayceiikOnruMmyQ0SZYW6WiR+wknWwWVLHC3lHTCpAnqzp5V42ckArOdlwHZu2Jvq2GMSM4Myx3w==", + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.45.0.tgz", + "integrity": "sha512-3EGgC0LFZuFfXcOeslhXHhsiInVhhN046YQsYIPflsicAk7v0wN946sZKWuerEfmqx/kFXOsbOeI1SkkTRmqWQ==", "requires": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/propagation-utils": "^0.30.11", + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/propagation-utils": "^0.30.12", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-bunyan": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.41.0.tgz", - "integrity": "sha512-NoQS+gcwQ7pzb2PZFyra6bAxDAVXBMmpKxBblEuXJWirGrAksQllg9XTdmqhrwT/KxUYrbVca/lMams7e51ysg==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.42.0.tgz", + "integrity": "sha512-GBh6ybwKmFZjc86SyHVx72jHg+4pFPaXT3IZgJ4QtnMsMf0/q5m2aHAjid+yakmEkApsnRWX8pJ8nkl1e+6mag==", "requires": { - "@opentelemetry/api-logs": "^0.53.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/api-logs": "^0.54.0", + "@opentelemetry/instrumentation": "^0.54.0", "@types/bunyan": "1.8.9" } }, "@opentelemetry/instrumentation-cassandra-driver": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cassandra-driver/-/instrumentation-cassandra-driver-0.41.0.tgz", - "integrity": "sha512-hvTNcC8qjCQEHZTLAlTmDptjsEGqCKpN+90hHH8Nn/GwilGr5TMSwGrlfstdJuZWyw8HAnRUed6bcjvmHHk2Xw==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cassandra-driver/-/instrumentation-cassandra-driver-0.42.0.tgz", + "integrity": "sha512-35I9Gw4BeSs9NPe7fugu9e/mWKaapc/N1wounHnGt259/Q3ISGMOQRrOwIBw+x/XJygJvn4Ss1c+r5h89TsVAw==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-connect": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.39.0.tgz", - "integrity": "sha512-pGBiKevLq7NNglMgqzmeKczF4XQMTOUOTkK8afRHMZMnrK3fcETyTH7lVaSozwiOM3Ws+SuEmXZT7DYrrhxGlg==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.40.0.tgz", + "integrity": "sha512-3aR/3YBQ160siitwwRLjwqrv2KBT16897+bo6yz8wIfel6nWOxTZBJudcbsK3p42pTC7qrbotJ9t/1wRLpv79Q==", "requires": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/connect": "3.4.36" } }, "@opentelemetry/instrumentation-cucumber": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.9.0.tgz", - "integrity": "sha512-4PQNFnIqnA2WM3ZHpr0xhZpHSqJ5xJ6ppTIzZC7wPqe+ZBpj41vG8B6ieqiPfq+im4QdqbYnzLb3rj48GDEN9g==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.10.0.tgz", + "integrity": "sha512-5sT6Ap3W7StEL0Oax/vd1YTEcTPTefx+9myzkKrr72hxzFzSooGRCxlU3sfPwZqWptUV7+QWTMd7SqGEEPnE/w==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-dataloader": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.12.0.tgz", - "integrity": "sha512-pnPxatoFE0OXIZDQhL2okF//dmbiWFzcSc8pUg9TqofCLYZySSxDCgQc69CJBo5JnI3Gz1KP+mOjS4WAeRIH4g==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.13.0.tgz", + "integrity": "sha512-wbU3WdgUAXljEIY2nfpkqID/VH70ThnES8mZZHKCZlV/Pl5T4+qmrVdT7U9/WUzz8flwsXfER6T6jl48Wbl+LQ==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.54.0" } }, "@opentelemetry/instrumentation-dns": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.39.0.tgz", - "integrity": "sha512-+iPzvXqVdJa67QBuz2tuP0UI3LS1/cMMo6dS7360DDtOQX+sQzkiN+mo3Omn4T6ZRhkTDw6c7uwsHBcmL31+1g==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.40.0.tgz", + "integrity": "sha512-tLNR8XLPiYRKKk3/UqifXnPP2TVt1RcwvHU0R1ETL1xkZ1ZHMTmSC4x6TignnHOFtRixtJ05EgMGejnffaBXkQ==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "semver": "^7.5.4" + "@opentelemetry/instrumentation": "^0.54.0" } }, "@opentelemetry/instrumentation-express": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.43.0.tgz", - "integrity": "sha512-bxTIlzn9qPXJgrhz8/Do5Q3jIlqfpoJrSUtVGqH+90eM1v2PkPHc+SdE+zSqe4q9Y1UQJosmZ4N4bm7Zj/++MA==", + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.44.0.tgz", + "integrity": "sha512-GWgibp6Q0wxyFaaU8ERIgMMYgzcHmGrw3ILUtGchLtLncHNOKk0SNoWGqiylXWWT4HTn5XdV8MGawUgpZh80cA==", "requires": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-fastify": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.40.0.tgz", - "integrity": "sha512-74qj4nG3zPtU7g2x4sm2T4R3/pBMyrYstTsqSZwdlhQk1SD4l8OSY9sPRX1qkhfxOuW3U4KZQAV/Cymb3fB6hg==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.41.0.tgz", + "integrity": "sha512-pNRjFvf0mvqfJueaeL/qEkuGJwgtE5pgjIHGYwjc2rMViNCrtY9/Sf+Nu8ww6dDd/Oyk2fwZZP7i0XZfCnETrA==", "requires": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-fs": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.15.0.tgz", - "integrity": "sha512-JWVKdNLpu1skqZQA//jKOcKdJC66TWKqa2FUFq70rKohvaSq47pmXlnabNO+B/BvLfmidfiaN35XakT5RyMl2Q==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.16.0.tgz", + "integrity": "sha512-hMDRUxV38ln1R3lNz6osj3YjlO32ykbHqVrzG7gEhGXFQfu7LJUx8t9tEwE4r2h3CD4D0Rw4YGDU4yF4mP3ilg==", "requires": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.54.0" } }, "@opentelemetry/instrumentation-generic-pool": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.39.0.tgz", - "integrity": "sha512-y4v8Y+tSfRB3NNBvHjbjrn7rX/7sdARG7FuK6zR8PGb28CTa0kHpEGCJqvL9L8xkTNvTXo+lM36ajFGUaK1aNw==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.40.0.tgz", + "integrity": "sha512-k+/JlNDHN3bPi/Cir+Ew6tKHFVCa1ZFeQyGUw5HQkRX/twCRaN3kJFXJW+rDAN90XwK3RtC9AWwBihDGh/oSlQ==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.54.0" } }, "@opentelemetry/instrumentation-graphql": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.43.0.tgz", - "integrity": "sha512-aI3YMmC2McGd8KW5du1a2gBA0iOMOGLqg4s9YjzwbjFwjlmMNFSK1P3AIg374GWg823RPUGfVTIgZ/juk9CVOA==", + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.44.0.tgz", + "integrity": "sha512-FYXTe3Bv96aNpYktqm86BFUTpjglKD0kWI5T5bxYkLUPEPvFn38vWGMJTGrDMVou/i55E4jlWvcm6hFIqLsMbg==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.54.0" } }, "@opentelemetry/instrumentation-grpc": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.53.0.tgz", - "integrity": "sha512-Ss338T92yE1UCgr9zXSY3cPuaAy27uQw+wAC5IwsQKCXL5wwkiOgkd+2Ngksa9EGsgUEMwGeHi76bDdHFJ5Rrw==", + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.54.0.tgz", + "integrity": "sha512-IwLwAf1uC6I5lYjUxfvG0jFuppqNuaBIiaDxYFHMWeRX1Rejh4eqtQi2u+VVtSOHsCn2sRnS9hOxQ2w7+zzPLw==", "requires": { - "@opentelemetry/instrumentation": "0.53.0", + "@opentelemetry/instrumentation": "0.54.0", "@opentelemetry/semantic-conventions": "1.27.0" } }, "@opentelemetry/instrumentation-hapi": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.41.0.tgz", - "integrity": "sha512-jKDrxPNXDByPlYcMdZjNPYCvw0SQJjN+B1A+QH+sx+sAHsKSAf9hwFiJSrI6C4XdOls43V/f/fkp9ITkHhKFbQ==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.42.0.tgz", + "integrity": "sha512-TQC0BtIWLHrp6nKsYdZ5t5B7aiZ16BwbRqZtYYQxeJVsq/HQTANWpknjtA7KMxv5tAUMCrU/eDo8F3qioUOSZg==", "requires": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-http": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.53.0.tgz", - "integrity": "sha512-H74ErMeDuZfj7KgYCTOFGWF5W9AfaPnqLQQxeFq85+D29wwV2yqHbz2IKLYpkOh7EI6QwDEl7rZCIxjJLyc/CQ==", + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.54.0.tgz", + "integrity": "sha512-ovl0UrL+vGpi0O7fdZ1mHRdiQkuv6NGMRBRKZZygVCUFNXdoqTpvJRRbTYih5U5FC+PHIFssEordmlblRCaGUg==", "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/instrumentation": "0.53.0", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/instrumentation": "0.54.0", "@opentelemetry/semantic-conventions": "1.27.0", + "forwarded-parse": "2.1.2", "semver": "^7.5.2" } }, "@opentelemetry/instrumentation-ioredis": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.43.0.tgz", - "integrity": "sha512-i3Dke/LdhZbiUAEImmRG3i7Dimm/BD7t8pDDzwepSvIQ6s2X6FPia7561gw+64w+nx0+G9X14D7rEfaMEmmjig==", + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.44.0.tgz", + "integrity": "sha512-312pE2xc0ihX9haTf9WC4OF9in5EfVO1y5I8Ef9aMQKJNhuSe3IgzQAqGoLfaYajC+ig0IZ9SQKU8mRbFwHU+A==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/redis-common": "^0.36.2", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-kafkajs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.3.0.tgz", - "integrity": "sha512-UnkZueYK1ise8FXQeKlpBd7YYUtC7mM8J0wzUSccEfc/G8UqHQqAzIyYCUOUPUKp8GsjLnWOOK/3hJc4owb7Jg==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.4.0.tgz", + "integrity": "sha512-I9VwDG314g7SDL4t8kD/7+1ytaDBRbZQjhVaQaVIDR8K+mlsoBhLsWH79yHxhHQKvwCSZwqXF+TiTOhoQVUt7A==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-knex": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.40.0.tgz", - "integrity": "sha512-6jka2jfX8+fqjEbCn6hKWHVWe++mHrIkLQtaJqUkBt3ZBs2xn1+y0khxiDS0v/mNb0bIKDJWwtpKFfsQDM1Geg==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.41.0.tgz", + "integrity": "sha512-OhI1SlLv5qnsnm2dOVrian/x3431P75GngSpnR7c4fcVFv7prXGYu29Z6ILRWJf/NJt6fkbySmwdfUUnFnHCTg==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-koa": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.43.0.tgz", - "integrity": "sha512-lDAhSnmoTIN6ELKmLJBplXzT/Jqs5jGZehuG22EdSMaTwgjMpxMDI1YtlKEhiWPWkrz5LUsd0aOO0ZRc9vn3AQ==", + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.44.0.tgz", + "integrity": "sha512-ryPqGIQ4hpMGd85bAGjRMDAy/ic+Qdh1GtFGJo9KaXdzbcvZoF1ZgXVsKTYDxbD1n5C0BoQy6rcWg8Lu68iCJA==", "requires": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-lru-memoizer": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.40.0.tgz", - "integrity": "sha512-21xRwZsEdMPnROu/QsaOIODmzw59IYpGFmuC4aFWvMj6stA8+Ei1tX67nkarJttlNjoM94um0N4X26AD7ff54A==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.41.0.tgz", + "integrity": "sha512-6OePkk4RYCPVsnS0TroEK6UZzxxxjVWaE6EPdOn2qxGHMtm+Qb80tiBQ6BbmC+f7bjc27O85JY8gxeTybhHZXw==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.54.0" } }, "@opentelemetry/instrumentation-memcached": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-memcached/-/instrumentation-memcached-0.39.0.tgz", - "integrity": "sha512-WfwvKAZ9I1qILRP5EUd88HQjwAAL+trXpCpozjBi4U6a0A07gB3fZ5PFAxbXemSjF5tHk9KVoROnqHvQ+zzFSQ==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-memcached/-/instrumentation-memcached-0.40.0.tgz", + "integrity": "sha512-VzJUUH6cVz8yrb25RvvjhxCpwu4vUk28I0m5nnnhebULOo8p9lda5PgQeVde2+jQAd977C/vN714fkbYOmwb+A==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/memcached": "^2.2.6" } }, "@opentelemetry/instrumentation-mongodb": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.47.0.tgz", - "integrity": "sha512-yqyXRx2SulEURjgOQyJzhCECSh5i1uM49NUaq9TqLd6fA7g26OahyJfsr9NE38HFqGRHpi4loyrnfYGdrsoVjQ==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.48.0.tgz", + "integrity": "sha512-9YWvaGvrrcrydMsYGLu0w+RgmosLMKe3kv/UNlsPy8RLnCkN2z+bhhbjjjuxtUmvEuKZMCoXFluABVuBr1yhjw==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/sdk-metrics": "^1.9.1", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-mongoose": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.42.0.tgz", - "integrity": "sha512-AnWv+RaR86uG3qNEMwt3plKX1ueRM7AspfszJYVkvkehiicC3bHQA6vWdb6Zvy5HAE14RyFbu9+2hUUjR2NSyg==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.43.0.tgz", + "integrity": "sha512-y1mWuL/zb6IKi199HkROgmStxF/ybEsnKYgx+/lpLATd57oZHOqrXP9tLmp9qRVI5c6P5XEWfe7ZCvrj07iDMQ==", "requires": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-mysql": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.41.0.tgz", - "integrity": "sha512-jnvrV6BsQWyHS2qb2fkfbfSb1R/lmYwqEZITwufuRl37apTopswu9izc0b1CYRp/34tUG/4k/V39PND6eyiNvw==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.42.0.tgz", + "integrity": "sha512-1GN2EBGVSZABGQ25MSz3faeBW/DwhzmE10aNW1/A2mvQAxF1CvpMk17YmNUzwapVt29iKsiU3SXQG7vjh/019A==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/mysql": "2.15.26" } }, "@opentelemetry/instrumentation-mysql2": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.41.0.tgz", - "integrity": "sha512-REQB0x+IzVTpoNgVmy5b+UnH1/mDByrneimP6sbDHkp1j8QOl1HyWOrBH/6YWR0nrbU3l825Em5PlybjT3232g==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.42.0.tgz", + "integrity": "sha512-CQqOjCbHwEnaC+Bd6Sms+82iJkSbPpd7jD7Jwif7q8qXo6yrKLVDYDVK+zKbfnmQtu2xHaHj+xiq4tyjb3sMfg==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@opentelemetry/sql-common": "^0.40.1" } }, "@opentelemetry/instrumentation-nestjs-core": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.40.0.tgz", - "integrity": "sha512-WF1hCUed07vKmf5BzEkL0wSPinqJgH7kGzOjjMAiTGacofNXjb/y4KQ8loj2sNsh5C/NN7s1zxQuCgbWbVTGKg==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.41.0.tgz", + "integrity": "sha512-XCqtghFktpcJ2BOaJtFfqtTMsHffJADxfYhJl28WT6ygCChS2uZVxMKKLsy+i9VtPaw/i1IumPICL6mbhwq+Vw==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-net": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-net/-/instrumentation-net-0.39.0.tgz", - "integrity": "sha512-rixHoODfI/Cx1B0mH1BpxCT0bRSxktuBDrt9IvpT2KSEutK5hR0RsRdgdz/GKk+BQ4u+IG6godgMSGwNQCueEA==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-net/-/instrumentation-net-0.40.0.tgz", + "integrity": "sha512-abErnVRxTmtiF7EvBISW81Se2nj/j3Xtpfy//9++dgvDOXwbcD1Xz1via6ZHOm/VamboGhqPlYiO7ABzluPLwg==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-pg": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.46.0.tgz", - "integrity": "sha512-PLbYYC7EIoigh9uzhBrDjyL4yhH9akjV2Mln3ci9+lD7p9HE5nUUgYCgcUasyr4bz99c8xy9ErzKLt38Y7Kodg==", + "version": "0.47.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.47.0.tgz", + "integrity": "sha512-aKu5PCeUv3S8s1wq60JZ2o3DWV2wqvO7WAktjmkx5wXd2+tZRfyDCKFHbP90QuDG1HDzjJ138Ob4d4rJdPETCQ==", "requires": { "@opentelemetry/core": "^1.26.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "1.27.0", "@opentelemetry/sql-common": "^0.40.1", "@types/pg": "8.6.1", @@ -17657,95 +17108,129 @@ } }, "@opentelemetry/instrumentation-pino": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.42.0.tgz", - "integrity": "sha512-SoX6FzucBfTuFNMZjdurJhcYWq2ve8/LkhmyVLUW31HpIB45RF1JNum0u4MkGisosDmXlK4njomcgUovShI+WA==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.43.0.tgz", + "integrity": "sha512-jlOOgbODWRRNknWXY1VLgmqgG0SO4kLgU3XnejjO/3De4OisroAsMGk+1cRB5AQ6WZ8WLAMkMyTShaOe6j2Asw==", "requires": { - "@opentelemetry/api-logs": "^0.53.0", + "@opentelemetry/api-logs": "^0.54.0", "@opentelemetry/core": "^1.25.0", - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.54.0" } }, "@opentelemetry/instrumentation-redis": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.42.0.tgz", - "integrity": "sha512-jZBoqve0rEC51q0HuhjtZVq1DtUvJHzEJ3YKGvzGar2MU1J4Yt5+pQAQYh1W4jSoDyKeaI4hyeUdWM5N0c2lqA==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.43.0.tgz", + "integrity": "sha512-dufe08W3sCOjutbTJmV6tg2Y3+7IBe59oQrnIW2RCgjRhsW0Jjaenezt490eawO0MdXjUfFyrIUg8WetKhE4xA==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/redis-common": "^0.36.2", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-redis-4": { - "version": "0.42.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.42.1.tgz", - "integrity": "sha512-xm17LJhDfQzQo4wkM/zFwh6wk3SNN/FBFGkscI9Kj4efrb/o5p8Z3yE6ldBPNdIZ6RAwg2p3DL7fvE3DuUDJWA==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.43.0.tgz", + "integrity": "sha512-6B2+CFRY9xRnkeZrSvlTyY2yB/zAgxjbXS5EwXhE3ZAKR1hWWoUzaTADIKT5xe9/VbDW42U3UoOPCcaCmeAXww==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/redis-common": "^0.36.2", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-restify": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-restify/-/instrumentation-restify-0.41.0.tgz", - "integrity": "sha512-gKEo+X/wVKUBuD2WDDlF7SlDNBHMWjSQoLxFCsGqeKgHR0MGtwMel8uaDGg9LJ83nKqYy+7Vl/cDFxjba6H+/w==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-restify/-/instrumentation-restify-0.42.0.tgz", + "integrity": "sha512-ApDD9HNy6de6xrHmISEfkQHwwX1f1JrBj0ADnlk6tVdJ0j/vNmsZNLwaU2IA2K3mHqbp2YLarLgxAZp6rjcfWg==", "requires": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-router": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-router/-/instrumentation-router-0.40.0.tgz", - "integrity": "sha512-bRo4RaclGFiKtmv/N1D0MuzO7DuxbeqMkMCbPPng6mDwzpHAMpHz/K/IxJmF+H1Hi/NYXVjCKvHGClageLe9eA==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-router/-/instrumentation-router-0.41.0.tgz", + "integrity": "sha512-IbvzgaoylMqStOOtwucEvSu5CDbfQN+H1ZZ2p6c9Kmvzptqh6G441GFy0FFVVqxOAHNhQm2w6n0Ag8trdBjCfw==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-socket.io": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-socket.io/-/instrumentation-socket.io-0.42.0.tgz", - "integrity": "sha512-xB5tdsBzuZyicQTO3hDzJIpHQ7V1BYJ6vWPWgl19gWZDBdjEGc3HOupjkd3BUJyDoDhbMEHGk2nNlkUU99EfkA==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-socket.io/-/instrumentation-socket.io-0.43.0.tgz", + "integrity": "sha512-HAQoIZ6N/ey1L4jF69gmqo7RyeSv5rc4sZZAd1v6SVaB8ZolTEyWEzGlu1NRZZTnqfWNxDkX6J1/omWpDd9k0w==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0" } }, "@opentelemetry/instrumentation-tedious": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.14.0.tgz", - "integrity": "sha512-ofq7pPhSqvRDvD2FVx3RIWPj76wj4QubfrbqJtEx0A+fWoaYxJOCIQ92tYJh28elAmjMmgF/XaYuJuBhBv5J3A==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.15.0.tgz", + "integrity": "sha512-Kb7yo8Zsq2TUwBbmwYgTAMPK0VbhoS8ikJ6Bup9KrDtCx2JC01nCb+M0VJWXt7tl0+5jARUbKWh5jRSoImxdCw==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/tedious": "^4.0.14" } }, "@opentelemetry/instrumentation-undici": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.6.0.tgz", - "integrity": "sha512-ABJBhm5OdhGmbh0S/fOTE4N69IZ00CsHC5ijMYfzbw3E5NwLgpQk5xsljaECrJ8wz1SfXbO03FiSuu5AyRAkvQ==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.7.0.tgz", + "integrity": "sha512-1AAqbVt1QOLgnc9DEkHS2R/0FIPI74ud5qgitwP9sVYzRg6e66bPSoAIARCyuANJrWCUrfgI69vLTfRxhBM+3A==", "requires": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.54.0" } }, "@opentelemetry/instrumentation-winston": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.40.0.tgz", - "integrity": "sha512-eMk2tKl86YJ8/yHvtDbyhrE35/R0InhO9zuHTflPx8T0+IvKVUhPV71MsJr32sImftqeOww92QHt4Jd+a5db4g==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.41.0.tgz", + "integrity": "sha512-qtqGDx2Plu71s9xaeXut0YgZFG/y68ENG9vvo/SODeEC+4/APiS/htQ5YNJIxxjOuxYowdFYRqV9Kmef2EUzmw==", + "requires": { + "@opentelemetry/api-logs": "^0.54.0", + "@opentelemetry/instrumentation": "^0.54.0" + } + }, + "@opentelemetry/otlp-exporter-base": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.54.0.tgz", + "integrity": "sha512-g+H7+QleVF/9lz4zhaR9Dt4VwApjqG5WWupy5CTMpWJfHB/nLxBbX73GBZDgdiNfh08nO3rNa6AS7fK8OhgF5g==", + "requires": { + "@opentelemetry/core": "1.27.0", + "@opentelemetry/otlp-transformer": "0.54.0" + } + }, + "@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.54.0.tgz", + "integrity": "sha512-Yl2Dw0jlRWisEia9Hv/N8u2JLITCvzA6gAIKEvxpEu6nwHEftD2WhTJMIclkTtfmSW0rLmEEXymwmboG4xDN0Q==", "requires": { - "@opentelemetry/api-logs": "^0.53.0", - "@opentelemetry/instrumentation": "^0.53.0" + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/otlp-exporter-base": "0.54.0", + "@opentelemetry/otlp-transformer": "0.54.0" + } + }, + "@opentelemetry/otlp-transformer": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.54.0.tgz", + "integrity": "sha512-jRexIASQQzdK4AjfNIBfn94itAq4Q8EXR9d3b/OVbhd3kKQKvMr7GkxYDjbeTbY7hHCOLcLfJ3dpYQYGOe8qOQ==", + "requires": { + "@opentelemetry/api-logs": "0.54.0", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/resources": "1.27.0", + "@opentelemetry/sdk-logs": "0.54.0", + "@opentelemetry/sdk-metrics": "1.27.0", + "@opentelemetry/sdk-trace-base": "1.27.0", + "protobufjs": "^7.3.0" } }, "@opentelemetry/propagation-utils": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagation-utils/-/propagation-utils-0.30.11.tgz", - "integrity": "sha512-rY4L/2LWNk5p/22zdunpqVmgz6uN419DsRTw5KFMa6u21tWhXS8devlMy4h8m8nnS20wM7r6yYweCNNKjgLYJw==", + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagation-utils/-/propagation-utils-0.30.12.tgz", + "integrity": "sha512-bgab3q/4dYUutUpQCEaSDa+mLoQJG3vJKeSiGuhM4iZaSpkz8ov0fs1MGil5PfxCo6Hhw3bB3bFYhUtnsfT/Pg==", "requires": {} }, "@opentelemetry/propagator-aws-xray": { @@ -17756,15 +17241,31 @@ "@opentelemetry/core": "^1.0.0" } }, + "@opentelemetry/propagator-b3": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.27.0.tgz", + "integrity": "sha512-pTsko3gnMioe3FeWcwTQR3omo5C35tYsKKwjgTCTVCgd3EOWL9BZrMfgLBmszrwXABDfUrlAEFN/0W0FfQGynQ==", + "requires": { + "@opentelemetry/core": "1.27.0" + } + }, + "@opentelemetry/propagator-jaeger": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.27.0.tgz", + "integrity": "sha512-EI1bbK0wn0yIuKlc2Qv2LKBRw6LiUWevrjCF80fn/rlaB+7StAi8Y5s8DBqAYNpY7v1q86+NjU18v7hj2ejU3A==", + "requires": { + "@opentelemetry/core": "1.27.0" + } + }, "@opentelemetry/redis-common": { "version": "0.36.2", "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz", "integrity": "sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==" }, "@opentelemetry/resource-detector-alibaba-cloud": { - "version": "0.29.3", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.29.3.tgz", - "integrity": "sha512-jdnG/cYItxwKGRj2n3YsJn1+j3QsMRUfaH/mQSj2b6yULo7bKO4turvASwxy3GuSDH55VwrK+F8oIbanJk69ng==", + "version": "0.29.4", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.29.4.tgz", + "integrity": "sha512-U3sWPoBXiEE51jJGhRrW19hLvrRbBbZWTp3Yc7IaRVFODNNzmibOolyi2ow1XN68UgRT4BRuwgwbnM5GbG/E5Q==", "requires": { "@opentelemetry/core": "^1.26.0", "@opentelemetry/resources": "^1.10.0", @@ -17772,9 +17273,9 @@ } }, "@opentelemetry/resource-detector-aws": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-1.6.2.tgz", - "integrity": "sha512-xxT6PVmcBTtCo0rf4Bv/mnDMpVRITVt13bDX8mOqKVb0kr5EwIMabZS5EGJhXjP4nljrOIA7ZlOJgSX0Kehfkw==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-1.7.0.tgz", + "integrity": "sha512-VxrwUi/9QcVIV+40d/jOKQthfD/E4/ppQ9FsYpDH7qy16cOO5519QOdihCQJYpVNbgDqf6q3hVrCy1f8UuG8YA==", "requires": { "@opentelemetry/core": "^1.0.0", "@opentelemetry/resources": "^1.10.0", @@ -17782,9 +17283,9 @@ } }, "@opentelemetry/resource-detector-azure": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-azure/-/resource-detector-azure-0.2.11.tgz", - "integrity": "sha512-XepvQfTXWyHAoAziCfXGwYbSZL0LHtFk5iuKKN2VE2vzcoiw5Tepi0Qafuwb7CCtpQRReao4H7E29MFbCmh47g==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-azure/-/resource-detector-azure-0.2.12.tgz", + "integrity": "sha512-iIarQu6MiCjEEp8dOzmBvCSlRITPFTinFB2oNKAjU6xhx8d7eUcjNOKhBGQTvuCriZrxrEvDaEEY9NfrPQ6uYQ==", "requires": { "@opentelemetry/core": "^1.25.1", "@opentelemetry/resources": "^1.10.1", @@ -17792,9 +17293,9 @@ } }, "@opentelemetry/resource-detector-container": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.4.4.tgz", - "integrity": "sha512-ZEN2mq7lIjQWJ8NTt1umtr6oT/Kb89856BOmESLSvgSHbIwOFYs7cSfSRH5bfiVw6dXTQAVbZA/wLgCHKrebJA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.5.0.tgz", + "integrity": "sha512-ozp+ggcbl17xFfL91+DFgP8nmfzthNLxVTDOQUVgQgngVsSaBb5/I1Tnt63ZX2GCMdBJTxUBbFsqFvO0CjfGLg==", "requires": { "@opentelemetry/core": "^1.26.0", "@opentelemetry/resources": "^1.10.0", @@ -17802,9 +17303,9 @@ } }, "@opentelemetry/resource-detector-gcp": { - "version": "0.29.12", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.29.12.tgz", - "integrity": "sha512-liFG9XTaFVyY9YdFy4r2qVNa4a+Mg3k+XoWzzq2F+/xR0hFLfL0H4k7CsMW+T4Vl+1Cvc9W9WEVt5VCF4u/SYw==", + "version": "0.29.13", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.29.13.tgz", + "integrity": "sha512-vdotx+l3Q+89PeyXMgKEGnZ/CwzwMtuMi/ddgD9/5tKZ08DfDGB2Npz9m2oXPHRCjc4Ro6ifMqFlRyzIvgOjhg==", "requires": { "@opentelemetry/core": "^1.0.0", "@opentelemetry/resources": "^1.10.0", @@ -17813,226 +17314,77 @@ } }, "@opentelemetry/resources": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.25.1.tgz", - "integrity": "sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.27.0.tgz", + "integrity": "sha512-jOwt2VJ/lUD5BLc+PMNymDrUCpm5PKi1E9oSVYAvz01U/VdndGmrtV3DU1pG4AwlYhJRHbHfOUIlpBeXCPw6QQ==", "requires": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/semantic-conventions": "1.25.1" - }, - "dependencies": { - "@opentelemetry/core": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.25.1.tgz", - "integrity": "sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==", - "requires": { - "@opentelemetry/semantic-conventions": "1.25.1" - } - }, - "@opentelemetry/semantic-conventions": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", - "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==" - } + "@opentelemetry/core": "1.27.0", + "@opentelemetry/semantic-conventions": "1.27.0" + } + }, + "@opentelemetry/sdk-logs": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.54.0.tgz", + "integrity": "sha512-HeWvOPiWhEw6lWvg+lCIi1WhJnIPbI4/OFZgHq9tKfpwF3LX6/kk3+GR8sGUGAEZfbjPElkkngzvd2s03zbD7Q==", + "requires": { + "@opentelemetry/api-logs": "0.54.0", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/resources": "1.27.0" } }, "@opentelemetry/sdk-metrics": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.25.1.tgz", - "integrity": "sha512-9Mb7q5ioFL4E4dDrc4wC/A3NTHDat44v4I3p2pLPSxRvqUbDIQyMVr9uK+EU69+HWhlET1VaSrRzwdckWqY15Q==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.27.0.tgz", + "integrity": "sha512-JzWgzlutoXCydhHWIbLg+r76m+m3ncqvkCcsswXAQ4gqKS+LOHKhq+t6fx1zNytvLuaOUBur7EvWxECc4jPQKg==", "requires": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/resources": "1.25.1", - "lodash.merge": "^4.6.2" - }, - "dependencies": { - "@opentelemetry/core": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.25.1.tgz", - "integrity": "sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==", - "requires": { - "@opentelemetry/semantic-conventions": "1.25.1" - } - }, - "@opentelemetry/semantic-conventions": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", - "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==" - } + "@opentelemetry/core": "1.27.0", + "@opentelemetry/resources": "1.27.0" } }, "@opentelemetry/sdk-node": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.53.0.tgz", - "integrity": "sha512-0hsxfq3BKy05xGktwG8YdGdxV978++x40EAKyKr1CaHZRh8uqVlXnclnl7OMi9xLMJEcXUw7lGhiRlArFcovyg==", - "requires": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/exporter-logs-otlp-grpc": "0.53.0", - "@opentelemetry/exporter-logs-otlp-http": "0.53.0", - "@opentelemetry/exporter-logs-otlp-proto": "0.53.0", - "@opentelemetry/exporter-trace-otlp-grpc": "0.53.0", - "@opentelemetry/exporter-trace-otlp-http": "0.53.0", - "@opentelemetry/exporter-trace-otlp-proto": "0.53.0", - "@opentelemetry/exporter-zipkin": "1.26.0", - "@opentelemetry/instrumentation": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-metrics": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "@opentelemetry/sdk-trace-node": "1.26.0", + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.54.0.tgz", + "integrity": "sha512-F0mdwb4WPFJNypcmkxQnj3sIfh/73zkBgYePXMK8ghsBwYw4+PgM3/85WT6NzNUeOvWtiXacx5CFft2o7rGW3w==", + "requires": { + "@opentelemetry/api-logs": "0.54.0", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/exporter-logs-otlp-grpc": "0.54.0", + "@opentelemetry/exporter-logs-otlp-http": "0.54.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.54.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.54.0", + "@opentelemetry/exporter-trace-otlp-http": "0.54.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.54.0", + "@opentelemetry/exporter-zipkin": "1.27.0", + "@opentelemetry/instrumentation": "0.54.0", + "@opentelemetry/resources": "1.27.0", + "@opentelemetry/sdk-logs": "0.54.0", + "@opentelemetry/sdk-metrics": "1.27.0", + "@opentelemetry/sdk-trace-base": "1.27.0", + "@opentelemetry/sdk-trace-node": "1.27.0", "@opentelemetry/semantic-conventions": "1.27.0" - }, - "dependencies": { - "@opentelemetry/exporter-trace-otlp-grpc": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.53.0.tgz", - "integrity": "sha512-m6KSh6OBDwfDjpzPVbuJbMgMbkoZfpxYH2r262KckgX9cMYvooWXEKzlJYsNDC6ADr28A1rtRoUVRwNfIN4tUg==", - "requires": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0" - } - }, - "@opentelemetry/exporter-trace-otlp-http": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.53.0.tgz", - "integrity": "sha512-m7F5ZTq+V9mKGWYpX8EnZ7NjoqAU7VemQ1E2HAG+W/u0wpY1x0OmbxAXfGKFHCspdJk8UKlwPGrpcB8nay3P8A==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0" - } - }, - "@opentelemetry/exporter-trace-otlp-proto": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.53.0.tgz", - "integrity": "sha512-T/bdXslwRKj23S96qbvGtaYOdfyew3TjPEKOk5mHjkCmkVl1O9C/YMdejwSsdLdOq2YW30KjR9kVi0YMxZushQ==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0" - } - }, - "@opentelemetry/exporter-zipkin": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.26.0.tgz", - "integrity": "sha512-PW5R34n3SJHO4t0UetyHKiXL6LixIqWN6lWncg3eRXhKuT30x+b7m5sDJS0kEWRfHeS+kG7uCw2vBzmB2lk3Dw==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - } - }, - "@opentelemetry/otlp-exporter-base": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.53.0.tgz", - "integrity": "sha512-UCWPreGQEhD6FjBaeDuXhiMf6kkBODF0ZQzrk/tuQcaVDJ+dDQ/xhJp192H9yWnKxVpEjFrSSLnpqmX4VwX+eA==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-transformer": "0.53.0" - } - }, - "@opentelemetry/otlp-grpc-exporter-base": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.53.0.tgz", - "integrity": "sha512-F7RCN8VN+lzSa4fGjewit8Z5fEUpY/lmMVy5EWn2ZpbAabg3EE3sCLuTNfOiooNGnmvzimUPruoeqeko/5/TzQ==", - "requires": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0" - } - }, - "@opentelemetry/otlp-transformer": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.53.0.tgz", - "integrity": "sha512-rM0sDA9HD8dluwuBxLetUmoqGJKSAbWenwD65KY9iZhUxdBHRLrIdrABfNDP7aiTjcgK8XFyTn5fhDz7N+W6DA==", - "requires": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-metrics": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "protobufjs": "^7.3.0" - } - }, - "@opentelemetry/propagator-b3": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.26.0.tgz", - "integrity": "sha512-vvVkQLQ/lGGyEy9GT8uFnI047pajSOVnZI2poJqVGD3nJ+B9sFGdlHNnQKophE3lHfnIH0pw2ubrCTjZCgIj+Q==", - "requires": { - "@opentelemetry/core": "1.26.0" - } - }, - "@opentelemetry/propagator-jaeger": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.26.0.tgz", - "integrity": "sha512-DelFGkCdaxA1C/QA0Xilszfr0t4YbGd3DjxiCDPh34lfnFr+VkkrjV9S8ZTJvAzfdKERXhfOxIKBoGPJwoSz7Q==", - "requires": { - "@opentelemetry/core": "1.26.0" - } - }, - "@opentelemetry/resources": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.26.0.tgz", - "integrity": "sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - } - }, - "@opentelemetry/sdk-logs": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.53.0.tgz", - "integrity": "sha512-dhSisnEgIj/vJZXZV6f6KcTnyLDx/VuQ6l3ejuZpMpPlh9S1qMHiZU9NMmOkVkwwHkMy3G6mEBwdP23vUZVr4g==", - "requires": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" - } - }, - "@opentelemetry/sdk-metrics": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.26.0.tgz", - "integrity": "sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" - } - }, - "@opentelemetry/sdk-trace-base": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz", - "integrity": "sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - } - }, - "@opentelemetry/sdk-trace-node": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.26.0.tgz", - "integrity": "sha512-Fj5IVKrj0yeUwlewCRwzOVcr5avTuNnMHWf7GPc1t6WaT78J6CJyF3saZ/0RkZfdeNO8IcBl/bNcWMVZBMRW8Q==", - "requires": { - "@opentelemetry/context-async-hooks": "1.26.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/propagator-b3": "1.26.0", - "@opentelemetry/propagator-jaeger": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "semver": "^7.5.2" - } - } + } + }, + "@opentelemetry/sdk-trace-base": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.27.0.tgz", + "integrity": "sha512-btz6XTQzwsyJjombpeqCX6LhiMQYpzt2pIYNPnw0IPO/3AhT6yjnf8Mnv3ZC2A4eRYOjqrg+bfaXg9XHDRJDWQ==", + "requires": { + "@opentelemetry/core": "1.27.0", + "@opentelemetry/resources": "1.27.0", + "@opentelemetry/semantic-conventions": "1.27.0" + } + }, + "@opentelemetry/sdk-trace-node": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.27.0.tgz", + "integrity": "sha512-dWZp/dVGdUEfRBjBq2BgNuBlFqHCxyyMc8FsN0NX15X07mxSUO0SZRLyK/fdAVrde8nqFI/FEdMH4rgU9fqJfQ==", + "requires": { + "@opentelemetry/context-async-hooks": "1.27.0", + "@opentelemetry/core": "1.27.0", + "@opentelemetry/propagator-b3": "1.27.0", + "@opentelemetry/propagator-jaeger": "1.27.0", + "@opentelemetry/sdk-trace-base": "1.27.0", + "semver": "^7.5.2" } }, "@opentelemetry/semantic-conventions": { @@ -18464,92 +17816,92 @@ "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" }, "@swc/core": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.36.tgz", - "integrity": "sha512-bu7ymMX+LCJOSSrKank25Jaq66ymLVA9fOUuy4ck3/6rbXdLw+pIJPnIDKQ9uNcxww8KDxOuJk9Ui9pqR+aGFw==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.42.tgz", + "integrity": "sha512-iQrRk3SKndQZ4ptJv1rzeQSiCYQIhMjiO97QXOlCcCoaazOLKPnLnXzU4Kv0FuBFyYfG2FE94BoR0XI2BN02qw==", "devOptional": true, "requires": { - "@swc/core-darwin-arm64": "1.7.36", - "@swc/core-darwin-x64": "1.7.36", - "@swc/core-linux-arm-gnueabihf": "1.7.36", - "@swc/core-linux-arm64-gnu": "1.7.36", - "@swc/core-linux-arm64-musl": "1.7.36", - "@swc/core-linux-x64-gnu": "1.7.36", - "@swc/core-linux-x64-musl": "1.7.36", - "@swc/core-win32-arm64-msvc": "1.7.36", - "@swc/core-win32-ia32-msvc": "1.7.36", - "@swc/core-win32-x64-msvc": "1.7.36", + "@swc/core-darwin-arm64": "1.7.42", + "@swc/core-darwin-x64": "1.7.42", + "@swc/core-linux-arm-gnueabihf": "1.7.42", + "@swc/core-linux-arm64-gnu": "1.7.42", + "@swc/core-linux-arm64-musl": "1.7.42", + "@swc/core-linux-x64-gnu": "1.7.42", + "@swc/core-linux-x64-musl": "1.7.42", + "@swc/core-win32-arm64-msvc": "1.7.42", + "@swc/core-win32-ia32-msvc": "1.7.42", + "@swc/core-win32-x64-msvc": "1.7.42", "@swc/counter": "^0.1.3", "@swc/types": "^0.1.13" } }, "@swc/core-darwin-arm64": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.36.tgz", - "integrity": "sha512-8vDczXzCgv3ceTPhEivlpGprN44YlrCK1nbfU9g2TrhV/Aiqi09W/eM5zLesdoM1Z3mJl492gc/8nlTkpDdusw==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.42.tgz", + "integrity": "sha512-fWhaCs2+8GDRIcjExVDEIfbptVrxDqG8oHkESnXgymmvqTWzWei5SOnPNMS8Q+MYsn/b++Y2bDxkcwmq35Bvxg==", "dev": true, "optional": true }, "@swc/core-darwin-x64": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.7.36.tgz", - "integrity": "sha512-Pa2Gao7+Wf5m3SsK4abKRtd48AtoUnJInvaC3d077swBfgZjbjUbQvcpdc2dOeQtWwo49rFqUZJonMsL0jnPgQ==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.7.42.tgz", + "integrity": "sha512-ZaVHD2bijrlkCyD7NDzLmSK849Jgcx+6DdL4x1dScoz1slJ8GTvLtEu0JOUaaScQwA+cVlhmrmlmi9ssjbRLGQ==", "dev": true, "optional": true }, "@swc/core-linux-arm-gnueabihf": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.36.tgz", - "integrity": "sha512-3YsMWd7V+WZEjbfBnLkkz/olcRBa8nyoK0iIOnNARJBMcYaJxjkJSMZpmSojCnIVwvjA1N83CPAbUL+W+fCnHg==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.42.tgz", + "integrity": "sha512-iF0BJj7hVTbY/vmbvyzVTh/0W80+Q4fbOYschdUM3Bsud39TA+lSaPOefOHywkNH58EQ1z3EAxYcJOWNES7GFQ==", "dev": true, "optional": true }, "@swc/core-linux-arm64-gnu": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.36.tgz", - "integrity": "sha512-lqM3aBB7kJazJYOwHeA5OGNLqXoQPZ/76b3dV+XcjN1GhD0CcXz6mW5PRYVin6OSN1eKrKBKJjtDA1mqADDEvw==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.42.tgz", + "integrity": "sha512-xGu8j+DOLYTLkVmsfZPJbNPW1EkiWgSucT0nOlz77bLxImukt/0+HVm2hOwHSKuArQ8C3cjahAMY3b/s4VH2ww==", "dev": true, "optional": true }, "@swc/core-linux-arm64-musl": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.36.tgz", - "integrity": "sha512-bqei2YDzvUfG0pth5W2xJaj0eG4XWYk0d/NJ75vBX6bkIzK6dC8iuKQ41jOfUWonnrAs7rTDDJW0sTn/evvRdw==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.42.tgz", + "integrity": "sha512-qtW3JNO7i1yHEko59xxz+jY38+tYmB96JGzj6XzygMbYJYZDYbrOpXQvKbMGNG3YeTDan7Fp2jD0dlKf7NgDPA==", "dev": true, "optional": true }, "@swc/core-linux-x64-gnu": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.36.tgz", - "integrity": "sha512-03maXTUyaBjeCxlDltmdzHje1ryQt1C4OWmmNgSSRXjLb+GNnAenwOJMSrcvHP/aNClD2pwsFCnYKDGy+sYE6w==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.42.tgz", + "integrity": "sha512-F9WY1TN+hhhtiEzZjRQziNLt36M5YprMeOBHjsLVNqwgflzleSI7ulgnlQECS8c8zESaXj3ksGduAoJYtPC1cA==", "dev": true, "optional": true }, "@swc/core-linux-x64-musl": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.36.tgz", - "integrity": "sha512-XXysqLkvjtQnXm1zHqLhy00UYPv/gk5OtwR732X+piNisnEbcJBqI8Qp9O7YvLWllRcoP8IMBGDWLGdGLSpViA==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.42.tgz", + "integrity": "sha512-7YMdOaYKLMQ8JGfnmRDwidpLFs/6ka+80zekeM0iCVO48yLrJR36G0QGXzMjKsXI0BPhq+mboZRRENK4JfQnEA==", "dev": true, "optional": true }, "@swc/core-win32-arm64-msvc": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.36.tgz", - "integrity": "sha512-k7+dmb13a/zPw+E4XYfPmLZFWJgcOcBRKIjYl9nQErtYsgsg3Ji6TBbsvJVETy23lNHyewZ17V5Vq6NzaG0hzg==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.42.tgz", + "integrity": "sha512-C5CYWaIZEyqPl5W/EwcJ/mLBJFHVoUEa/IwWi0b4q2fCXcSCktQGwKXOQ+d67GneiZoiq0HasgcdMmMpGS9YRQ==", "dev": true, "optional": true }, "@swc/core-win32-ia32-msvc": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.36.tgz", - "integrity": "sha512-ridD3ay6YM2PEYHZXXFN+edYEv0FOynaqOBP+NSnGNHA35azItIjoIe+KNi4WltGtAjpKCHSpjGCNfna12wdYQ==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.42.tgz", + "integrity": "sha512-3j47seZ5pO62mbrqvPe1iwhe2BXnM5q7iB+n2xgA38PCGYt0mnaJafqmpCXm/uYZOCMqSNynaoOWCMMZm4sqtA==", "dev": true, "optional": true }, "@swc/core-win32-x64-msvc": { - "version": "1.7.36", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.36.tgz", - "integrity": "sha512-j1z2Z1Ln9d0E3dHsPkC1K9XDh0ojhRPwV+GfRTu4D61PE+aYhYLvbJC6xPvL4/204QrStRS7eDu3m+BcDp3rgQ==", + "version": "1.7.42", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.42.tgz", + "integrity": "sha512-FXl9MdeUogZLGDcLr6QIRdDVkpG0dkN4MLM4dwQ5kcAk+XfKPrQibX6M2kcfhsCx+jtBqtK7hRFReRXPWJZGbA==", "dev": true, "optional": true }, @@ -18568,9 +17920,9 @@ } }, "@swc/types": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.13.tgz", - "integrity": "sha512-JL7eeCk6zWCbiYQg2xQSdLXQJl8Qoc9rXmG2cEKvHe3CKwMHwHGpfOb8frzNLmbycOo6I51qxnLnn9ESf4I20Q==", + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.14.tgz", + "integrity": "sha512-PbSmTiYCN+GMrvfjrMo9bdY+f2COnwbdnoMw7rqU/PI5jXpKjxOGZ0qqZCImxnT81NkNsKnmEpvu+hRXLBeCJg==", "devOptional": true, "requires": { "@swc/counter": "^0.1.3" @@ -18645,9 +17997,9 @@ } }, "@types/archiver": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.2.tgz", - "integrity": "sha512-KmROQqbQzKGuaAbmK+ZcytkJ51+YqDa7NmbXjmtC5YBLSyQYo21YaUnQ3HbaPFKL1ooo6RQ6OPYPIDyxfpDDXw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.3.tgz", + "integrity": "sha512-a6wUll6k3zX6qs5KlxIggs1P1JcYJaTCx2gnlr+f0S1yd2DoaEwoIK10HmBaLnZwWneBz+JBm0dwcZu0zECBcQ==", "dev": true, "requires": { "@types/readdir-glob": "*" @@ -18720,9 +18072,9 @@ "dev": true }, "@types/cors": { - "version": "2.8.14", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.14.tgz", - "integrity": "sha512-RXHUvNWYICtbP6s18PnOCaqToK8y14DnLd75c6HfyKf228dxy7pHNOQkxPtvXKp/hINFMDjbYzsj63nnpPMSRQ==", + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", "requires": { "@types/node": "*" } @@ -18791,9 +18143,9 @@ } }, "@types/fluent-ffmpeg": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.26.tgz", - "integrity": "sha512-0JVF3wdQG+pN0ImwWD0bNgJiKF2OHg/7CDBHw5UIbRTvlnkgGHK6V5doE54ltvhud4o31/dEiHm23CAlxFiUQg==", + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.27.tgz", + "integrity": "sha512-QiDWjihpUhriISNoBi2hJBRUUmoj/BMTYcfz+F+ZM9hHWBYABFAE6hjP/TbCZC0GWwlpa3FzvHH9RzFeRusZ7A==", "dev": true, "requires": { "@types/node": "*" @@ -18833,9 +18185,9 @@ "dev": true }, "@types/lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ==", + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", + "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==", "dev": true }, "@types/luxon": { @@ -18890,11 +18242,11 @@ } }, "@types/node": { - "version": "20.16.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.13.tgz", - "integrity": "sha512-GjQ7im10B0labo8ZGXDGROUl9k0BNyDgzfGpb4g/cl+4yYDWVKcozANF4FGr4/p0O/rAkQClM6Wiwkije++1Tg==", + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", "requires": { - "undici-types": "~6.19.2" + "undici-types": "~6.19.8" } }, "@types/nodemailer": { @@ -19003,9 +18355,9 @@ "dev": true }, "@types/react": { - "version": "18.3.11", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", - "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==", + "version": "18.3.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", "dev": true, "requires": { "@types/prop-types": "*", @@ -19122,16 +18474,16 @@ "integrity": "sha512-c/hzNDBh7eRF+KbCf+OoZxKbnkpaK/cKp9iLQWqB7muXtM+MtL9SUUH8vCFcLn6dH1Qm05jiexK0ofWY7TfOhQ==" }, "@typescript-eslint/eslint-plugin": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.10.0.tgz", - "integrity": "sha512-phuB3hoP7FFKbRXxjl+DRlQDuJqhpOnm5MmtROXyWi3uS/Xg2ZXqiQfcG2BJHiN4QKyzdOJi3NEn/qTnjUlkmQ==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.12.2.tgz", + "integrity": "sha512-gQxbxM8mcxBwaEmWdtLCIGLfixBMHhQjBqR8sVWNTPpcj45WlYL2IObS/DNMLH1DBP0n8qz+aiiLTGfopPEebw==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.10.0", - "@typescript-eslint/type-utils": "8.10.0", - "@typescript-eslint/utils": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0", + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/type-utils": "8.12.2", + "@typescript-eslint/utils": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -19139,54 +18491,54 @@ } }, "@typescript-eslint/parser": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.10.0.tgz", - "integrity": "sha512-E24l90SxuJhytWJ0pTQydFT46Nk0Z+bsLKo/L8rtQSL93rQ6byd1V/QbDpHUTdLPOMsBCcYXZweADNCfOCmOAg==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.12.2.tgz", + "integrity": "sha512-MrvlXNfGPLH3Z+r7Tk+Z5moZAc0dzdVjTgUgwsdGweH7lydysQsnSww3nAmsq8blFuRD5VRlAr9YdEFw3e6PBw==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.10.0", - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/typescript-estree": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0", + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/typescript-estree": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.10.0.tgz", - "integrity": "sha512-AgCaEjhfql9MDKjMUxWvH7HjLeBqMCBfIaBbzzIcBbQPZE7CPh1m6FF+L75NUMJFMLYhCywJXIDEMa3//1A0dw==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.12.2.tgz", + "integrity": "sha512-gPLpLtrj9aMHOvxJkSbDBmbRuYdtiEbnvO25bCMza3DhMjTQw0u7Y1M+YR5JPbMsXXnSPuCf5hfq0nEkQDL/JQ==", "dev": true, "requires": { - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0" + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2" } }, "@typescript-eslint/type-utils": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.10.0.tgz", - "integrity": "sha512-PCpUOpyQSpxBn230yIcK+LeCQaXuxrgCm2Zk1S+PTIRJsEfU6nJ0TtwyH8pIwPK/vJoA+7TZtzyAJSGBz+s/dg==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.12.2.tgz", + "integrity": "sha512-bwuU4TAogPI+1q/IJSKuD4shBLc/d2vGcRT588q+jzayQyjVK2X6v/fbR4InY2U2sgf8MEvVCqEWUzYzgBNcGQ==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "8.10.0", - "@typescript-eslint/utils": "8.10.0", + "@typescript-eslint/typescript-estree": "8.12.2", + "@typescript-eslint/utils": "8.12.2", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" } }, "@typescript-eslint/types": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.10.0.tgz", - "integrity": "sha512-k/E48uzsfJCRRbGLapdZgrX52csmWJ2rcowwPvOZ8lwPUv3xW6CcFeJAXgx4uJm+Ge4+a4tFOkdYvSpxhRhg1w==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.12.2.tgz", + "integrity": "sha512-VwDwMF1SZ7wPBUZwmMdnDJ6sIFk4K4s+ALKLP6aIQsISkPv8jhiw65sAK6SuWODN/ix+m+HgbYDkH+zLjrzvOA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.10.0.tgz", - "integrity": "sha512-3OE0nlcOHaMvQ8Xu5gAfME3/tWVDpb/HxtpUZ1WeOAksZ/h/gwrBzCklaGzwZT97/lBbbxJ16dMA98JMEngW4w==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.12.2.tgz", + "integrity": "sha512-mME5MDwGe30Pq9zKPvyduyU86PH7aixwqYR2grTglAdB+AN8xXQ1vFGpYaUSJ5o5P/5znsSBeNcs5g5/2aQwow==", "dev": true, "requires": { - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -19216,42 +18568,42 @@ } }, "@typescript-eslint/utils": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.10.0.tgz", - "integrity": "sha512-Oq4uZ7JFr9d1ZunE/QKy5egcDRXT/FrS2z/nlxzPua2VHFtmMvFNDvpq1m/hq0ra+T52aUezfcjGRIB7vNJF9w==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.12.2.tgz", + "integrity": "sha512-UTTuDIX3fkfAz6iSVa5rTuSfWIYZ6ATtEocQ/umkRSyC9O919lbZ8dcH7mysshrCdrAM03skJOEYaBugxN+M6A==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.10.0", - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/typescript-estree": "8.10.0" + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/typescript-estree": "8.12.2" } }, "@typescript-eslint/visitor-keys": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.10.0.tgz", - "integrity": "sha512-k8nekgqwr7FadWk548Lfph6V3r9OVqjzAIVskE7orMZR23cGJjAOVazsZSJW+ElyjfTM4wx/1g88Mi70DDtG9A==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.12.2.tgz", + "integrity": "sha512-PChz8UaKQAVNHghsHcPyx1OMHoFRUEA7rJSK/mDhdq85bk+PLsUHUBqTQTFt18VJZbmxBovM65fezlheQRsSDA==", "dev": true, "requires": { - "@typescript-eslint/types": "8.10.0", + "@typescript-eslint/types": "8.12.2", "eslint-visitor-keys": "^3.4.3" } }, "@vitest/coverage-v8": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.3.tgz", - "integrity": "sha512-2OJ3c7UPoFSmBZwqD2VEkUw6A/tzPF0LmW0ZZhhB8PFxuc+9IBG/FaSM+RLEenc7ljzFvGN+G0nGQoZnh7sy2A==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.4.tgz", + "integrity": "sha512-FPKQuJfR6VTfcNMcGpqInmtJuVXFSCd9HQltYncfR01AzXhLucMEtQ5SinPdZxsT5x/5BK7I5qFJ5/ApGCmyTQ==", "dev": true, "requires": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.6", + "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.11", - "magicast": "^0.3.4", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", "std-env": "^3.7.0", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" @@ -19269,32 +18621,32 @@ } }, "@vitest/expect": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.3.tgz", - "integrity": "sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.4.tgz", + "integrity": "sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==", "dev": true, "requires": { - "@vitest/spy": "2.1.3", - "@vitest/utils": "2.1.3", - "chai": "^5.1.1", + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", "tinyrainbow": "^1.2.0" } }, "@vitest/mocker": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.3.tgz", - "integrity": "sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.4.tgz", + "integrity": "sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==", "dev": true, "requires": { - "@vitest/spy": "2.1.3", + "@vitest/spy": "2.1.4", "estree-walker": "^3.0.3", - "magic-string": "^0.30.11" + "magic-string": "^0.30.12" }, "dependencies": { "magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", "dev": true, "requires": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -19303,39 +18655,39 @@ } }, "@vitest/pretty-format": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.3.tgz", - "integrity": "sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.4.tgz", + "integrity": "sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==", "dev": true, "requires": { "tinyrainbow": "^1.2.0" } }, "@vitest/runner": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.3.tgz", - "integrity": "sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.4.tgz", + "integrity": "sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==", "dev": true, "requires": { - "@vitest/utils": "2.1.3", + "@vitest/utils": "2.1.4", "pathe": "^1.1.2" } }, "@vitest/snapshot": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.3.tgz", - "integrity": "sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.4.tgz", + "integrity": "sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==", "dev": true, "requires": { - "@vitest/pretty-format": "2.1.3", - "magic-string": "^0.30.11", + "@vitest/pretty-format": "2.1.4", + "magic-string": "^0.30.12", "pathe": "^1.1.2" }, "dependencies": { "magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", "dev": true, "requires": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -19344,22 +18696,22 @@ } }, "@vitest/spy": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.3.tgz", - "integrity": "sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.4.tgz", + "integrity": "sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==", "dev": true, "requires": { - "tinyspy": "^3.0.0" + "tinyspy": "^3.0.2" } }, "@vitest/utils": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.3.tgz", - "integrity": "sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.4.tgz", + "integrity": "sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==", "dev": true, "requires": { - "@vitest/pretty-format": "2.1.3", - "loupe": "^3.1.1", + "@vitest/pretty-format": "2.1.4", + "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" } }, @@ -20013,9 +19365,9 @@ "dev": true }, "bullmq": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-4.18.0.tgz", - "integrity": "sha512-TJiS8YaO3GpaiSum3EufaXHnZc7Q5+BjJP1+kM8pD/DAB7BzI44RHZh9yEHgFHI0ikwmHzULIA9zUqkF3L53Ag==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-4.18.2.tgz", + "integrity": "sha512-Cx0O98IlGiFw7UBa+zwGz+nH0Pcl1wfTvMVBlsMna3s0219hXroVovh1xPRgomyUcbyciHiugGCkW0RRNZDHYQ==", "requires": { "cron-parser": "^4.6.0", "glob": "^8.0.3", @@ -20112,9 +19464,9 @@ "integrity": "sha512-p407+D1tIkDvsEAPS22lJxLQQaG8OTBEqo0KhzfABGk0TU4juBNDSfH0hyAp/HRyx+M8L17z/ltyhxh27FTfQg==" }, "chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, "requires": { "assertion-error": "^2.0.1", @@ -20434,9 +19786,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" }, "cookie-parser": { "version": "1.4.7", @@ -20445,13 +19797,6 @@ "requires": { "cookie": "0.7.2", "cookie-signature": "1.0.6" - }, - "dependencies": { - "cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" - } } }, "cookie-signature": { @@ -20806,11 +20151,6 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==" }, - "dotenv-expand": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", - "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==" - }, "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -20880,16 +20220,16 @@ } }, "engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", "requires": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "~0.4.1", + "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", @@ -20897,9 +20237,9 @@ } }, "engine.io-parser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", - "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==" + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==" }, "enhanced-resolve": { "version": "5.17.1", @@ -20991,17 +20331,17 @@ "dev": true }, "eslint": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz", - "integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz", + "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.6.0", + "@eslint/core": "^0.7.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.12.0", + "@eslint/js": "9.13.0", "@eslint/plugin-kit": "^0.2.0", "@humanfs/node": "^0.16.5", "@humanwhocodes/module-importer": "^1.0.1", @@ -21052,9 +20392,9 @@ } }, "eslint-visitor-keys": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", - "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true }, "glob-parent": { @@ -21216,31 +20556,37 @@ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, "exiftool-vendored": { - "version": "28.6.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-28.6.0.tgz", - "integrity": "sha512-Cx8/8ov1tKEacHhsi7FNYdisIhKq/SeQfprYSpYzwBuJwkPmCV8w7tTIvUJRQX9rvopXhBA4eBf1FPXqTZW5vA==", + "version": "28.7.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-28.7.0.tgz", + "integrity": "sha512-0zoq6kBS1yPjzJs+p0qZDinWEA72PTKoRk5ETYKfmeRcZAkhv83Y3HCpbb/LdgJJywfm8BcIJGezrBHvL7dVnQ==", "requires": { "@photostructure/tz-lookup": "^11.0.0", "@types/luxon": "^3.4.2", "batch-cluster": "^13.0.0", - "exiftool-vendored.exe": "12.97.0", - "exiftool-vendored.pl": "12.97.0", + "exiftool-vendored.exe": "12.99.0", + "exiftool-vendored.pl": "12.99.0", "he": "^1.2.0", "luxon": "^3.5.0" } }, "exiftool-vendored.exe": { - "version": "12.97.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.97.0.tgz", - "integrity": "sha512-+HxyFigEJOtwRjP7PhEslhZKuVW2V0hvmHPHtbVtNKGfAUGcfc95xNTjASQfKJvc+9ZuvzdEBPkEQmyA/ZYdIw==", + "version": "12.99.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.99.0.tgz", + "integrity": "sha512-ffpJHCzC9OYJqw4JlPNtCwRy02jwhmnSJEF/QqEjpuIWDEnlRBQP/yWRh1Nw21K1R4FB4yG5PlCgEDu09VQz/w==", "optional": true }, "exiftool-vendored.pl": { - "version": "12.97.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.97.0.tgz", - "integrity": "sha512-mXe9JEH3csfyPWcC7+H6IpfaokDMMr4S45n7MtiobGPdeeh+kFnf1SQ9cxg4sF403P6IKVeYYPbzgKMlpro9eQ==", + "version": "12.99.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.99.0.tgz", + "integrity": "sha512-qRVEPQxtoerXF+izJ0O7jGAr5o0Uyvnyu7ao5DTKzF+V7Fv3SurE0l43oCeZPFKo/Ld4V7vEylhFCm4IHVZKWA==", "optional": true }, + "expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true + }, "express": { "version": "4.21.1", "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", @@ -21526,6 +20872,11 @@ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, + "forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==" + }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -22233,9 +21584,9 @@ "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==" }, "nopt": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", - "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", "requires": { "abbrev": "^2.0.0" } @@ -22439,7 +21790,8 @@ "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true }, "log-symbols": { "version": "4.1.0", @@ -22492,14 +21844,25 @@ } }, "magicast": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", - "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, "requires": { - "@babel/parser": "^7.24.4", - "@babel/types": "^7.24.0", + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" + }, + "dependencies": { + "@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "dev": true, + "requires": { + "@babel/types": "^7.26.0" + } + } } }, "make-dir": { @@ -22674,9 +22037,9 @@ "dev": true }, "mock-fs": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.4.0.tgz", - "integrity": "sha512-3ROPnEMgBOkusBMYQUW2rnT3wZwsgfOKzJDLvx/TZ7FL1WmWvwSwn3j4aDR5fLDGtgcc1WF0Z1y0di7c9L4FKw==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.4.1.tgz", + "integrity": "sha512-sz/Q8K1gXXXHR+qr0GZg2ysxCRr323kuN10O7CtQjraJsFDJ4SJ+0I5MzALz7aRp9lHk8Cc/YdsT95h9Ka1aFw==", "dev": true }, "module-details-from-path": { @@ -22884,20 +22247,20 @@ } }, "next": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.3.tgz", - "integrity": "sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==", - "requires": { - "@next/env": "14.2.3", - "@next/swc-darwin-arm64": "14.2.3", - "@next/swc-darwin-x64": "14.2.3", - "@next/swc-linux-arm64-gnu": "14.2.3", - "@next/swc-linux-arm64-musl": "14.2.3", - "@next/swc-linux-x64-gnu": "14.2.3", - "@next/swc-linux-x64-musl": "14.2.3", - "@next/swc-win32-arm64-msvc": "14.2.3", - "@next/swc-win32-ia32-msvc": "14.2.3", - "@next/swc-win32-x64-msvc": "14.2.3", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.10.tgz", + "integrity": "sha512-sDDExXnh33cY3RkS9JuFEKaS4HmlWmDKP1VJioucCG6z5KuA008DPsDZOzi8UfqEk3Ii+2NCQSJrfbEWtZZfww==", + "requires": { + "@next/env": "14.2.10", + "@next/swc-darwin-arm64": "14.2.10", + "@next/swc-darwin-x64": "14.2.10", + "@next/swc-linux-arm64-gnu": "14.2.10", + "@next/swc-linux-arm64-musl": "14.2.10", + "@next/swc-linux-x64-gnu": "14.2.10", + "@next/swc-linux-x64-musl": "14.2.10", + "@next/swc-win32-arm64-msvc": "14.2.10", + "@next/swc-win32-ia32-msvc": "14.2.10", + "@next/swc-win32-x64-msvc": "14.2.10", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -22952,9 +22315,9 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "nodemailer": { - "version": "6.9.15", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.15.tgz", - "integrity": "sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==" + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", + "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==" }, "nopt": { "version": "5.0.0", @@ -23290,9 +22653,9 @@ "integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==" }, "pg": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.0.tgz", - "integrity": "sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw==", + "version": "8.13.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.1.tgz", + "integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==", "requires": { "pg-cloudflare": "^1.1.1", "pg-connection-string": "^2.7.0", @@ -23578,9 +22941,9 @@ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" }, "protobufjs": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.2.tgz", - "integrity": "sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -23704,24 +23067,24 @@ } }, "react-email": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/react-email/-/react-email-3.0.1.tgz", - "integrity": "sha512-G4Bkx2ULIScy/0Z8nnWywHt0W1iTkaYCdh9rWNuQ3eVZ6B3ttTUDE9uUy3VNQ8dtQbmG0cpt8+XmImw7mMBW6Q==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/react-email/-/react-email-3.0.2.tgz", + "integrity": "sha512-R7Doynb6NbnDvHx+9dWxkiWN2eaq9hj4MxRdkS94cVD/WDaIzESSLm62GtAAyLJ65xA2ROJydFlcYsDq4hGi4Q==", "requires": { "@babel/core": "7.24.5", "@babel/parser": "7.24.5", "chalk": "4.1.2", - "chokidar": "3.6.0", + "chokidar": "^4.0.1", "commander": "11.1.0", "debounce": "2.0.0", "esbuild": "0.19.11", "glob": "10.3.4", "log-symbols": "4.1.0", "mime-types": "2.1.35", - "next": "14.2.3", + "next": "14.2.10", "normalize-path": "3.0.0", "ora": "5.4.1", - "socket.io": "4.7.5" + "socket.io": "4.8.0" }, "dependencies": { "@esbuild/aix-ppc64": { @@ -23870,6 +23233,14 @@ "balanced-match": "^1.0.0" } }, + "chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "requires": { + "readdirp": "^4.0.1" + } + }, "commander": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", @@ -23924,6 +23295,11 @@ "requires": { "brace-expansion": "^2.0.1" } + }, + "readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==" } } }, @@ -24627,15 +24003,15 @@ "integrity": "sha512-YiuPbxpCj4hD9Qs06hGAz/OZhQ0eDuALN0lRWJez0eD/RevzKqGdUx1IOMUnXgpr+sXZLq3g8ERwbAH0bCb8vg==" }, "socket.io": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", - "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", + "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", "requires": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.5.2", + "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } @@ -24731,9 +24107,9 @@ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" }, "sql-formatter": { - "version": "15.4.3", - "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.4.3.tgz", - "integrity": "sha512-RnYhnCojj9jlaVr04Vol2E0aUnZuunUq3gArnzwagsyV5mBXeX6r1rRfHdDzyDkO1NcsPiHCs9ik00Kf9AUMfQ==", + "version": "15.4.5", + "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.4.5.tgz", + "integrity": "sha512-dxYn0OzEmB19/9Y+yh8bqD8kJx2S/4pOTM4QLKxQDh7K6lp1Sx9MhmiF9RUJHSVjfV72KihW5R1h6Kecy6O5qA==", "dev": true, "requires": { "argparse": "^2.0.1", @@ -25222,9 +24598,9 @@ "dev": true }, "tinyexec": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", - "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", "dev": true }, "tinypool": { @@ -25253,11 +24629,6 @@ "os-tmpdir": "~1.0.2" } }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -25368,9 +24739,9 @@ } }, "tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==" }, "tweetnacl": { "version": "0.14.5", @@ -25625,9 +24996,9 @@ } }, "validator": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", - "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==" + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==" }, "vary": { "version": "1.1.2", @@ -25647,13 +25018,13 @@ } }, "vite-node": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.3.tgz", - "integrity": "sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.4.tgz", + "integrity": "sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==", "dev": true, "requires": { "cac": "^6.7.14", - "debug": "^4.3.6", + "debug": "^4.3.7", "pathe": "^1.1.2", "vite": "^5.0.0" } @@ -25670,29 +25041,30 @@ } }, "vitest": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.3.tgz", - "integrity": "sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==", - "dev": true, - "requires": { - "@vitest/expect": "2.1.3", - "@vitest/mocker": "2.1.3", - "@vitest/pretty-format": "^2.1.3", - "@vitest/runner": "2.1.3", - "@vitest/snapshot": "2.1.3", - "@vitest/spy": "2.1.3", - "@vitest/utils": "2.1.3", - "chai": "^5.1.1", - "debug": "^4.3.6", - "magic-string": "^0.30.11", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.4.tgz", + "integrity": "sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==", + "dev": true, + "requires": { + "@vitest/expect": "2.1.4", + "@vitest/mocker": "2.1.4", + "@vitest/pretty-format": "^2.1.4", + "@vitest/runner": "2.1.4", + "@vitest/snapshot": "2.1.4", + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.7.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.0", - "tinypool": "^1.0.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.1.3", + "vite-node": "2.1.4", "why-is-node-running": "^2.3.0" }, "dependencies": { diff --git a/server/package.json b/server/package.json index 39a3f78d0e1fa..a54212052a346 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "immich", - "version": "1.118.2", + "version": "1.120.2", "description": "", "author": "", "private": true, @@ -24,10 +24,10 @@ "typeorm": "typeorm", "lifecycle": "node ./dist/utils/lifecycle.js", "typeorm:migrations:create": "typeorm migration:create", - "typeorm:migrations:generate": "typeorm migration:generate -d ./dist/database.config.js", - "typeorm:migrations:run": "typeorm migration:run -d ./dist/database.config.js", - "typeorm:migrations:revert": "typeorm migration:revert -d ./dist/database.config.js", - "typeorm:schema:drop": "typeorm query -d ./dist/database.config.js 'DROP schema public cascade; CREATE schema public;'", + "typeorm:migrations:generate": "typeorm migration:generate -d ./dist/bin/database.js", + "typeorm:migrations:run": "typeorm migration:run -d ./dist/bin/database.js", + "typeorm:migrations:revert": "typeorm migration:revert -d ./dist/bin/database.js", + "typeorm:schema:drop": "typeorm query -d ./dist/bin/database.js 'DROP schema public cascade; CREATE schema public;'", "typeorm:schema:reset": "npm run typeorm:schema:drop && npm run typeorm:migrations:run", "sync:open-api": "node ./dist/bin/sync-open-api.js", "sync:sql": "node ./dist/bin/sync-sql.js", @@ -36,7 +36,6 @@ "dependencies": { "@nestjs/bullmq": "^10.0.1", "@nestjs/common": "^10.2.2", - "@nestjs/config": "^3.0.0", "@nestjs/core": "^10.2.2", "@nestjs/event-emitter": "^2.0.4", "@nestjs/platform-express": "^10.2.2", @@ -45,10 +44,10 @@ "@nestjs/swagger": "^7.1.8", "@nestjs/typeorm": "^10.0.0", "@nestjs/websockets": "^10.2.2", - "@opentelemetry/auto-instrumentations-node": "^0.51.0", + "@opentelemetry/auto-instrumentations-node": "^0.52.0", "@opentelemetry/context-async-hooks": "^1.24.0", - "@opentelemetry/exporter-prometheus": "^0.53.0", - "@opentelemetry/sdk-node": "^0.53.0", + "@opentelemetry/exporter-prometheus": "^0.54.0", + "@opentelemetry/sdk-node": "^0.54.0", "@react-email/components": "^0.0.25", "@socket.io/redis-adapter": "^8.3.0", "archiver": "^7.0.0", @@ -88,7 +87,8 @@ "tailwindcss-preset-email": "^1.3.2", "thumbhash": "^0.1.1", "typeorm": "^0.3.17", - "ua-parser-js": "^1.0.35" + "ua-parser-js": "^1.0.35", + "validator": "^13.12.0" }, "devDependencies": { "@eslint/eslintrc": "^3.1.0", @@ -108,7 +108,7 @@ "@types/lodash": "^4.14.197", "@types/mock-fs": "^4.13.1", "@types/multer": "^1.4.7", - "@types/node": "^20.16.12", + "@types/node": "^22.9.0", "@types/nodemailer": "^6.4.14", "@types/picomatch": "^3.0.0", "@types/pngjs": "^6.0.5", @@ -139,6 +139,6 @@ "vitest": "^2.0.5" }, "volta": { - "node": "20.18.0" + "node": "22.11.0" } } diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 3c26faaca325c..da8fa55606099 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -1,18 +1,17 @@ import { BullModule } from '@nestjs/bullmq'; import { Inject, Module, OnModuleDestroy, OnModuleInit, ValidationPipe } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE, ModuleRef } from '@nestjs/core'; import { ScheduleModule, SchedulerRegistry } from '@nestjs/schedule'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ClsModule } from 'nestjs-cls'; import { OpenTelemetryModule } from 'nestjs-otel'; import { commands } from 'src/commands'; -import { clsConfig, immichAppConfig } from 'src/config'; +import { IWorker } from 'src/constants'; import { controllers } from 'src/controllers'; -import { databaseConfig } from 'src/database.config'; import { entities } from 'src/entities'; import { ImmichWorker } from 'src/enum'; import { IEventRepository } from 'src/interfaces/event.interface'; +import { IJobRepository } from 'src/interfaces/job.interface'; import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ITelemetryRepository } from 'src/interfaces/telemetry.interface'; import { AuthGuard } from 'src/middleware/auth.guard'; @@ -38,19 +37,18 @@ const middleware = [ ]; const configRepository = new ConfigRepository(); -const { bull, otel } = configRepository.getEnv(); +const { bull, cls, database, otel } = configRepository.getEnv(); const imports = [ BullModule.forRoot(bull.config), BullModule.registerQueue(...bull.queues), - ClsModule.forRoot(clsConfig), - ConfigModule.forRoot(immichAppConfig), + ClsModule.forRoot(cls.config), OpenTelemetryModule.forRoot(otel), TypeOrmModule.forRootAsync({ inject: [ModuleRef], useFactory: (moduleRef: ModuleRef) => { return { - ...databaseConfig, + ...database.config, poolErrorHandler: (error) => { moduleRef.get(DatabaseService, { strict: false }).handleConnectionError(error); }, @@ -60,29 +58,31 @@ const imports = [ TypeOrmModule.forFeature(entities), ]; -abstract class BaseModule implements OnModuleInit, OnModuleDestroy { - private get worker() { - return this.getWorker(); - } - +class BaseModule implements OnModuleInit, OnModuleDestroy { constructor( + @Inject(IWorker) private worker: ImmichWorker, @Inject(ILoggerRepository) logger: ILoggerRepository, @Inject(IEventRepository) private eventRepository: IEventRepository, + @Inject(IJobRepository) private jobRepository: IJobRepository, @Inject(ITelemetryRepository) private telemetryRepository: ITelemetryRepository, ) { logger.setAppName(this.worker); } - abstract getWorker(): ImmichWorker; - async onModuleInit() { this.telemetryRepository.setup({ repositories: repositories.map(({ useClass }) => useClass) }); + + this.jobRepository.setup({ services }); + if (this.worker === ImmichWorker.MICROSERVICES) { + this.jobRepository.startWorkers(); + } + this.eventRepository.setup({ services }); - await this.eventRepository.emit('app.bootstrap', this.worker); + await this.eventRepository.emit('app.bootstrap'); } async onModuleDestroy() { - await this.eventRepository.emit('app.shutdown', this.worker); + await this.eventRepository.emit('app.shutdown'); await teardownTelemetry(); } } @@ -90,23 +90,15 @@ abstract class BaseModule implements OnModuleInit, OnModuleDestroy { @Module({ imports: [...imports, ScheduleModule.forRoot()], controllers: [...controllers], - providers: [...common, ...middleware], + providers: [...common, ...middleware, { provide: IWorker, useValue: ImmichWorker.API }], }) -export class ApiModule extends BaseModule { - getWorker() { - return ImmichWorker.API; - } -} +export class ApiModule extends BaseModule {} @Module({ imports: [...imports], - providers: [...common, SchedulerRegistry], + providers: [...common, { provide: IWorker, useValue: ImmichWorker.MICROSERVICES }, SchedulerRegistry], }) -export class MicroservicesModule extends BaseModule { - getWorker() { - return ImmichWorker.MICROSERVICES; - } -} +export class MicroservicesModule extends BaseModule {} @Module({ imports: [...imports], diff --git a/server/src/bin/database.ts b/server/src/bin/database.ts new file mode 100644 index 0000000000000..c861902b4e589 --- /dev/null +++ b/server/src/bin/database.ts @@ -0,0 +1,11 @@ +import { ConfigRepository } from 'src/repositories/config.repository'; +import { DataSource } from 'typeorm'; + +const { database } = new ConfigRepository().getEnv(); + +/** + * @deprecated - DO NOT USE THIS + * + * this export is ONLY to be used for TypeORM commands in package.json#scripts + */ +export const dataSource = new DataSource({ ...database.config, host: 'localhost' }); diff --git a/server/src/bin/healthcheck.ts b/server/src/bin/healthcheck.ts index 6de58c2002fef..2613ee6a19a21 100644 --- a/server/src/bin/healthcheck.ts +++ b/server/src/bin/healthcheck.ts @@ -3,7 +3,7 @@ import { ImmichWorker } from 'src/enum'; import { ConfigRepository } from 'src/repositories/config.repository'; const main = async () => { - const { workers, port } = new ConfigRepository().getEnv(); + const { host, workers, port } = new ConfigRepository().getEnv(); if (!workers.includes(ImmichWorker.API)) { process.exit(); } @@ -11,7 +11,7 @@ const main = async () => { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 2000); try { - const response = await fetch(`http://localhost:${port}/api/server/ping`, { + const response = await fetch(`http://${host || 'localhost'}:${port}/api/server/ping`, { signal: controller.signal, }); diff --git a/server/src/bin/sync-sql.ts b/server/src/bin/sync-sql.ts index e4f11cc6928a9..98f26d879a6f0 100644 --- a/server/src/bin/sync-sql.ts +++ b/server/src/bin/sync-sql.ts @@ -8,7 +8,6 @@ import { OpenTelemetryModule } from 'nestjs-otel'; import { mkdir, rm, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { format } from 'sql-formatter'; -import { databaseConfig } from 'src/database.config'; import { GENERATE_SQL_KEY, GenerateSqlQueries } from 'src/decorators'; import { entities } from 'src/entities'; import { ILoggerRepository } from 'src/interfaces/logger.interface'; @@ -74,12 +73,12 @@ class SqlGenerator { await rm(this.options.targetDir, { force: true, recursive: true }); await mkdir(this.options.targetDir); - const { otel } = new ConfigRepository().getEnv(); + const { database, otel } = new ConfigRepository().getEnv(); const moduleFixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ - ...databaseConfig, + ...database.config, host: 'localhost', entities, logging: ['query'], diff --git a/server/src/config.ts b/server/src/config.ts index fca6719bc0032..2b74f00e7a9ef 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -1,15 +1,9 @@ -import { ConfigModuleOptions } from '@nestjs/config'; import { CronExpression } from '@nestjs/schedule'; -import { Request, Response } from 'express'; -import Joi, { Root } from 'joi'; -import { CLS_ID, ClsModuleOptions } from 'nestjs-cls'; import { AudioCodec, Colorspace, CQMode, ImageFormat, - ImmichEnvironment, - ImmichHeader, LogLevel, ToneMapping, TranscodeHWAccel, @@ -21,6 +15,13 @@ import { ConcurrentQueueName, QueueName } from 'src/interfaces/job.interface'; import { ImageOptions } from 'src/interfaces/media.interface'; export interface SystemConfig { + backup: { + database: { + enabled: boolean; + cronExpression: string; + keepLastAmount: number; + }; + }; ffmpeg: { crf: number; threads: number; @@ -35,7 +36,6 @@ export interface SystemConfig { bframes: number; refs: number; gopSize: number; - npl: number; temporalAQ: boolean; cqMode: CQMode; twoPass: boolean; @@ -156,6 +156,13 @@ export interface SystemConfig { } export const defaults = Object.freeze({ + backup: { + database: { + enabled: true, + cronExpression: CronExpression.EVERY_DAY_AT_2AM, + keepLastAmount: 14, + }, + }, ffmpeg: { crf: 23, threads: 0, @@ -170,7 +177,6 @@ export const defaults = Object.freeze({ bframes: -1, refs: 0, gopSize: 0, - npl: 0, temporalAQ: false, cqMode: CQMode.AUTO, twoPass: false, @@ -309,67 +315,3 @@ export const defaults = Object.freeze({ deleteDelay: 7, }, }); - -const WHEN_DB_URL_SET = Joi.when('DB_URL', { - is: Joi.exist(), - then: Joi.string().optional(), - otherwise: Joi.string().required(), -}); - -export const immichAppConfig: ConfigModuleOptions = { - envFilePath: '.env', - isGlobal: true, - validationSchema: Joi.object({ - IMMICH_ENV: Joi.string() - .optional() - .valid(...Object.values(ImmichEnvironment)) - .default(ImmichEnvironment.PRODUCTION), - IMMICH_LOG_LEVEL: Joi.string() - .optional() - .valid(...Object.values(LogLevel)), - - DB_USERNAME: WHEN_DB_URL_SET, - DB_PASSWORD: WHEN_DB_URL_SET, - DB_DATABASE_NAME: WHEN_DB_URL_SET, - DB_URL: Joi.string().optional(), - DB_VECTOR_EXTENSION: Joi.string().optional().valid('pgvector', 'pgvecto.rs').default('pgvecto.rs'), - DB_SKIP_MIGRATIONS: Joi.boolean().optional().default(false), - - IMMICH_PORT: Joi.number().optional(), - IMMICH_API_METRICS_PORT: Joi.number().optional(), - IMMICH_MICROSERVICES_METRICS_PORT: Joi.number().optional(), - - IMMICH_TRUSTED_PROXIES: Joi.extend((joi: Root) => ({ - type: 'stringArray', - base: joi.array(), - coerce: (value) => (value.split ? value.split(',') : value), - })) - .stringArray() - .single() - .items( - Joi.string().ip({ - version: ['ipv4', 'ipv6'], - cidr: 'optional', - }), - ), - - IMMICH_METRICS: Joi.boolean().optional().default(false), - IMMICH_HOST_METRICS: Joi.boolean().optional(), - IMMICH_API_METRICS: Joi.boolean().optional(), - IMMICH_IO_METRICS: Joi.boolean().optional(), - }), -}; - -export const clsConfig: ClsModuleOptions = { - middleware: { - mount: true, - generateId: true, - setup: (cls, req: Request, res: Response) => { - const headerValues = req.headers[ImmichHeader.CID]; - const headerValue = Array.isArray(headerValues) ? headerValues[0] : headerValues; - const cid = headerValue || cls.get(CLS_ID); - cls.set(CLS_ID, cid); - res.header(ImmichHeader.CID, cid); - }, - }, -}; diff --git a/server/src/constants.ts b/server/src/constants.ts index e99970723a42f..fc2442130e002 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -1,6 +1,7 @@ import { Duration } from 'luxon'; import { readFileSync } from 'node:fs'; import { SemVer } from 'semver'; +import { ExifOrientation } from 'src/enum'; export const POSTGRES_VERSION_RANGE = '>=14.0.0'; export const VECTORS_VERSION_RANGE = '>=0.2 <0.4'; @@ -13,6 +14,8 @@ export const ADDED_IN_PREFIX = 'This property was added in '; export const SALT_ROUNDS = 10; +export const IWorker = 'IWorker'; + const { version } = JSON.parse(readFileSync('./package.json', 'utf8')); export const serverVersion = new SemVer(version); @@ -79,3 +82,19 @@ export const CLIP_MODEL_INFO: Record = { 'nllb-clip-large-siglip__mrl': { dimSize: 1152 }, 'nllb-clip-large-siglip__v1': { dimSize: 1152 }, }; + +type SharpRotationData = { + angle?: number; + flip?: boolean; + flop?: boolean; +}; +export const ORIENTATION_TO_SHARP_ROTATION: Record = { + [ExifOrientation.Horizontal]: { angle: 0 }, + [ExifOrientation.MirrorHorizontal]: { angle: 0, flop: true }, + [ExifOrientation.Rotate180]: { angle: 180 }, + [ExifOrientation.MirrorVertical]: { angle: 180, flop: true }, + [ExifOrientation.MirrorHorizontalRotate270CW]: { angle: 270, flip: true }, + [ExifOrientation.Rotate90CW]: { angle: 90 }, + [ExifOrientation.MirrorHorizontalRotate90CW]: { angle: 90, flip: true }, + [ExifOrientation.Rotate270CW]: { angle: 270 }, +} as const; diff --git a/server/src/database.config.ts b/server/src/database.config.ts deleted file mode 100644 index 2a46067bc1ce1..0000000000000 --- a/server/src/database.config.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ConfigRepository } from 'src/repositories/config.repository'; -import { DataSource } from 'typeorm'; -import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js'; - -const { database } = new ConfigRepository().getEnv(); -const { url, host, port, username, password, name } = database; -const urlOrParts = url - ? { url } - : { - host, - port, - username, - password, - database: name, - }; - -/* eslint unicorn/prefer-module: "off" -- We can fix this when migrating to ESM*/ -export const databaseConfig: PostgresConnectionOptions = { - type: 'postgres', - entities: [__dirname + '/entities/*.entity.{js,ts}'], - migrations: [__dirname + '/migrations/*.{js,ts}'], - subscribers: [__dirname + '/subscribers/*.{js,ts}'], - migrationsRun: false, - synchronize: false, - connectTimeoutMS: 10_000, // 10 seconds - parseInt8: true, - ...urlOrParts, -}; - -/** - * @deprecated - DO NOT USE THIS - * - * this export is ONLY to be used for TypeORM commands in package.json#scripts - */ -export const dataSource = new DataSource({ ...databaseConfig, host: 'localhost' }); diff --git a/server/src/decorators.ts b/server/src/decorators.ts index db755c5ff9c56..be379bf64e36e 100644 --- a/server/src/decorators.ts +++ b/server/src/decorators.ts @@ -4,6 +4,7 @@ import _ from 'lodash'; import { ADDED_IN_PREFIX, DEPRECATED_IN_PREFIX, LIFECYCLE_EXTENSION } from 'src/constants'; import { MetadataKey } from 'src/enum'; import { EmitEvent } from 'src/interfaces/event.interface'; +import { JobName, QueueName } from 'src/interfaces/job.interface'; import { setUnion } from 'src/utils/set'; // PostgreSQL uses a 16-bit integer to indicate the number of bound parameters. This means that the @@ -122,6 +123,12 @@ export type EventConfig = { }; export const OnEvent = (config: EventConfig) => SetMetadata(MetadataKey.EVENT_CONFIG, config); +export type JobConfig = { + name: JobName; + queue: QueueName; +}; +export const OnJob = (config: JobConfig) => SetMetadata(MetadataKey.JOB_CONFIG, config); + type LifecycleRelease = 'NEXT_RELEASE' | string; type LifecycleMetadata = { addedAt?: LifecycleRelease; diff --git a/server/src/dtos/album.dto.ts b/server/src/dtos/album.dto.ts index b12847ee62537..76f4fdfc98f4a 100644 --- a/server/src/dtos/album.dto.ts +++ b/server/src/dtos/album.dto.ts @@ -7,6 +7,7 @@ import { AuthDto } from 'src/dtos/auth.dto'; import { UserResponseDto, mapUser } from 'src/dtos/user.dto'; import { AlbumEntity } from 'src/entities/album.entity'; import { AlbumUserRole, AssetOrder } from 'src/enum'; +import { getAssetDateTime } from 'src/utils/date-time'; import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation'; export class AlbumInfoDto { @@ -164,8 +165,8 @@ export const mapAlbum = (entity: AlbumEntity, withAssets: boolean, auth?: AuthDt const hasSharedLink = entity.sharedLinks?.length > 0; const hasSharedUser = sharedUsers.length > 0; - let startDate = assets.at(0)?.fileCreatedAt || undefined; - let endDate = assets.at(-1)?.fileCreatedAt || undefined; + let startDate = getAssetDateTime(assets.at(0)); + let endDate = getAssetDateTime(assets.at(-1)); // Swap dates if start date is greater than end date. if (startDate && endDate && startDate > endDate) { [startDate, endDate] = [endDate, startDate]; diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts index ed92208182ee5..a255ac103b10e 100644 --- a/server/src/dtos/asset-response.dto.ts +++ b/server/src/dtos/asset-response.dto.ts @@ -12,7 +12,6 @@ import { TagResponseDto, mapTag } from 'src/dtos/tag.dto'; import { UserResponseDto, mapUser } from 'src/dtos/user.dto'; import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { AssetEntity } from 'src/entities/asset.entity'; -import { SmartInfoEntity } from 'src/entities/smart-info.entity'; import { AssetType } from 'src/enum'; import { mimeTypes } from 'src/utils/mime-types'; @@ -45,7 +44,6 @@ export class AssetResponseDto extends SanitizedAssetResponseDto { isTrashed!: boolean; isOffline!: boolean; exifInfo?: ExifResponseDto; - smartInfo?: SmartInfoResponseDto; tags?: TagResponseDto[]; people?: PersonWithFacesResponseDto[]; unassignedFaces?: AssetFaceWithoutPersonResponseDto[]; @@ -141,7 +139,6 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As isTrashed: !!entity.deletedAt, duration: entity.duration ?? '0:00:00.00000', exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined, - smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined, livePhotoVideoId: entity.livePhotoVideoId, tags: entity.tags?.map((tag) => mapTag(tag)), people: peopleWithFaces(entity.faces), @@ -161,15 +158,3 @@ export class MemoryLaneResponseDto { assets!: AssetResponseDto[]; } - -export class SmartInfoResponseDto { - tags?: string[] | null; - objects?: string[] | null; -} - -export function mapSmartInfo(entity: SmartInfoEntity): SmartInfoResponseDto { - return { - tags: entity.tags, - objects: entity.objects, - }; -} diff --git a/server/src/dtos/env.dto.ts b/server/src/dtos/env.dto.ts new file mode 100644 index 0000000000000..6c238252a633a --- /dev/null +++ b/server/src/dtos/env.dto.ts @@ -0,0 +1,190 @@ +import { Transform, Type } from 'class-transformer'; +import { IsEnum, IsInt, IsString } from 'class-validator'; +import { ImmichEnvironment, LogLevel } from 'src/enum'; +import { IsIPRange, Optional, ValidateBoolean } from 'src/validation'; + +export class EnvDto { + @IsInt() + @Optional() + @Type(() => Number) + IMMICH_API_METRICS_PORT?: number; + + @IsString() + @Optional() + IMMICH_BUILD_DATA?: string; + + @IsString() + @Optional() + IMMICH_BUILD?: string; + + @IsString() + @Optional() + IMMICH_BUILD_URL?: string; + + @IsString() + @Optional() + IMMICH_BUILD_IMAGE?: string; + + @IsString() + @Optional() + IMMICH_BUILD_IMAGE_URL?: string; + + @IsString() + @Optional() + IMMICH_CONFIG_FILE?: string; + + @IsEnum(ImmichEnvironment) + @Optional() + IMMICH_ENV?: ImmichEnvironment; + + @IsString() + @Optional() + IMMICH_HOST?: string; + + @ValidateBoolean({ optional: true }) + IMMICH_IGNORE_MOUNT_CHECK_ERRORS?: boolean; + + @IsEnum(LogLevel) + @Optional() + IMMICH_LOG_LEVEL?: LogLevel; + + @IsInt() + @Optional() + @Type(() => Number) + IMMICH_MICROSERVICES_METRICS_PORT?: number; + + @IsInt() + @Optional() + @Type(() => Number) + IMMICH_PORT?: number; + + @IsString() + @Optional() + IMMICH_REPOSITORY?: string; + + @IsString() + @Optional() + IMMICH_REPOSITORY_URL?: string; + + @IsString() + @Optional() + IMMICH_SOURCE_REF?: string; + + @IsString() + @Optional() + IMMICH_SOURCE_COMMIT?: string; + + @IsString() + @Optional() + IMMICH_SOURCE_URL?: string; + + @IsString() + @Optional() + IMMICH_TELEMETRY_INCLUDE?: string; + + @IsString() + @Optional() + IMMICH_TELEMETRY_EXCLUDE?: string; + + @IsString() + @Optional() + IMMICH_THIRD_PARTY_SOURCE_URL?: string; + + @IsString() + @Optional() + IMMICH_THIRD_PARTY_BUG_FEATURE_URL?: string; + + @IsString() + @Optional() + IMMICH_THIRD_PARTY_DOCUMENTATION_URL?: string; + + @IsString() + @Optional() + IMMICH_THIRD_PARTY_SUPPORT_URL?: string; + + @IsIPRange({ requireCIDR: false }, { each: true }) + @Transform(({ value }) => + value && typeof value === 'string' + ? value + .split(',') + .map((value) => value.trim()) + .filter(Boolean) + : value, + ) + @Optional() + IMMICH_TRUSTED_PROXIES?: string[]; + + @IsString() + @Optional() + IMMICH_WORKERS_INCLUDE?: string; + + @IsString() + @Optional() + IMMICH_WORKERS_EXCLUDE?: string; + + @IsString() + @Optional() + DB_DATABASE_NAME?: string; + + @IsString() + @Optional() + DB_HOSTNAME?: string; + + @IsString() + @Optional() + DB_PASSWORD?: string; + + @IsInt() + @Optional() + @Type(() => Number) + DB_PORT?: number; + + @ValidateBoolean({ optional: true }) + DB_SKIP_MIGRATIONS?: boolean; + + @IsString() + @Optional() + DB_URL?: string; + + @IsString() + @Optional() + DB_USERNAME?: string; + + @IsEnum(['pgvector', 'pgvecto.rs']) + @Optional() + DB_VECTOR_EXTENSION?: 'pgvector' | 'pgvecto.rs'; + + @IsString() + @Optional() + NO_COLOR?: string; + + @IsString() + @Optional() + REDIS_HOSTNAME?: string; + + @IsInt() + @Optional() + @Type(() => Number) + REDIS_PORT?: number; + + @IsInt() + @Optional() + @Type(() => Number) + REDIS_DBINDEX?: number; + + @IsString() + @Optional() + REDIS_USERNAME?: string; + + @IsString() + @Optional() + REDIS_PASSWORD?: string; + + @IsString() + @Optional() + REDIS_SOCKET?: string; + + @IsString() + @Optional() + REDIS_URL?: string; +} diff --git a/server/src/dtos/job.dto.ts b/server/src/dtos/job.dto.ts index 49e4cfb67b3c8..31612bd8a4fb7 100644 --- a/server/src/dtos/job.dto.ts +++ b/server/src/dtos/job.dto.ts @@ -97,4 +97,7 @@ export class AllJobStatusResponseDto implements Record @ApiProperty({ type: JobStatusDto }) [QueueName.NOTIFICATION]!: JobStatusDto; + + @ApiProperty({ type: JobStatusDto }) + [QueueName.BACKUP_DATABASE]!: JobStatusDto; } diff --git a/server/src/dtos/server.dto.ts b/server/src/dtos/server.dto.ts index e54048335129f..cbabfa7aed661 100644 --- a/server/src/dtos/server.dto.ts +++ b/server/src/dtos/server.dto.ts @@ -86,6 +86,10 @@ export class UsageByUserDto { @ApiProperty({ type: 'integer', format: 'int64' }) usage!: number; @ApiProperty({ type: 'integer', format: 'int64' }) + usagePhotos!: number; + @ApiProperty({ type: 'integer', format: 'int64' }) + usageVideos!: number; + @ApiProperty({ type: 'integer', format: 'int64' }) quotaSizeInBytes!: number | null; } @@ -99,6 +103,12 @@ export class ServerStatsResponseDto { @ApiProperty({ type: 'integer', format: 'int64' }) usage = 0; + @ApiProperty({ type: 'integer', format: 'int64' }) + usagePhotos = 0; + + @ApiProperty({ type: 'integer', format: 'int64' }) + usageVideos = 0; + @ApiProperty({ isArray: true, type: UsageByUserDto, @@ -107,7 +117,9 @@ export class ServerStatsResponseDto { { photos: 1, videos: 1, - diskUsageRaw: 1, + diskUsageRaw: 2, + usagePhotos: 1, + usageVideos: 1, }, ], }) diff --git a/server/src/dtos/system-config.dto.ts b/server/src/dtos/system-config.dto.ts index 039dbd20ff36a..7a2e0632b4631 100644 --- a/server/src/dtos/system-config.dto.ts +++ b/server/src/dtos/system-config.dto.ts @@ -12,11 +12,8 @@ import { IsUrl, Max, Min, - Validate, ValidateIf, ValidateNested, - ValidatorConstraint, - ValidatorConstraintInterface, } from 'class-validator'; import { SystemConfig } from 'src/config'; import { CLIPConfig, DuplicateDetectionConfig, FacialRecognitionConfig } from 'src/dtos/model-config.dto'; @@ -33,19 +30,36 @@ import { VideoContainer, } from 'src/enum'; import { ConcurrentQueueName, QueueName } from 'src/interfaces/job.interface'; -import { ValidateBoolean, validateCronExpression } from 'src/validation'; - -@ValidatorConstraint({ name: 'cronValidator' }) -class CronValidator implements ValidatorConstraintInterface { - validate(expression: string): boolean { - return validateCronExpression(expression); - } -} +import { IsCronExpression, ValidateBoolean } from 'src/validation'; const isLibraryScanEnabled = (config: SystemConfigLibraryScanDto) => config.enabled; const isOAuthEnabled = (config: SystemConfigOAuthDto) => config.enabled; const isOAuthOverrideEnabled = (config: SystemConfigOAuthDto) => config.mobileOverrideEnabled; const isEmailNotificationEnabled = (config: SystemConfigSmtpDto) => config.enabled; +const isDatabaseBackupEnabled = (config: DatabaseBackupConfig) => config.enabled; + +export class DatabaseBackupConfig { + @ValidateBoolean() + enabled!: boolean; + + @ValidateIf(isDatabaseBackupEnabled) + @IsNotEmpty() + @IsCronExpression() + @IsString() + cronExpression!: string; + + @IsInt() + @IsPositive() + @IsNotEmpty() + keepLastAmount!: number; +} + +export class SystemConfigBackupsDto { + @Type(() => DatabaseBackupConfig) + @ValidateNested() + @IsObject() + database!: DatabaseBackupConfig; +} export class SystemConfigFFmpegDto { @IsInt() @@ -110,12 +124,6 @@ export class SystemConfigFFmpegDto { @ApiProperty({ type: 'integer' }) gopSize!: number; - @IsInt() - @Min(0) - @Type(() => Number) - @ApiProperty({ type: 'integer' }) - npl!: number; - @ValidateBoolean() temporalAQ!: boolean; @@ -226,7 +234,7 @@ class SystemConfigLibraryScanDto { @ValidateIf(isLibraryScanEnabled) @IsNotEmpty() - @Validate(CronValidator, { message: 'Invalid cron expression' }) + @IsCronExpression() @IsString() cronExpression!: string; } @@ -531,6 +539,11 @@ class SystemConfigUserDto { } export class SystemConfigDto implements SystemConfig { + @Type(() => SystemConfigBackupsDto) + @ValidateNested() + @IsObject() + backup!: SystemConfigBackupsDto; + @Type(() => SystemConfigFFmpegDto) @ValidateNested() @IsObject() diff --git a/server/src/entities/album.entity.ts b/server/src/entities/album.entity.ts index e5d2c9881496b..5aec5a0f47dd0 100644 --- a/server/src/entities/album.entity.ts +++ b/server/src/entities/album.entity.ts @@ -52,7 +52,7 @@ export class AlbumEntity { albumUsers!: AlbumUserEntity[]; @ManyToMany(() => AssetEntity, (asset) => asset.albums) - @JoinTable() + @JoinTable({ synchronize: false }) assets!: AssetEntity[]; @OneToMany(() => SharedLinkEntity, (link) => link.album) diff --git a/server/src/entities/asset.entity.ts b/server/src/entities/asset.entity.ts index 0b893134d0e0d..f9e5c5e9813d2 100644 --- a/server/src/entities/asset.entity.ts +++ b/server/src/entities/asset.entity.ts @@ -5,7 +5,6 @@ import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity'; import { ExifEntity } from 'src/entities/exif.entity'; import { LibraryEntity } from 'src/entities/library.entity'; import { SharedLinkEntity } from 'src/entities/shared-link.entity'; -import { SmartInfoEntity } from 'src/entities/smart-info.entity'; import { SmartSearchEntity } from 'src/entities/smart-search.entity'; import { StackEntity } from 'src/entities/stack.entity'; import { TagEntity } from 'src/entities/tag.entity'; @@ -143,9 +142,6 @@ export class AssetEntity { @OneToOne(() => ExifEntity, (exifEntity) => exifEntity.asset) exifInfo?: ExifEntity; - @OneToOne(() => SmartInfoEntity, (smartInfoEntity) => smartInfoEntity.asset) - smartInfo?: SmartInfoEntity; - @OneToOne(() => SmartSearchEntity, (smartSearchEntity) => smartSearchEntity.asset) smartSearch?: SmartSearchEntity; diff --git a/server/src/entities/index.ts b/server/src/entities/index.ts index 7425ee67d8a6e..75e92038acb2b 100644 --- a/server/src/entities/index.ts +++ b/server/src/entities/index.ts @@ -18,7 +18,6 @@ import { PartnerEntity } from 'src/entities/partner.entity'; import { PersonEntity } from 'src/entities/person.entity'; import { SessionEntity } from 'src/entities/session.entity'; import { SharedLinkEntity } from 'src/entities/shared-link.entity'; -import { SmartInfoEntity } from 'src/entities/smart-info.entity'; import { SmartSearchEntity } from 'src/entities/smart-search.entity'; import { StackEntity } from 'src/entities/stack.entity'; import { SystemMetadataEntity } from 'src/entities/system-metadata.entity'; @@ -46,7 +45,6 @@ export const entities = [ PartnerEntity, PersonEntity, SharedLinkEntity, - SmartInfoEntity, SmartSearchEntity, StackEntity, SystemMetadataEntity, diff --git a/server/src/entities/smart-info.entity.ts b/server/src/entities/smart-info.entity.ts deleted file mode 100644 index 86190c174d170..0000000000000 --- a/server/src/entities/smart-info.entity.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { AssetEntity } from 'src/entities/asset.entity'; -import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm'; - -@Entity('smart_info', { synchronize: false }) -export class SmartInfoEntity { - @OneToOne(() => AssetEntity, { onDelete: 'CASCADE', nullable: true }) - @JoinColumn({ name: 'assetId', referencedColumnName: 'id' }) - asset?: AssetEntity; - - @PrimaryColumn() - assetId!: string; - - @Column({ type: 'text', array: true, nullable: true }) - tags!: string[] | null; - - @Column({ type: 'text', array: true, nullable: true }) - objects!: string[] | null; -} diff --git a/server/src/entities/system-metadata.entity.ts b/server/src/entities/system-metadata.entity.ts index 0a238e1da5f6b..0a03a554039f3 100644 --- a/server/src/entities/system-metadata.entity.ts +++ b/server/src/entities/system-metadata.entity.ts @@ -1,5 +1,5 @@ import { SystemConfig } from 'src/config'; -import { SystemMetadataKey } from 'src/enum'; +import { StorageFolder, SystemMetadataKey } from 'src/enum'; import { Column, DeepPartial, Entity, PrimaryColumn } from 'typeorm'; @Entity('system_metadata') @@ -12,7 +12,7 @@ export class SystemMetadataEntity }; export interface SystemMetadata extends Record> { [SystemMetadataKey.ADMIN_ONBOARDING]: { isOnboarded: boolean }; @@ -20,6 +20,6 @@ export interface SystemMetadata extends Record; - [SystemMetadataKey.SYSTEM_FLAGS]: SystemFlags; + [SystemMetadataKey.SYSTEM_FLAGS]: DeepPartial; [SystemMetadataKey.VERSION_CHECK_STATE]: VersionCheckMetadata; } diff --git a/server/src/enum.ts b/server/src/enum.ts index 902d6635e7f52..3440d45cee6d2 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -181,6 +181,7 @@ export enum StorageFolder { UPLOAD = 'upload', PROFILE = 'profile', THUMBNAILS = 'thumbs', + BACKUPS = 'backups', } export enum SystemMetadataKey { @@ -334,6 +335,7 @@ export enum MetadataKey { SHARED_ROUTE = 'shared_route', API_KEY_SECURITY = 'api_key', EVENT_CONFIG = 'event_config', + JOB_CONFIG = 'job_config', TELEMETRY_ENABLED = 'telemetry_enabled', } @@ -363,3 +365,22 @@ export enum ImmichWorker { API = 'api', MICROSERVICES = 'microservices', } + +export enum ImmichTelemetry { + HOST = 'host', + API = 'api', + IO = 'io', + REPO = 'repo', + JOB = 'job', +} + +export enum ExifOrientation { + Horizontal = 1, + MirrorHorizontal = 2, + Rotate180 = 3, + MirrorVertical = 4, + MirrorHorizontalRotate270CW = 5, + Rotate90CW = 6, + MirrorHorizontalRotate90CW = 7, + Rotate270CW = 8, +} diff --git a/server/src/interfaces/asset.interface.ts b/server/src/interfaces/asset.interface.ts index 37d3326a8abc5..1b32c57d4141b 100644 --- a/server/src/interfaces/asset.interface.ts +++ b/server/src/interfaces/asset.interface.ts @@ -28,9 +28,7 @@ export enum WithoutProperty { EXIF = 'exif', SMART_SEARCH = 'smart-search', DUPLICATE = 'duplicate', - OBJECT_TAGS = 'object-tags', FACES = 'faces', - PERSON = 'person', SIDECAR = 'sidecar', } @@ -94,7 +92,6 @@ export type AssetWithoutRelations = Omit< | 'library' | 'exifInfo' | 'sharedLinks' - | 'smartInfo' | 'smartSearch' | 'tags' >; @@ -190,7 +187,6 @@ export interface IAssetRepository { upsertExif(exif: Partial): Promise; upsertJobStatus(...jobStatus: Partial[]): Promise; getAssetIdByCity(userId: string, options: AssetExploreFieldOptions): Promise>; - getAssetIdByTag(userId: string, options: AssetExploreFieldOptions): Promise>; getDuplicates(options: AssetBuilderOptions): Promise; getAllForUserFullSync(options: AssetFullSyncOptions): Promise; getChangedDeltaSync(options: AssetDeltaSyncOptions): Promise; diff --git a/server/src/interfaces/config.interface.ts b/server/src/interfaces/config.interface.ts index 4391909df7131..300b55f27b6fd 100644 --- a/server/src/interfaces/config.interface.ts +++ b/server/src/interfaces/config.interface.ts @@ -1,9 +1,11 @@ import { RegisterQueueOptions } from '@nestjs/bullmq'; import { QueueOptions } from 'bullmq'; import { RedisOptions } from 'ioredis'; +import { ClsModuleOptions } from 'nestjs-cls'; import { OpenTelemetryModuleOptions } from 'nestjs-otel/lib/interfaces'; -import { ImmichEnvironment, ImmichWorker, LogLevel } from 'src/enum'; -import { VectorExtension } from 'src/interfaces/database.interface'; +import { ImmichEnvironment, ImmichTelemetry, ImmichWorker, LogLevel } from 'src/enum'; +import { DatabaseConnectionParams, VectorExtension } from 'src/interfaces/database.interface'; +import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js'; export const IConfigRepository = 'IConfigRepository'; @@ -35,13 +37,12 @@ export interface EnvData { queues: RegisterQueueOptions[]; }; + cls: { + config: ClsModuleOptions; + }; + database: { - url?: string; - host: string; - port: number; - username: string; - password: string; - name: string; + config: PostgresConnectionOptions & DatabaseConnectionParams; skipMigrations: boolean; vectorExtension: VectorExtension; }; @@ -77,11 +78,7 @@ export interface EnvData { telemetry: { apiPort: number; microservicesPort: number; - enabled: boolean; - apiMetrics: boolean; - hostMetrics: boolean; - repoMetrics: boolean; - jobMetrics: boolean; + metrics: Set; }; storage: { @@ -96,4 +93,5 @@ export interface EnvData { export interface IConfigRepository { getEnv(): EnvData; + getWorker(): ImmichWorker | undefined; } diff --git a/server/src/interfaces/cron.interface.ts b/server/src/interfaces/cron.interface.ts new file mode 100644 index 0000000000000..ceb554864a172 --- /dev/null +++ b/server/src/interfaces/cron.interface.ts @@ -0,0 +1,20 @@ +export const ICronRepository = 'ICronRepository'; + +type CronBase = { + name: string; + start?: boolean; +}; + +export type CronCreate = CronBase & { + expression: string; + onTick: () => void; +}; + +export type CronUpdate = CronBase & { + expression?: string; +}; + +export interface ICronRepository { + create(cron: CronCreate): void; + update(cron: CronUpdate): void; +} diff --git a/server/src/interfaces/database.interface.ts b/server/src/interfaces/database.interface.ts index 79550d416ea5e..6a10a92f315c0 100644 --- a/server/src/interfaces/database.interface.ts +++ b/server/src/interfaces/database.interface.ts @@ -7,6 +7,22 @@ export enum DatabaseExtension { export type VectorExtension = DatabaseExtension.VECTOR | DatabaseExtension.VECTORS; +export type DatabaseConnectionURL = { + connectionType: 'url'; + url: string; +}; + +export type DatabaseConnectionParts = { + connectionType: 'parts'; + host: string; + port: number; + username: string; + password: string; + database: string; +}; + +export type DatabaseConnectionParams = DatabaseConnectionURL | DatabaseConnectionParts; + export enum VectorIndex { CLIP = 'clip_index', FACE = 'face_index', @@ -19,8 +35,9 @@ export enum DatabaseLock { StorageTemplateMigration = 420, VersionHistory = 500, CLIPDimSize = 512, - LibraryWatch = 1337, + Library = 1337, GetSystemConfig = 69, + BackupDatabase = 42, } export const EXTENSION_NAMES: Record = { diff --git a/server/src/interfaces/event.interface.ts b/server/src/interfaces/event.interface.ts index 40efaf150c4df..9a9e23cca0018 100644 --- a/server/src/interfaces/event.interface.ts +++ b/server/src/interfaces/event.interface.ts @@ -2,21 +2,21 @@ import { ClassConstructor } from 'class-transformer'; import { SystemConfig } from 'src/config'; import { AssetResponseDto } from 'src/dtos/asset-response.dto'; import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto'; -import { ImmichWorker } from 'src/enum'; +import { JobItem, QueueName } from 'src/interfaces/job.interface'; export const IEventRepository = 'IEventRepository'; type EventMap = { // app events - 'app.bootstrap': [ImmichWorker]; - 'app.shutdown': [ImmichWorker]; + 'app.bootstrap': []; + 'app.shutdown': []; + 'config.init': [{ newConfig: SystemConfig }]; // config events 'config.update': [ { newConfig: SystemConfig; - /** When the server starts, `oldConfig` is `undefined` */ - oldConfig?: SystemConfig; + oldConfig: SystemConfig; }, ]; 'config.validate': [{ newConfig: SystemConfig; oldConfig: SystemConfig }]; @@ -38,6 +38,8 @@ type EventMap = { 'assets.delete': [{ assetIds: string[]; userId: string }]; 'assets.restore': [{ assetIds: string[]; userId: string }]; + 'job.start': [QueueName, JobItem]; + // session events 'session.delete': [{ sessionId: string }]; @@ -86,9 +88,15 @@ export type EventItem = { server: boolean; }; +export enum BootstrapEventPriority { + // Database service should be initialized before anything else, most other services need database access + DatabaseService = -200, + // Initialise config after other bootstrap services, stop other services from using config on bootstrap + SystemConfig = 100, +} + export interface IEventRepository { setup(options: { services: ClassConstructor[] }): void; - on(item: EventItem): void; emit(event: T, ...args: ArgsOf): Promise; /** diff --git a/server/src/interfaces/job.interface.ts b/server/src/interfaces/job.interface.ts index 82176ffa93c83..7976f813022ff 100644 --- a/server/src/interfaces/job.interface.ts +++ b/server/src/interfaces/job.interface.ts @@ -1,3 +1,4 @@ +import { ClassConstructor } from 'class-transformer'; import { EmailImageAttachment } from 'src/interfaces/notification.interface'; export enum QueueName { @@ -15,11 +16,15 @@ export enum QueueName { SIDECAR = 'sidecar', LIBRARY = 'library', NOTIFICATION = 'notifications', + BACKUP_DATABASE = 'backupDatabase', } export type ConcurrentQueueName = Exclude< QueueName, - QueueName.STORAGE_TEMPLATE_MIGRATION | QueueName.FACIAL_RECOGNITION | QueueName.DUPLICATE_DETECTION + | QueueName.STORAGE_TEMPLATE_MIGRATION + | QueueName.FACIAL_RECOGNITION + | QueueName.DUPLICATE_DETECTION + | QueueName.BACKUP_DATABASE >; export enum JobCommand { @@ -31,6 +36,9 @@ export enum JobCommand { } export enum JobName { + //backups + BACKUP_DATABASE = 'database-backup', + // conversion QUEUE_VIDEO_CONVERSION = 'queue-video-conversion', VIDEO_CONVERSION = 'video-conversion', @@ -209,6 +217,9 @@ export enum QueueCleanType { } export type JobItem = + // Backups + | { name: JobName.BACKUP_DATABASE; data?: IBaseJob } + // Transcoding | { name: JobName.QUEUE_VIDEO_CONVERSION; data: IBaseJob } | { name: JobName.VIDEO_CONVERSION; data: IEntityJob } @@ -228,8 +239,8 @@ export type JobItem = // Migration | { name: JobName.QUEUE_MIGRATION; data?: IBaseJob } - | { name: JobName.MIGRATE_ASSET; data?: IEntityJob } - | { name: JobName.MIGRATE_PERSON; data?: IEntityJob } + | { name: JobName.MIGRATE_ASSET; data: IEntityJob } + | { name: JobName.MIGRATE_PERSON; data: IEntityJob } // Metadata Extraction | { name: JobName.QUEUE_METADATA_EXTRACTION; data: IBaseJob } @@ -276,7 +287,7 @@ export type JobItem = | { name: JobName.LIBRARY_SYNC_FILE; data: ILibraryFileJob } | { name: JobName.LIBRARY_QUEUE_SYNC_FILES; data: IEntityJob } | { name: JobName.LIBRARY_QUEUE_SYNC_ASSETS; data: IEntityJob } - | { name: JobName.LIBRARY_SYNC_ASSET; data: IEntityJob } + | { name: JobName.LIBRARY_SYNC_ASSET; data: ILibraryAssetJob } | { name: JobName.LIBRARY_DELETE; data: IEntityJob } | { name: JobName.LIBRARY_QUEUE_SYNC_ALL; data?: IBaseJob } | { name: JobName.LIBRARY_QUEUE_CLEANUP; data: IBaseJob } @@ -295,16 +306,15 @@ export enum JobStatus { FAILED = 'failed', SKIPPED = 'skipped', } - -export type JobHandler = (data: T) => Promise; -export type JobItemHandler = (item: JobItem) => Promise; +export type Jobs = { [K in JobItem['name']]: (JobItem & { name: K })['data'] }; +export type JobOf = Jobs[T]; export const IJobRepository = 'IJobRepository'; export interface IJobRepository { - addHandler(queueName: QueueName, concurrency: number, handler: JobItemHandler): void; - addCronJob(name: string, expression: string, onTick: () => void, start?: boolean): void; - updateCronJob(name: string, expression?: string, start?: boolean): void; + setup(options: { services: ClassConstructor[] }): void; + startWorkers(): void; + run(job: JobItem): Promise; setConcurrency(queueName: QueueName, concurrency: number): void; queue(item: JobItem): Promise; queueAll(items: JobItem[]): Promise; diff --git a/server/src/interfaces/media.interface.ts b/server/src/interfaces/media.interface.ts index 2bc8ccde36d8b..468a6ad88d9ec 100644 --- a/server/src/interfaces/media.interface.ts +++ b/server/src/interfaces/media.interface.ts @@ -1,5 +1,5 @@ import { Writable } from 'node:stream'; -import { ImageFormat, TranscodeTarget, VideoCodec } from 'src/enum'; +import { ExifOrientation, ImageFormat, TranscodeTarget, VideoCodec } from 'src/enum'; export const IMediaRepository = 'IMediaRepository'; @@ -31,6 +31,7 @@ interface DecodeImageOptions { export interface DecodeToBufferOptions extends DecodeImageOptions { size: number; + orientation?: ExifOrientation; } export type GenerateThumbnailOptions = ImageOptions & DecodeImageOptions; @@ -59,6 +60,7 @@ export interface VideoStreamInfo { frameCount: number; isHDR: boolean; bitrate: number; + pixelFormat: string; } export interface AudioStreamInfo { @@ -112,7 +114,12 @@ export interface ImageBuffer { } export interface VideoCodecSWConfig { - getCommand(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream: AudioStreamInfo): TranscodeCommand; + getCommand( + target: TranscodeTarget, + videoStream: VideoStreamInfo, + audioStream: AudioStreamInfo, + format?: VideoFormat, + ): TranscodeCommand; } export interface VideoCodecHWConfig extends VideoCodecSWConfig { diff --git a/server/src/interfaces/process.interface.ts b/server/src/interfaces/process.interface.ts new file mode 100644 index 0000000000000..14a8c1ff33477 --- /dev/null +++ b/server/src/interfaces/process.interface.ts @@ -0,0 +1,25 @@ +import { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio } from 'node:child_process'; +import { Readable } from 'node:stream'; + +export interface ImmichReadStream { + stream: Readable; + type?: string; + length?: number; +} + +export interface ImmichZipStream extends ImmichReadStream { + addFile: (inputPath: string, filename: string) => void; + finalize: () => Promise; +} + +export interface DiskUsage { + available: number; + free: number; + total: number; +} + +export const IProcessRepository = 'IProcessRepository'; + +export interface IProcessRepository { + spawn(command: string, args?: readonly string[], options?: SpawnOptionsWithoutStdio): ChildProcessWithoutNullStreams; +} diff --git a/server/src/interfaces/search.interface.ts b/server/src/interfaces/search.interface.ts index 63d74a35fb626..87bf1bc4b152a 100644 --- a/server/src/interfaces/search.interface.ts +++ b/server/src/interfaces/search.interface.ts @@ -68,7 +68,6 @@ export interface SearchStatusOptions { export interface SearchOneToOneRelationOptions { withExif?: boolean; - withSmartInfo?: boolean; withStacked?: boolean; } diff --git a/server/src/interfaces/storage.interface.ts b/server/src/interfaces/storage.interface.ts index 321f7b8367f23..b304d94fefd2d 100644 --- a/server/src/interfaces/storage.interface.ts +++ b/server/src/interfaces/storage.interface.ts @@ -1,7 +1,7 @@ import { WatchOptions } from 'chokidar'; import { Stats } from 'node:fs'; import { FileReadOptions } from 'node:fs/promises'; -import { Readable } from 'node:stream'; +import { Readable, Writable } from 'node:stream'; import { CrawlOptionsDto, WalkOptionsDto } from 'src/dtos/library.dto'; export interface ImmichReadStream { @@ -36,6 +36,7 @@ export interface IStorageRepository { createReadStream(filepath: string, mimeType?: string | null): Promise; readFile(filepath: string, options?: FileReadOptions): Promise; createFile(filepath: string, buffer: Buffer): Promise; + createWriteStream(filepath: string): Writable; createOrOverwriteFile(filepath: string, buffer: Buffer): Promise; overwriteFile(filepath: string, buffer: Buffer): Promise; realpath(filepath: string): Promise; diff --git a/server/src/interfaces/user.interface.ts b/server/src/interfaces/user.interface.ts index 3353d45dce215..385a4d3d50e91 100644 --- a/server/src/interfaces/user.interface.ts +++ b/server/src/interfaces/user.interface.ts @@ -11,6 +11,8 @@ export interface UserStatsQueryResponse { photos: number; videos: number; usage: number; + usagePhotos: number; + usageVideos: number; quotaSizeInBytes: number | null; } diff --git a/server/src/main.ts b/server/src/main.ts index 11cc44ec10b92..3097eee69bd74 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -1,5 +1,5 @@ import { CommandFactory } from 'nest-commander'; -import { fork } from 'node:child_process'; +import { ChildProcess, fork } from 'node:child_process'; import { Worker } from 'node:worker_threads'; import { ImmichAdminModule } from 'src/app.module'; import { ImmichWorker, LogLevel } from 'src/enum'; @@ -10,23 +10,41 @@ if (immichApp) { process.argv.splice(2, 1); } -function bootstrapWorker(name: ImmichWorker) { - console.log(`Starting ${name} worker`); +let apiProcess: ChildProcess | undefined; - const execArgv = process.execArgv.map((arg) => (arg.startsWith('--inspect') ? '--inspect=0.0.0.0:9231' : arg)); - const worker = - name === 'api' ? fork(`./dist/workers/${name}.js`, [], { execArgv }) : new Worker(`./dist/workers/${name}.js`); +const onError = (name: string, error: Error) => { + console.error(`${name} worker error: ${error}`); +}; - worker.on('error', (error) => { - console.error(`${name} worker error: ${error}`); - }); +const onExit = (name: string, exitCode: number | null) => { + if (exitCode !== 0) { + console.error(`${name} worker exited with code ${exitCode}`); - worker.on('exit', (exitCode) => { - if (exitCode !== 0) { - console.error(`${name} worker exited with code ${exitCode}`); - process.exit(exitCode); + if (apiProcess && name !== ImmichWorker.API) { + console.error('Killing api process'); + apiProcess.kill('SIGTERM'); + apiProcess = undefined; } - }); + } + + process.exit(exitCode); +}; + +function bootstrapWorker(name: ImmichWorker) { + console.log(`Starting ${name} worker`); + + let worker: Worker | ChildProcess; + if (name === ImmichWorker.API) { + worker = fork(`./dist/workers/${name}.js`, [], { + execArgv: process.execArgv.map((arg) => (arg.startsWith('--inspect') ? '--inspect=0.0.0.0:9231' : arg)), + }); + apiProcess = worker; + } else { + worker = new Worker(`./dist/workers/${name}.js`); + } + + worker.on('error', (error) => onError(name, error)); + worker.on('exit', (exitCode) => onExit(name, exitCode)); } function bootstrap() { diff --git a/server/src/migrations/1729793521993-AddAlbumAssetCreatedAt.ts b/server/src/migrations/1729793521993-AddAlbumAssetCreatedAt.ts new file mode 100644 index 0000000000000..280b34890d094 --- /dev/null +++ b/server/src/migrations/1729793521993-AddAlbumAssetCreatedAt.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddAlbumAssetCreatedAt1729793521993 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "albums_assets_assets" ADD COLUMN "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "albums_assets_assets" DROP COLUMN "createdAt"`); + } +} diff --git a/server/src/migrations/1730227312171-RemoveNplFromSystemConfig.ts b/server/src/migrations/1730227312171-RemoveNplFromSystemConfig.ts new file mode 100644 index 0000000000000..2c929191dd7e4 --- /dev/null +++ b/server/src/migrations/1730227312171-RemoveNplFromSystemConfig.ts @@ -0,0 +1,12 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveNplFromSystemConfig1730227312171 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update system_metadata + set value = value #- '{ffmpeg,npl}' + where key = 'system-config' and value->'ffmpeg'->'npl' is not null`); + } + + public async down(): Promise {} +} diff --git a/server/src/migrations/1730989238718-DropSmartInfoTable.ts b/server/src/migrations/1730989238718-DropSmartInfoTable.ts new file mode 100644 index 0000000000000..a4de2652d6471 --- /dev/null +++ b/server/src/migrations/1730989238718-DropSmartInfoTable.ts @@ -0,0 +1,11 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DropSmartInfoTable1730989238718 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE smart_info`); + } + + public async down(): Promise { + // not implemented + } +} diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql index eda91482bb1d0..e7f5b558b0772 100644 --- a/server/src/queries/asset.repository.sql +++ b/server/src/queries/asset.repository.sql @@ -87,7 +87,7 @@ WHERE ) AND ("entity"."deletedAt" IS NULL) ORDER BY - "entity"."localDateTime" ASC + "entity"."fileCreatedAt" ASC -- AssetRepository.getByIds SELECT @@ -183,9 +183,6 @@ SELECT "AssetEntity__AssetEntity_exifInfo"."bitsPerSample" AS "AssetEntity__AssetEntity_exifInfo_bitsPerSample", "AssetEntity__AssetEntity_exifInfo"."rating" AS "AssetEntity__AssetEntity_exifInfo_rating", "AssetEntity__AssetEntity_exifInfo"."fps" AS "AssetEntity__AssetEntity_exifInfo_fps", - "AssetEntity__AssetEntity_smartInfo"."assetId" AS "AssetEntity__AssetEntity_smartInfo_assetId", - "AssetEntity__AssetEntity_smartInfo"."tags" AS "AssetEntity__AssetEntity_smartInfo_tags", - "AssetEntity__AssetEntity_smartInfo"."objects" AS "AssetEntity__AssetEntity_smartInfo_objects", "AssetEntity__AssetEntity_tags"."id" AS "AssetEntity__AssetEntity_tags_id", "AssetEntity__AssetEntity_tags"."value" AS "AssetEntity__AssetEntity_tags_value", "AssetEntity__AssetEntity_tags"."createdAt" AS "AssetEntity__AssetEntity_tags_createdAt", @@ -252,7 +249,6 @@ SELECT FROM "assets" "AssetEntity" LEFT JOIN "exif" "AssetEntity__AssetEntity_exifInfo" ON "AssetEntity__AssetEntity_exifInfo"."assetId" = "AssetEntity"."id" - LEFT JOIN "smart_info" "AssetEntity__AssetEntity_smartInfo" ON "AssetEntity__AssetEntity_smartInfo"."assetId" = "AssetEntity"."id" LEFT JOIN "tag_asset" "AssetEntity_AssetEntity__AssetEntity_tags" ON "AssetEntity_AssetEntity__AssetEntity_tags"."assetsId" = "AssetEntity"."id" LEFT JOIN "tags" "AssetEntity__AssetEntity_tags" ON "AssetEntity__AssetEntity_tags"."id" = "AssetEntity_AssetEntity__AssetEntity_tags"."tagsId" LEFT JOIN "asset_faces" "AssetEntity__AssetEntity_faces" ON "AssetEntity__AssetEntity_faces"."assetId" = "AssetEntity"."id" @@ -932,36 +928,6 @@ WHERE LIMIT 12 --- AssetRepository.getAssetIdByTag -WITH - "random_tags" AS ( - SELECT - unnest(tags) AS "tag" - FROM - "smart_info" "si" - GROUP BY - tag - HAVING - count(*) >= $1 - ) -SELECT DISTINCT - ON (unnest("si"."tags")) "asset"."id" AS "data", - unnest("si"."tags") AS "value" -FROM - "assets" "asset" - INNER JOIN "smart_info" "si" ON "asset"."id" = si."assetId" - INNER JOIN "random_tags" "t" ON "si"."tags" @> ARRAY[t.tag] -WHERE - ( - "asset"."isVisible" = true - AND "asset"."type" = $2 - AND "asset"."ownerId" IN ($3) - AND "asset"."isArchived" = $4 - ) - AND ("asset"."deletedAt" IS NULL) -LIMIT - 12 - -- AssetRepository.getAllForUserFullSync SELECT "asset"."id" AS "asset_id", diff --git a/server/src/queries/user.repository.sql b/server/src/queries/user.repository.sql index ab0a6cc534bd0..c35dc540cef42 100644 --- a/server/src/queries/user.repository.sql +++ b/server/src/queries/user.repository.sql @@ -140,7 +140,23 @@ SELECT "assets"."libraryId" IS NULL ), 0 - ) AS "usage" + ) AS "usage", + COALESCE( + SUM("exif"."fileSizeInByte") FILTER ( + WHERE + "assets"."libraryId" IS NULL + AND "assets"."type" = 'IMAGE' + ), + 0 + ) AS "usagePhotos", + COALESCE( + SUM("exif"."fileSizeInByte") FILTER ( + WHERE + "assets"."libraryId" IS NULL + AND "assets"."type" = 'VIDEO' + ), + 0 + ) AS "usageVideos" FROM "users" "users" LEFT JOIN "assets" "assets" ON "assets"."ownerId" = "users"."id" diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index 6080e943e4b04..ce7d257b40f28 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -5,7 +5,6 @@ import { AssetFileEntity } from 'src/entities/asset-files.entity'; import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity'; import { AssetEntity } from 'src/entities/asset.entity'; import { ExifEntity } from 'src/entities/exif.entity'; -import { SmartInfoEntity } from 'src/entities/smart-info.entity'; import { AssetFileType, AssetOrder, AssetStatus, AssetType, PaginationMode } from 'src/enum'; import { AssetBuilderOptions, @@ -60,7 +59,6 @@ export class AssetRepository implements IAssetRepository { @InjectRepository(AssetFileEntity) private fileRepository: Repository, @InjectRepository(ExifEntity) private exifRepository: Repository, @InjectRepository(AssetJobStatusEntity) private jobStatusRepository: Repository, - @InjectRepository(SmartInfoEntity) private smartInfoRepository: Repository, ) {} async upsertExif(exif: Partial): Promise { @@ -93,7 +91,7 @@ export class AssetRepository implements IAssetRepository { ) .leftJoinAndSelect('entity.exifInfo', 'exifInfo') .leftJoinAndSelect('entity.files', 'files') - .orderBy('entity.localDateTime', 'ASC') + .orderBy('entity.fileCreatedAt', 'ASC') .getMany(); } @@ -119,7 +117,6 @@ export class AssetRepository implements IAssetRepository { where: { id: In(ids) }, relations: { exifInfo: true, - smartInfo: true, tags: true, faces: { person: true, @@ -422,22 +419,6 @@ export class AssetRepository implements IAssetRepository { break; } - case WithoutProperty.OBJECT_TAGS: { - relations = { - smartInfo: true, - }; - where = { - jobStatus: { - previewAt: Not(IsNull()), - }, - isVisible: true, - smartInfo: { - tags: IsNull(), - }, - }; - break; - } - case WithoutProperty.FACES: { relations = { faces: true, @@ -457,23 +438,6 @@ export class AssetRepository implements IAssetRepository { break; } - case WithoutProperty.PERSON: { - relations = { - faces: true, - }; - where = { - jobStatus: { - previewAt: Not(IsNull()), - }, - isVisible: true, - faces: { - assetId: Not(IsNull()), - personId: IsNull(), - }, - }; - break; - } - case WithoutProperty.SIDECAR: { where = [ { sidecarPath: IsNull(), isVisible: true }, @@ -611,35 +575,6 @@ export class AssetRepository implements IAssetRepository { return { fieldName: 'exifInfo.city', items }; } - @GenerateSql({ params: [DummyValue.UUID, { minAssetsPerField: 5, maxFields: 12 }] }) - async getAssetIdByTag( - ownerId: string, - { minAssetsPerField, maxFields }: AssetExploreFieldOptions, - ): Promise> { - const cte = this.smartInfoRepository - .createQueryBuilder('si') - .select('unnest(tags)', 'tag') - .groupBy('tag') - .having('count(*) >= :minAssetsPerField', { minAssetsPerField }); - - const items = await this.getBuilder({ - userIds: [ownerId], - exifInfo: false, - assetType: AssetType.IMAGE, - isArchived: false, - }) - .select('unnest(si.tags)', 'value') - .addSelect('asset.id', 'data') - .distinctOn(['unnest(si.tags)']) - .innerJoin('smart_info', 'si', 'asset.id = si."assetId"') - .addCommonTableExpression(cte, 'random_tags') - .innerJoin('random_tags', 't', 'si.tags @> ARRAY[t.tag]') - .limit(maxFields) - .getRawMany(); - - return { fieldName: 'smartInfo.tags', items }; - } - private getBuilder(options: AssetBuilderOptions) { const builder = this.repository.createQueryBuilder('asset').where('asset.isVisible = true'); diff --git a/server/src/repositories/config.repository.spec.ts b/server/src/repositories/config.repository.spec.ts index 84da211182793..2ff5f5307317f 100644 --- a/server/src/repositories/config.repository.spec.ts +++ b/server/src/repositories/config.repository.spec.ts @@ -1,3 +1,4 @@ +import { ImmichTelemetry } from 'src/enum'; import { clearEnvCache, ConfigRepository } from 'src/repositories/config.repository'; const getEnv = () => { @@ -7,16 +8,14 @@ const getEnv = () => { const resetEnv = () => { for (const env of [ + 'IMMICH_ENV', 'IMMICH_WORKERS_INCLUDE', 'IMMICH_WORKERS_EXCLUDE', 'IMMICH_TRUSTED_PROXIES', 'IMMICH_API_METRICS_PORT', 'IMMICH_MICROSERVICES_METRICS_PORT', - 'IMMICH_METRICS', - 'IMMICH_API_METRICS', - 'IMMICH_HOST_METRICS', - 'IMMICH_IO_METRICS', - 'IMMICH_JOB_METRICS', + 'IMMICH_TELEMETRY_INCLUDE', + 'IMMICH_TELEMETRY_EXCLUDE', 'DB_URL', 'DB_HOSTNAME', @@ -64,16 +63,30 @@ describe('getEnv', () => { resetEnv(); }); + it('should use defaults', () => { + const config = getEnv(); + + expect(config).toMatchObject({ + host: undefined, + port: 2283, + environment: 'production', + configFile: undefined, + logLevel: undefined, + }); + }); + describe('database', () => { it('should use defaults', () => { const { database } = getEnv(); expect(database).toEqual({ - url: undefined, - host: 'database', - port: 5432, - name: 'immich', - username: 'postgres', - password: 'postgres', + config: expect.objectContaining({ + type: 'postgres', + host: 'database', + port: 5432, + database: 'immich', + username: 'postgres', + password: 'postgres', + }), skipMigrations: false, vectorExtension: 'vectors', }); @@ -202,6 +215,11 @@ describe('getEnv', () => { trustedProxies: ['10.1.0.0', '10.2.0.0', '169.254.0.0/16'], }); }); + + it('should reject invalid trusted proxies', () => { + process.env.IMMICH_TRUSTED_PROXIES = '10.1'; + expect(() => getEnv()).toThrowError('Invalid environment variables: IMMICH_TRUSTED_PROXIES'); + }); }); describe('telemetry', () => { @@ -210,11 +228,7 @@ describe('getEnv', () => { expect(telemetry).toEqual({ apiPort: 8081, microservicesPort: 8082, - enabled: false, - apiMetrics: false, - hostMetrics: false, - jobMetrics: false, - repoMetrics: false, + metrics: new Set([]), }); }); @@ -225,32 +239,29 @@ describe('getEnv', () => { expect(telemetry).toMatchObject({ apiPort: 2001, microservicesPort: 2002, + metrics: expect.any(Set), }); }); it('should run with telemetry enabled', () => { - process.env.IMMICH_METRICS = 'true'; + process.env.IMMICH_TELEMETRY_INCLUDE = 'all'; const { telemetry } = getEnv(); - expect(telemetry).toMatchObject({ - enabled: true, - apiMetrics: true, - hostMetrics: true, - jobMetrics: true, - repoMetrics: true, - }); + expect(telemetry.metrics).toEqual(new Set(Object.values(ImmichTelemetry))); }); it('should run with telemetry enabled and jobs disabled', () => { - process.env.IMMICH_METRICS = 'true'; - process.env.IMMICH_JOB_METRICS = 'false'; + process.env.IMMICH_TELEMETRY_INCLUDE = 'all'; + process.env.IMMICH_TELEMETRY_EXCLUDE = 'job'; const { telemetry } = getEnv(); - expect(telemetry).toMatchObject({ - enabled: true, - apiMetrics: true, - hostMetrics: true, - jobMetrics: false, - repoMetrics: true, - }); + expect(telemetry.metrics).toEqual( + new Set([ImmichTelemetry.API, ImmichTelemetry.HOST, ImmichTelemetry.IO, ImmichTelemetry.REPO]), + ); + }); + + it('should run with specific telemetry metrics', () => { + process.env.IMMICH_TELEMETRY_INCLUDE = 'io, host, api'; + const { telemetry } = getEnv(); + expect(telemetry.metrics).toEqual(new Set([ImmichTelemetry.API, ImmichTelemetry.HOST, ImmichTelemetry.IO])); }); }); }); diff --git a/server/src/repositories/config.repository.ts b/server/src/repositories/config.repository.ts index fabccd78464d4..a8a1c9972b3d5 100644 --- a/server/src/repositories/config.repository.ts +++ b/server/src/repositories/config.repository.ts @@ -1,15 +1,18 @@ -import { Injectable } from '@nestjs/common'; -import { join } from 'node:path'; -import { citiesFile, excludePaths } from 'src/constants'; +import { Inject, Injectable, Optional } from '@nestjs/common'; +import { plainToInstance } from 'class-transformer'; +import { validateSync } from 'class-validator'; +import { Request, Response } from 'express'; +import { CLS_ID } from 'nestjs-cls'; +import { join, resolve } from 'node:path'; +import { citiesFile, excludePaths, IWorker } from 'src/constants'; import { Telemetry } from 'src/decorators'; -import { ImmichEnvironment, ImmichWorker, LogLevel } from 'src/enum'; +import { EnvDto } from 'src/dtos/env.dto'; +import { ImmichEnvironment, ImmichHeader, ImmichTelemetry, ImmichWorker } from 'src/enum'; import { EnvData, IConfigRepository } from 'src/interfaces/config.interface'; import { DatabaseExtension } from 'src/interfaces/database.interface'; import { QueueName } from 'src/interfaces/job.interface'; import { setDifference } from 'src/utils/set'; -// TODO replace src/config validation with class-validator, here - const productionKeys = { client: 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF2LzdTMzJjUkE1KysxTm5WRHNDTQpzcFAvakpISU1xT0pYRm5oNE53QTJPcHorUk1mZGNvOTJQc09naCt3d1FlRXYxVTJjMnBqelRpUS8ybHJLcS9rCnpKUmxYd2M0Y1Vlc1FETUpPRitQMnFPTlBiQUprWHZDWFlCVUxpdENJa29Md2ZoU0dOanlJS2FSRGhkL3ROeU4KOCtoTlJabllUMWhTSWo5U0NrS3hVQ096YXRQVjRtQ0RlclMrYkUrZ0VVZVdwOTlWOWF6dkYwRkltblRXcFFTdwpjOHdFWmdPTWg0c3ZoNmFpY3dkemtQQ3dFTGFrMFZhQkgzMUJFVUNRTGI5K0FJdEhBVXRKQ0t4aGI1V2pzMXM5CmJyWGZpMHZycGdjWi82RGFuWTJxZlNQem5PbXZEMkZycmxTMXE0SkpOM1ZvN1d3LzBZeS95TWNtelRXWmhHdWgKVVFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tDQo=', @@ -25,42 +28,53 @@ const stagingKeys = { }; const WORKER_TYPES = new Set(Object.values(ImmichWorker)); +const TELEMETRY_TYPES = new Set(Object.values(ImmichTelemetry)); -const asSet = (value: string | undefined, defaults: ImmichWorker[]) => { +const asSet = (value: string | undefined, defaults: T[]) => { const values = (value || '').replaceAll(/\s/g, '').split(',').filter(Boolean); - return new Set(values.length === 0 ? defaults : (values as ImmichWorker[])); + return new Set(values.length === 0 ? defaults : (values as T[])); }; -const parseBoolean = (value: string | undefined, defaultValue: boolean) => (value ? value === 'true' : defaultValue); - const getEnv = (): EnvData => { - const included = asSet(process.env.IMMICH_WORKERS_INCLUDE, [ImmichWorker.API, ImmichWorker.MICROSERVICES]); - const excluded = asSet(process.env.IMMICH_WORKERS_EXCLUDE, []); - const workers = [...setDifference(included, excluded)]; + const dto = plainToInstance(EnvDto, process.env); + const errors = validateSync(dto); + if (errors.length > 0) { + throw new Error( + `Invalid environment variables: ${errors.map((error) => `${error.property}=${error.value}`).join(', ')}`, + ); + } + + const includedWorkers = asSet(dto.IMMICH_WORKERS_INCLUDE, [ImmichWorker.API, ImmichWorker.MICROSERVICES]); + const excludedWorkers = asSet(dto.IMMICH_WORKERS_EXCLUDE, []); + const workers = [...setDifference(includedWorkers, excludedWorkers)]; for (const worker of workers) { if (!WORKER_TYPES.has(worker)) { throw new Error(`Invalid worker(s) found: ${workers.join(',')}`); } } - const environment = process.env.IMMICH_ENV as ImmichEnvironment; + const environment = dto.IMMICH_ENV || ImmichEnvironment.PRODUCTION; const isProd = environment === ImmichEnvironment.PRODUCTION; - const buildFolder = process.env.IMMICH_BUILD_DATA || '/build'; + const buildFolder = dto.IMMICH_BUILD_DATA || '/build'; const folders = { + // eslint-disable-next-line unicorn/prefer-module + dist: resolve(`${__dirname}/..`), geodata: join(buildFolder, 'geodata'), web: join(buildFolder, 'www'), }; + const databaseUrl = dto.DB_URL; + let redisConfig = { - host: process.env.REDIS_HOSTNAME || 'redis', - port: Number.parseInt(process.env.REDIS_PORT || '') || 6379, - db: Number.parseInt(process.env.REDIS_DBINDEX || '') || 0, - username: process.env.REDIS_USERNAME || undefined, - password: process.env.REDIS_PASSWORD || undefined, - path: process.env.REDIS_SOCKET || undefined, + host: dto.REDIS_HOSTNAME || 'redis', + port: dto.REDIS_PORT || 6379, + db: dto.REDIS_DBINDEX || 0, + username: dto.REDIS_USERNAME || undefined, + password: dto.REDIS_PASSWORD || undefined, + path: dto.REDIS_SOCKET || undefined, }; - const redisUrl = process.env.REDIS_URL; + const redisUrl = dto.REDIS_URL; if (redisUrl && redisUrl.startsWith('ioredis://')) { try { redisConfig = JSON.parse(Buffer.from(redisUrl.slice(10), 'base64').toString()); @@ -69,34 +83,40 @@ const getEnv = (): EnvData => { } } - const globalEnabled = parseBoolean(process.env.IMMICH_METRICS, false); - const hostMetrics = parseBoolean(process.env.IMMICH_HOST_METRICS, globalEnabled); - const apiMetrics = parseBoolean(process.env.IMMICH_API_METRICS, globalEnabled); - const repoMetrics = parseBoolean(process.env.IMMICH_IO_METRICS, globalEnabled); - const jobMetrics = parseBoolean(process.env.IMMICH_JOB_METRICS, globalEnabled); - const telemetryEnabled = globalEnabled || hostMetrics || apiMetrics || repoMetrics || jobMetrics; + const includedTelemetries = + dto.IMMICH_TELEMETRY_INCLUDE === 'all' + ? new Set(Object.values(ImmichTelemetry)) + : asSet(dto.IMMICH_TELEMETRY_INCLUDE, []); + + const excludedTelemetries = asSet(dto.IMMICH_TELEMETRY_EXCLUDE, []); + const telemetries = setDifference(includedTelemetries, excludedTelemetries); + for (const telemetry of telemetries) { + if (!TELEMETRY_TYPES.has(telemetry)) { + throw new Error(`Invalid telemetry found: ${telemetry}`); + } + } return { - host: process.env.IMMICH_HOST, - port: Number(process.env.IMMICH_PORT) || 2283, + host: dto.IMMICH_HOST, + port: dto.IMMICH_PORT || 2283, environment, - configFile: process.env.IMMICH_CONFIG_FILE, - logLevel: process.env.IMMICH_LOG_LEVEL as LogLevel, + configFile: dto.IMMICH_CONFIG_FILE, + logLevel: dto.IMMICH_LOG_LEVEL, buildMetadata: { - build: process.env.IMMICH_BUILD, - buildUrl: process.env.IMMICH_BUILD_URL, - buildImage: process.env.IMMICH_BUILD_IMAGE, - buildImageUrl: process.env.IMMICH_BUILD_IMAGE_URL, - repository: process.env.IMMICH_REPOSITORY, - repositoryUrl: process.env.IMMICH_REPOSITORY_URL, - sourceRef: process.env.IMMICH_SOURCE_REF, - sourceCommit: process.env.IMMICH_SOURCE_COMMIT, - sourceUrl: process.env.IMMICH_SOURCE_URL, - thirdPartySourceUrl: process.env.IMMICH_THIRD_PARTY_SOURCE_URL, - thirdPartyBugFeatureUrl: process.env.IMMICH_THIRD_PARTY_BUG_FEATURE_URL, - thirdPartyDocumentationUrl: process.env.IMMICH_THIRD_PARTY_DOCUMENTATION_URL, - thirdPartySupportUrl: process.env.IMMICH_THIRD_PARTY_SUPPORT_URL, + build: dto.IMMICH_BUILD, + buildUrl: dto.IMMICH_BUILD_URL, + buildImage: dto.IMMICH_BUILD_IMAGE, + buildImageUrl: dto.IMMICH_BUILD_IMAGE_URL, + repository: dto.IMMICH_REPOSITORY, + repositoryUrl: dto.IMMICH_REPOSITORY_URL, + sourceRef: dto.IMMICH_SOURCE_REF, + sourceCommit: dto.IMMICH_SOURCE_COMMIT, + sourceUrl: dto.IMMICH_SOURCE_URL, + thirdPartySourceUrl: dto.IMMICH_THIRD_PARTY_SOURCE_URL, + thirdPartyBugFeatureUrl: dto.IMMICH_THIRD_PARTY_BUG_FEATURE_URL, + thirdPartyDocumentationUrl: dto.IMMICH_THIRD_PARTY_DOCUMENTATION_URL, + thirdPartySupportUrl: dto.IMMICH_THIRD_PARTY_SUPPORT_URL, }, bull: { @@ -112,33 +132,59 @@ const getEnv = (): EnvData => { queues: Object.values(QueueName).map((name) => ({ name })), }, + cls: { + config: { + middleware: { + mount: true, + generateId: true, + setup: (cls, req: Request, res: Response) => { + const headerValues = req.headers[ImmichHeader.CID]; + const headerValue = Array.isArray(headerValues) ? headerValues[0] : headerValues; + const cid = headerValue || cls.get(CLS_ID); + cls.set(CLS_ID, cid); + res.header(ImmichHeader.CID, cid); + }, + }, + }, + }, + database: { - url: process.env.DB_URL, - host: process.env.DB_HOSTNAME || 'database', - port: Number(process.env.DB_PORT) || 5432, - username: process.env.DB_USERNAME || 'postgres', - password: process.env.DB_PASSWORD || 'postgres', - name: process.env.DB_DATABASE_NAME || 'immich', - - skipMigrations: process.env.DB_SKIP_MIGRATIONS === 'true', - vectorExtension: - process.env.DB_VECTOR_EXTENSION === 'pgvector' ? DatabaseExtension.VECTOR : DatabaseExtension.VECTORS, + config: { + type: 'postgres', + entities: [`${folders.dist}/entities` + '/*.entity.{js,ts}'], + migrations: [`${folders.dist}/migrations` + '/*.{js,ts}'], + subscribers: [`${folders.dist}/subscribers` + '/*.{js,ts}'], + migrationsRun: false, + synchronize: false, + connectTimeoutMS: 10_000, // 10 seconds + parseInt8: true, + ...(databaseUrl + ? { connectionType: 'url', url: databaseUrl } + : { + connectionType: 'parts', + host: dto.DB_HOSTNAME || 'database', + port: dto.DB_PORT || 5432, + username: dto.DB_USERNAME || 'postgres', + password: dto.DB_PASSWORD || 'postgres', + database: dto.DB_DATABASE_NAME || 'immich', + }), + }, + + skipMigrations: dto.DB_SKIP_MIGRATIONS ?? false, + vectorExtension: dto.DB_VECTOR_EXTENSION === 'pgvector' ? DatabaseExtension.VECTOR : DatabaseExtension.VECTORS, }, licensePublicKey: isProd ? productionKeys : stagingKeys, network: { - trustedProxies: (process.env.IMMICH_TRUSTED_PROXIES ?? '') - .split(',') - .map((value) => value.trim()) - .filter(Boolean), + trustedProxies: dto.IMMICH_TRUSTED_PROXIES ?? [], }, otel: { metrics: { - hostMetrics, + hostMetrics: telemetries.has(ImmichTelemetry.HOST), apiMetrics: { - enable: apiMetrics, + enable: telemetries.has(ImmichTelemetry.API), ignoreRoutes: excludePaths, }, }, @@ -162,22 +208,18 @@ const getEnv = (): EnvData => { }, storage: { - ignoreMountCheckErrors: process.env.IMMICH_IGNORE_MOUNT_CHECK_ERRORS === 'true', + ignoreMountCheckErrors: !!dto.IMMICH_IGNORE_MOUNT_CHECK_ERRORS, }, telemetry: { - apiPort: Number(process.env.IMMICH_API_METRICS_PORT || '') || 8081, - microservicesPort: Number(process.env.IMMICH_MICROSERVICES_METRICS_PORT || '') || 8082, - enabled: telemetryEnabled, - hostMetrics, - apiMetrics, - repoMetrics, - jobMetrics, + apiPort: dto.IMMICH_API_METRICS_PORT || 8081, + microservicesPort: dto.IMMICH_MICROSERVICES_METRICS_PORT || 8082, + metrics: telemetries, }, workers, - noColor: !!process.env.NO_COLOR, + noColor: !!dto.NO_COLOR, }; }; @@ -186,6 +228,8 @@ let cached: EnvData | undefined; @Injectable() @Telemetry({ enabled: false }) export class ConfigRepository implements IConfigRepository { + constructor(@Inject(IWorker) @Optional() private worker?: ImmichWorker) {} + getEnv(): EnvData { if (!cached) { cached = getEnv(); @@ -193,6 +237,10 @@ export class ConfigRepository implements IConfigRepository { return cached; } + + getWorker() { + return this.worker; + } } export const clearEnvCache = () => (cached = undefined); diff --git a/server/src/repositories/cron.repository.ts b/server/src/repositories/cron.repository.ts new file mode 100644 index 0000000000000..fd7589a0343d9 --- /dev/null +++ b/server/src/repositories/cron.repository.ts @@ -0,0 +1,52 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { SchedulerRegistry } from '@nestjs/schedule'; +import { CronJob, CronTime } from 'cron'; +import { CronCreate, CronUpdate, ICronRepository } from 'src/interfaces/cron.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; + +@Injectable() +export class CronRepository implements ICronRepository { + constructor( + private schedulerRegistry: SchedulerRegistry, + @Inject(ILoggerRepository) private logger: ILoggerRepository, + ) { + this.logger.setContext(CronRepository.name); + } + + create({ name, expression, onTick, start = true }: CronCreate): void { + const job = new CronJob( + expression, + onTick, + // function to run onComplete + undefined, + // whether it should start directly + start, + // timezone + undefined, + // context + undefined, + // runOnInit + undefined, + // utcOffset + undefined, + // prevents memory leaking by automatically stopping when the node process finishes + true, + ); + + this.schedulerRegistry.addCronJob(name, job); + } + + update({ name, expression, start }: CronUpdate): void { + const job = this.schedulerRegistry.getCronJob(name); + if (expression) { + job.setTime(new CronTime(expression)); + } + if (start !== undefined) { + if (start) { + job.start(); + } else { + job.stop(); + } + } + } +} diff --git a/server/src/repositories/event.repository.ts b/server/src/repositories/event.repository.ts index bb265196f9bda..4451ee09c573a 100644 --- a/server/src/repositories/event.repository.ts +++ b/server/src/repositories/event.repository.ts @@ -57,7 +57,6 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect setup({ services }: { services: ClassConstructor[] }) { const reflector = this.moduleRef.get(Reflector, { strict: false }); - const repository = this.moduleRef.get(IEventRepository); const items: Item[] = []; // discovery @@ -94,7 +93,7 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect // register by priority for (const handler of handlers) { - repository.on(handler); + this.addHandler(handler); } } @@ -134,7 +133,7 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect await client.leave(client.nsp.name); } - on(item: EventItem): void { + private addHandler(item: EventItem): void { const event = item.event; if (!this.emitHandlers[event]) { diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index 94a0212204740..eb6a5d6f71893 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -6,6 +6,7 @@ import { IKeyRepository } from 'src/interfaces/api-key.interface'; import { IAssetRepository } from 'src/interfaces/asset.interface'; import { IAuditRepository } from 'src/interfaces/audit.interface'; import { IConfigRepository } from 'src/interfaces/config.interface'; +import { ICronRepository } from 'src/interfaces/cron.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; @@ -22,6 +23,7 @@ import { INotificationRepository } from 'src/interfaces/notification.interface'; import { IOAuthRepository } from 'src/interfaces/oauth.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; +import { IProcessRepository } from 'src/interfaces/process.interface'; import { ISearchRepository } from 'src/interfaces/search.interface'; import { IServerInfoRepository } from 'src/interfaces/server-info.interface'; import { ISessionRepository } from 'src/interfaces/session.interface'; @@ -43,6 +45,7 @@ import { ApiKeyRepository } from 'src/repositories/api-key.repository'; import { AssetRepository } from 'src/repositories/asset.repository'; import { AuditRepository } from 'src/repositories/audit.repository'; import { ConfigRepository } from 'src/repositories/config.repository'; +import { CronRepository } from 'src/repositories/cron.repository'; import { CryptoRepository } from 'src/repositories/crypto.repository'; import { DatabaseRepository } from 'src/repositories/database.repository'; import { EventRepository } from 'src/repositories/event.repository'; @@ -59,6 +62,7 @@ import { NotificationRepository } from 'src/repositories/notification.repository import { OAuthRepository } from 'src/repositories/oauth.repository'; import { PartnerRepository } from 'src/repositories/partner.repository'; import { PersonRepository } from 'src/repositories/person.repository'; +import { ProcessRepository } from 'src/repositories/process.repository'; import { SearchRepository } from 'src/repositories/search.repository'; import { ServerInfoRepository } from 'src/repositories/server-info.repository'; import { SessionRepository } from 'src/repositories/session.repository'; @@ -81,6 +85,7 @@ export const repositories = [ { provide: IAssetRepository, useClass: AssetRepository }, { provide: IAuditRepository, useClass: AuditRepository }, { provide: IConfigRepository, useClass: ConfigRepository }, + { provide: ICronRepository, useClass: CronRepository }, { provide: ICryptoRepository, useClass: CryptoRepository }, { provide: IDatabaseRepository, useClass: DatabaseRepository }, { provide: IEventRepository, useClass: EventRepository }, @@ -98,6 +103,7 @@ export const repositories = [ { provide: IOAuthRepository, useClass: OAuthRepository }, { provide: IPartnerRepository, useClass: PartnerRepository }, { provide: IPersonRepository, useClass: PersonRepository }, + { provide: IProcessRepository, useClass: ProcessRepository }, { provide: ISearchRepository, useClass: SearchRepository }, { provide: IServerInfoRepository, useClass: ServerInfoRepository }, { provide: ISessionRepository, useClass: SessionRepository }, diff --git a/server/src/repositories/job.repository.ts b/server/src/repositories/job.repository.ts index 2b783e7d2f614..c6c2947617c4f 100644 --- a/server/src/repositories/job.repository.ts +++ b/server/src/repositories/job.repository.ts @@ -1,158 +1,121 @@ import { getQueueToken } from '@nestjs/bullmq'; import { Inject, Injectable } from '@nestjs/common'; -import { ModuleRef } from '@nestjs/core'; +import { ModuleRef, Reflector } from '@nestjs/core'; import { SchedulerRegistry } from '@nestjs/schedule'; -import { Job, JobsOptions, Processor, Queue, Worker, WorkerOptions } from 'bullmq'; -import { CronJob, CronTime } from 'cron'; +import { JobsOptions, Queue, Worker } from 'bullmq'; +import { ClassConstructor } from 'class-transformer'; import { setTimeout } from 'node:timers/promises'; +import { JobConfig } from 'src/decorators'; +import { MetadataKey } from 'src/enum'; import { IConfigRepository } from 'src/interfaces/config.interface'; +import { IEventRepository } from 'src/interfaces/event.interface'; import { IEntityJob, IJobRepository, JobCounts, JobItem, JobName, + JobOf, + JobStatus, QueueCleanType, QueueName, QueueStatus, } from 'src/interfaces/job.interface'; import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { getKeyByValue, getMethodNames, ImmichStartupError } from 'src/utils/misc'; -export const JOBS_TO_QUEUE: Record = { - // misc - [JobName.ASSET_DELETION]: QueueName.BACKGROUND_TASK, - [JobName.ASSET_DELETION_CHECK]: QueueName.BACKGROUND_TASK, - [JobName.USER_DELETE_CHECK]: QueueName.BACKGROUND_TASK, - [JobName.USER_DELETION]: QueueName.BACKGROUND_TASK, - [JobName.DELETE_FILES]: QueueName.BACKGROUND_TASK, - [JobName.CLEAN_OLD_AUDIT_LOGS]: QueueName.BACKGROUND_TASK, - [JobName.CLEAN_OLD_SESSION_TOKENS]: QueueName.BACKGROUND_TASK, - [JobName.PERSON_CLEANUP]: QueueName.BACKGROUND_TASK, - [JobName.USER_SYNC_USAGE]: QueueName.BACKGROUND_TASK, - - // conversion - [JobName.QUEUE_VIDEO_CONVERSION]: QueueName.VIDEO_CONVERSION, - [JobName.VIDEO_CONVERSION]: QueueName.VIDEO_CONVERSION, - - // thumbnails - [JobName.QUEUE_GENERATE_THUMBNAILS]: QueueName.THUMBNAIL_GENERATION, - [JobName.GENERATE_THUMBNAILS]: QueueName.THUMBNAIL_GENERATION, - [JobName.GENERATE_PERSON_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION, - - // tags - [JobName.TAG_CLEANUP]: QueueName.BACKGROUND_TASK, - - // metadata - [JobName.QUEUE_METADATA_EXTRACTION]: QueueName.METADATA_EXTRACTION, - [JobName.METADATA_EXTRACTION]: QueueName.METADATA_EXTRACTION, - [JobName.LINK_LIVE_PHOTOS]: QueueName.METADATA_EXTRACTION, - - // storage template - [JobName.STORAGE_TEMPLATE_MIGRATION]: QueueName.STORAGE_TEMPLATE_MIGRATION, - [JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE]: QueueName.STORAGE_TEMPLATE_MIGRATION, - - // migration - [JobName.QUEUE_MIGRATION]: QueueName.MIGRATION, - [JobName.MIGRATE_ASSET]: QueueName.MIGRATION, - [JobName.MIGRATE_PERSON]: QueueName.MIGRATION, - - // facial recognition - [JobName.QUEUE_FACE_DETECTION]: QueueName.FACE_DETECTION, - [JobName.FACE_DETECTION]: QueueName.FACE_DETECTION, - [JobName.QUEUE_FACIAL_RECOGNITION]: QueueName.FACIAL_RECOGNITION, - [JobName.FACIAL_RECOGNITION]: QueueName.FACIAL_RECOGNITION, - - // smart search - [JobName.QUEUE_SMART_SEARCH]: QueueName.SMART_SEARCH, - [JobName.SMART_SEARCH]: QueueName.SMART_SEARCH, - - // duplicate detection - [JobName.QUEUE_DUPLICATE_DETECTION]: QueueName.DUPLICATE_DETECTION, - [JobName.DUPLICATE_DETECTION]: QueueName.DUPLICATE_DETECTION, - - // XMP sidecars - [JobName.QUEUE_SIDECAR]: QueueName.SIDECAR, - [JobName.SIDECAR_DISCOVERY]: QueueName.SIDECAR, - [JobName.SIDECAR_SYNC]: QueueName.SIDECAR, - [JobName.SIDECAR_WRITE]: QueueName.SIDECAR, - - // Library management - [JobName.LIBRARY_SYNC_FILE]: QueueName.LIBRARY, - [JobName.LIBRARY_QUEUE_SYNC_FILES]: QueueName.LIBRARY, - [JobName.LIBRARY_QUEUE_SYNC_ASSETS]: QueueName.LIBRARY, - [JobName.LIBRARY_DELETE]: QueueName.LIBRARY, - [JobName.LIBRARY_SYNC_ASSET]: QueueName.LIBRARY, - [JobName.LIBRARY_QUEUE_SYNC_ALL]: QueueName.LIBRARY, - [JobName.LIBRARY_QUEUE_CLEANUP]: QueueName.LIBRARY, - - // Notification - [JobName.SEND_EMAIL]: QueueName.NOTIFICATION, - [JobName.NOTIFY_ALBUM_INVITE]: QueueName.NOTIFICATION, - [JobName.NOTIFY_ALBUM_UPDATE]: QueueName.NOTIFICATION, - [JobName.NOTIFY_SIGNUP]: QueueName.NOTIFICATION, - - // Version check - [JobName.VERSION_CHECK]: QueueName.BACKGROUND_TASK, - - // Trash - [JobName.QUEUE_TRASH_EMPTY]: QueueName.BACKGROUND_TASK, +type JobMapItem = { + jobName: JobName; + queueName: QueueName; + handler: (job: JobOf) => Promise; + label: string; }; @Injectable() export class JobRepository implements IJobRepository { private workers: Partial> = {}; + private handlers: Partial> = {}; constructor( - private moduleReference: ModuleRef, - private schedulerReqistry: SchedulerRegistry, + private moduleRef: ModuleRef, + private schedulerRegistry: SchedulerRegistry, @Inject(IConfigRepository) private configRepository: IConfigRepository, + @Inject(IEventRepository) private eventRepository: IEventRepository, @Inject(ILoggerRepository) private logger: ILoggerRepository, ) { this.logger.setContext(JobRepository.name); } - addHandler(queueName: QueueName, concurrency: number, handler: (item: JobItem) => Promise) { - const { bull } = this.configRepository.getEnv(); - const workerHandler: Processor = async (job: Job) => handler(job as JobItem); - const workerOptions: WorkerOptions = { ...bull.config, concurrency }; - this.workers[queueName] = new Worker(queueName, workerHandler, workerOptions); - } + setup({ services }: { services: ClassConstructor[] }) { + const reflector = this.moduleRef.get(Reflector, { strict: false }); + + // discovery + for (const Service of services) { + const instance = this.moduleRef.get(Service); + for (const methodName of getMethodNames(instance)) { + const handler = instance[methodName]; + const config = reflector.get(MetadataKey.JOB_CONFIG, handler); + if (!config) { + continue; + } + + const { name: jobName, queue: queueName } = config; + const label = `${Service.name}.${handler.name}`; + + // one handler per job + if (this.handlers[jobName]) { + const jobKey = getKeyByValue(JobName, jobName); + const errorMessage = `Failed to add job handler for ${label}`; + this.logger.error( + `${errorMessage}. JobName.${jobKey} is already handled by ${this.handlers[jobName].label}.`, + ); + throw new ImmichStartupError(errorMessage); + } + + this.handlers[jobName] = { + label, + jobName, + queueName, + handler: handler.bind(instance), + }; + + this.logger.verbose(`Added job handler: ${jobName} => ${label}`); + } + } - addCronJob(name: string, expression: string, onTick: () => void, start = true): void { - const job = new CronJob( - expression, - onTick, - // function to run onComplete - undefined, - // whether it should start directly - start, - // timezone - undefined, - // context - undefined, - // runOnInit - undefined, - // utcOffset - undefined, - // prevents memory leaking by automatically stopping when the node process finishes - true, - ); - - this.schedulerReqistry.addCronJob(name, job); + // no missing handlers + for (const [jobKey, jobName] of Object.entries(JobName)) { + const item = this.handlers[jobName]; + if (!item) { + const errorMessage = `Failed to find job handler for Job.${jobKey} ("${jobName}")`; + this.logger.error( + `${errorMessage}. Make sure to add the @OnJob({ name: JobName.${jobKey}, queue: QueueName.XYZ }) decorator for the new job.`, + ); + throw new ImmichStartupError(errorMessage); + } + } } - updateCronJob(name: string, expression?: string, start?: boolean): void { - const job = this.schedulerReqistry.getCronJob(name); - if (expression) { - job.setTime(new CronTime(expression)); + startWorkers() { + const { bull } = this.configRepository.getEnv(); + for (const queueName of Object.values(QueueName)) { + this.logger.debug(`Starting worker for queue: ${queueName}`); + this.workers[queueName] = new Worker( + queueName, + (job) => this.eventRepository.emit('job.start', queueName, job as JobItem), + { ...bull.config, concurrency: 1 }, + ); } - if (start !== undefined) { - if (start) { - job.start(); - } else { - job.stop(); - } + } + + async run({ name, data }: JobItem) { + const item = this.handlers[name as JobName]; + if (!item) { + this.logger.warn(`Skipping unknown job: "${name}"`); + return JobStatus.SKIPPED; } + + return item.handler(data); } setConcurrency(queueName: QueueName, concurrency: number) { @@ -201,6 +164,10 @@ export class JobRepository implements IJobRepository { ) as unknown as Promise; } + private getQueueName(name: JobName) { + return (this.handlers[name] as JobMapItem).queueName; + } + async queueAll(items: JobItem[]): Promise { if (items.length === 0) { return; @@ -209,7 +176,7 @@ export class JobRepository implements IJobRepository { const promises = []; const itemsByQueue = {} as Record; for (const item of items) { - const queueName = JOBS_TO_QUEUE[item.name]; + const queueName = this.getQueueName(item.name); const job = { name: item.name, data: item.data || {}, @@ -270,11 +237,11 @@ export class JobRepository implements IJobRepository { } private getQueue(queue: QueueName): Queue { - return this.moduleReference.get(getQueueToken(queue), { strict: false }); + return this.moduleRef.get(getQueueToken(queue), { strict: false }); } public async removeJob(jobId: string, name: JobName): Promise { - const existingJob = await this.getQueue(JOBS_TO_QUEUE[name]).getJob(jobId); + const existingJob = await this.getQueue(this.getQueueName(name)).getJob(jobId); if (!existingJob) { return; } diff --git a/server/src/repositories/map.repository.ts b/server/src/repositories/map.repository.ts index 7ad94016e8667..f87ba6d0ac83c 100644 --- a/server/src/repositories/map.repository.ts +++ b/server/src/repositories/map.repository.ts @@ -249,6 +249,7 @@ export class MapRepository implements IMapRepository { const input = createReadStream(filePath); let bufferGeodata: QueryDeepPartialEntity[] = []; const lineReader = readLine.createInterface({ input }); + let count = 0; for await (const line of lineReader) { const lineSplit = line.split('\t'); @@ -257,8 +258,12 @@ export class MapRepository implements IMapRepository { } const geoData = lineToEntityMapper(lineSplit); bufferGeodata.push(geoData); - if (bufferGeodata.length > 1000) { + if (bufferGeodata.length >= 1000) { await queryRunner.manager.upsert(GeodataPlacesEntity, bufferGeodata, ['id']); + count += bufferGeodata.length; + if (count % 10_000 === 0) { + this.logger.log(`${count} geodata records imported`); + } bufferGeodata = []; } } diff --git a/server/src/repositories/media.repository.ts b/server/src/repositories/media.repository.ts index 7e1ca84993fed..8dcbf208c6ae8 100644 --- a/server/src/repositories/media.repository.ts +++ b/server/src/repositories/media.repository.ts @@ -5,6 +5,7 @@ import { Duration } from 'luxon'; import fs from 'node:fs/promises'; import { Writable } from 'node:stream'; import sharp from 'sharp'; +import { ORIENTATION_TO_SHARP_ROTATION } from 'src/constants'; import { Colorspace, LogLevel } from 'src/enum'; import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { @@ -82,7 +83,15 @@ export class MediaRepository implements IMediaRepository { .withIccProfile(options.colorspace); if (!options.raw) { - pipeline = pipeline.rotate(); + const { angle, flip, flop } = options.orientation ? ORIENTATION_TO_SHARP_ROTATION[options.orientation] : {}; + pipeline = pipeline.rotate(angle); + if (flip) { + pipeline = pipeline.flip(); + } + + if (flop) { + pipeline = pipeline.flop(); + } } if (options.crop) { @@ -110,22 +119,23 @@ export class MediaRepository implements IMediaRepository { format: { formatName: results.format.format_name, formatLongName: results.format.format_long_name, - duration: results.format.duration || 0, - bitrate: results.format.bit_rate ?? 0, + duration: this.parseFloat(results.format.duration), + bitrate: this.parseInt(results.format.bit_rate), }, videoStreams: results.streams .filter((stream) => stream.codec_type === 'video') .filter((stream) => !stream.disposition?.attached_pic) .map((stream) => ({ index: stream.index, - height: stream.height || 0, - width: stream.width || 0, + height: this.parseInt(stream.height), + width: this.parseInt(stream.width), codecName: stream.codec_name === 'h265' ? 'hevc' : stream.codec_name, codecType: stream.codec_type, frameCount: this.parseInt(options?.countFrames ? stream.nb_read_packets : stream.nb_frames), rotation: this.parseInt(stream.rotation), isHDR: stream.color_transfer === 'smpte2084' || stream.color_transfer === 'arib-std-b67', bitrate: this.parseInt(stream.bit_rate), + pixelFormat: stream.pix_fmt || 'yuv420p', })), audioStreams: results.streams .filter((stream) => stream.codec_type === 'audio') @@ -215,4 +225,8 @@ export class MediaRepository implements IMediaRepository { private parseInt(value: string | number | undefined): number { return Number.parseInt(value as string) || 0; } + + private parseFloat(value: string | number | undefined): number { + return Number.parseFloat(value as string) || 0; + } } diff --git a/server/src/repositories/notification.repository.spec.ts b/server/src/repositories/notification.repository.spec.ts new file mode 100644 index 0000000000000..983be21d2b905 --- /dev/null +++ b/server/src/repositories/notification.repository.spec.ts @@ -0,0 +1,74 @@ +import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { EmailRenderRequest, EmailTemplate } from 'src/interfaces/notification.interface'; +import { NotificationRepository } from 'src/repositories/notification.repository'; +import { Mocked } from 'vitest'; + +describe(NotificationRepository.name, () => { + let sut: NotificationRepository; + let loggerMock: Mocked; + + beforeEach(() => { + loggerMock = { + setContext: vitest.fn(), + debug: vitest.fn(), + } as unknown as Mocked; + + sut = new NotificationRepository(loggerMock); + }); + + describe('renderEmail', () => { + it('should render the email correctly for TEST_EMAIL template', async () => { + const request: EmailRenderRequest = { + template: EmailTemplate.TEST_EMAIL, + data: { displayName: 'Alen Turing', baseUrl: 'http://localhost' }, + }; + + const result = await sut.renderEmail(request); + + expect(result.html).toContain(' { + const request: EmailRenderRequest = { + template: EmailTemplate.WELCOME, + data: { displayName: 'Alen Turing', username: 'turing', baseUrl: 'http://localhost' }, + }; + + const result = await sut.renderEmail(request); + + expect(result.html).toContain(' { + const request: EmailRenderRequest = { + template: EmailTemplate.ALBUM_INVITE, + data: { + albumName: 'Vacation', + albumId: '123', + senderName: 'John', + recipientName: 'Jane', + baseUrl: 'http://localhost', + }, + }; + + const result = await sut.renderEmail(request); + + expect(result.html).toContain(' { + const request: EmailRenderRequest = { + template: EmailTemplate.ALBUM_UPDATE, + data: { albumName: 'Holiday', albumId: '123', recipientName: 'Jane', baseUrl: 'http://localhost' }, + }; + + const result = await sut.renderEmail(request); + + expect(result.html).toContain(', @InjectRepository(AssetEntity) private assetRepository: Repository, @InjectRepository(ExifEntity) private exifRepository: Repository, @InjectRepository(AssetFaceEntity) private assetFaceRepository: Repository, @@ -278,7 +276,7 @@ export class SearchRepository implements ISearchRepository { @GenerateSql({ params: [[DummyValue.UUID]] }) async getAssetsByCity(userIds: string[]): Promise { const parameters = [userIds, true, false, AssetType.IMAGE]; - const rawRes = await this.repository.query(this.assetsByCityQuery, parameters); + const rawRes = await this.assetRepository.query(this.assetsByCityQuery, parameters); const items: AssetEntity[] = []; for (const res of rawRes) { diff --git a/server/src/repositories/stack.repository.ts b/server/src/repositories/stack.repository.ts index ae1a7f70d4a3a..9e9f09c4429ca 100644 --- a/server/src/repositories/stack.repository.ts +++ b/server/src/repositories/stack.repository.ts @@ -127,6 +127,7 @@ export class StackRepository implements IStackRepository { relations: { assets: { exifInfo: true, + tags: true, }, }, order: { diff --git a/server/src/repositories/storage.repository.ts b/server/src/repositories/storage.repository.ts index 1ef0e9d6bf1f7..e4c0c68451477 100644 --- a/server/src/repositories/storage.repository.ts +++ b/server/src/repositories/storage.repository.ts @@ -2,9 +2,10 @@ import { Inject, Injectable } from '@nestjs/common'; import archiver from 'archiver'; import chokidar, { WatchOptions } from 'chokidar'; import { escapePath, glob, globStream } from 'fast-glob'; -import { constants, createReadStream, existsSync, mkdirSync } from 'node:fs'; +import { constants, createReadStream, createWriteStream, existsSync, mkdirSync } from 'node:fs'; import fs from 'node:fs/promises'; import path from 'node:path'; +import { Writable } from 'node:stream'; import { CrawlOptionsDto, WalkOptionsDto } from 'src/dtos/library.dto'; import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { @@ -42,6 +43,10 @@ export class StorageRepository implements IStorageRepository { return fs.writeFile(filepath, buffer, { flag: 'wx' }); } + createWriteStream(filepath: string): Writable { + return createWriteStream(filepath, { flags: 'w' }); + } + createOrOverwriteFile(filepath: string, buffer: Buffer) { return fs.writeFile(filepath, buffer, { flag: 'w' }); } diff --git a/server/src/repositories/telemetry.repository.ts b/server/src/repositories/telemetry.repository.ts index f450c162dcdd2..25104609671c8 100644 --- a/server/src/repositories/telemetry.repository.ts +++ b/server/src/repositories/telemetry.repository.ts @@ -14,7 +14,7 @@ import { snakeCase, startCase } from 'lodash'; import { MetricService } from 'nestjs-otel'; import { copyMetadataFromFunctionToFunction } from 'nestjs-otel/lib/opentelemetry.utils'; import { serverVersion } from 'src/constants'; -import { MetadataKey } from 'src/enum'; +import { ImmichTelemetry, MetadataKey } from 'src/enum'; import { IConfigRepository } from 'src/interfaces/config.interface'; import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMetricGroupRepository, ITelemetryRepository, MetricGroupOptions } from 'src/interfaces/telemetry.interface'; @@ -99,17 +99,18 @@ export class TelemetryRepository implements ITelemetryRepository { @Inject(ILoggerRepository) private logger: ILoggerRepository, ) { const { telemetry } = this.configRepository.getEnv(); - const { apiMetrics, hostMetrics, jobMetrics, repoMetrics } = telemetry; + const { metrics } = telemetry; - this.api = new MetricGroupRepository(metricService).configure({ enabled: apiMetrics }); - this.host = new MetricGroupRepository(metricService).configure({ enabled: hostMetrics }); - this.jobs = new MetricGroupRepository(metricService).configure({ enabled: jobMetrics }); - this.repo = new MetricGroupRepository(metricService).configure({ enabled: repoMetrics }); + this.api = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.API) }); + this.host = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.HOST) }); + this.jobs = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.JOB) }); + this.repo = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.REPO) }); } setup({ repositories }: { repositories: ClassConstructor[] }) { const { telemetry } = this.configRepository.getEnv(); - if (!telemetry.enabled || !telemetry.repoMetrics) { + const { metrics } = telemetry; + if (!metrics.has(ImmichTelemetry.REPO)) { return; } diff --git a/server/src/repositories/user.repository.ts b/server/src/repositories/user.repository.ts index 6ac8536ef8a79..a2e4375701a2a 100644 --- a/server/src/repositories/user.repository.ts +++ b/server/src/repositories/user.repository.ts @@ -108,6 +108,14 @@ export class UserRepository implements IUserRepository { .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'IMAGE' AND assets.isVisible)`, 'photos') .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'VIDEO' AND assets.isVisible)`, 'videos') .addSelect('COALESCE(SUM(exif.fileSizeInByte) FILTER (WHERE assets.libraryId IS NULL), 0)', 'usage') + .addSelect( + `COALESCE(SUM(exif.fileSizeInByte) FILTER (WHERE assets.libraryId IS NULL AND assets.type = 'IMAGE'), 0)`, + 'usagePhotos', + ) + .addSelect( + `COALESCE(SUM(exif.fileSizeInByte) FILTER (WHERE assets.libraryId IS NULL AND assets.type = 'VIDEO'), 0)`, + 'usageVideos', + ) .addSelect('users.quotaSizeInBytes', 'quotaSizeInBytes') .leftJoin('users.assets', 'assets') .leftJoin('assets.exifInfo', 'exif') @@ -119,6 +127,8 @@ export class UserRepository implements IUserRepository { stat.photos = Number(stat.photos); stat.videos = Number(stat.videos); stat.usage = Number(stat.usage); + stat.usagePhotos = Number(stat.usagePhotos); + stat.usageVideos = Number(stat.usageVideos); stat.quotaSizeInBytes = stat.quotaSizeInBytes; } diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index 2f31806e81444..98d6ec00f695f 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -1,6 +1,7 @@ import { BadRequestException } from '@nestjs/common'; import _ from 'lodash'; import { DateTime, Duration } from 'luxon'; +import { OnJob } from 'src/decorators'; import { AssetResponseDto, MemoryLaneResponseDto, @@ -21,12 +22,13 @@ import { MemoryLaneDto } from 'src/dtos/search.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { AssetStatus, Permission } from 'src/enum'; import { - IAssetDeleteJob, ISidecarWriteJob, JOBS_ASSET_PAGINATION_SIZE, JobItem, JobName, + JobOf, JobStatus, + QueueName, } from 'src/interfaces/job.interface'; import { BaseService } from 'src/services/base.service'; import { getAssetFiles, getMyPartnerIds, onAfterUnlink, onBeforeLink, onBeforeUnlink } from 'src/utils/asset.util'; @@ -92,7 +94,6 @@ export class AssetService extends BaseService { { exifInfo: true, sharedLinks: true, - smartInfo: true, tags: true, owner: true, faces: { @@ -160,7 +161,6 @@ export class AssetService extends BaseService { const asset = await this.assetRepository.getById(id, { exifInfo: true, owner: true, - smartInfo: true, tags: true, faces: { person: true, @@ -186,6 +186,7 @@ export class AssetService extends BaseService { await this.assetRepository.updateAll(ids, options); } + @OnJob({ name: JobName.ASSET_DELETION_CHECK, queue: QueueName.BACKGROUND_TASK }) async handleAssetDeletionCheck(): Promise { const config = await this.getConfig({ withCache: false }); const trashedDays = config.trash.enabled ? config.trash.days : 0; @@ -211,7 +212,8 @@ export class AssetService extends BaseService { return JobStatus.SUCCESS; } - async handleAssetDeletion(job: IAssetDeleteJob): Promise { + @OnJob({ name: JobName.ASSET_DELETION, queue: QueueName.BACKGROUND_TASK }) + async handleAssetDeletion(job: JobOf): Promise { const { id, deleteOnDisk } = job; const asset = await this.assetRepository.getById(id, { diff --git a/server/src/services/audit.service.ts b/server/src/services/audit.service.ts index d891c88b3911c..3fc838e5e90ef 100644 --- a/server/src/services/audit.service.ts +++ b/server/src/services/audit.service.ts @@ -3,6 +3,7 @@ import { DateTime } from 'luxon'; import { resolve } from 'node:path'; import { AUDIT_LOG_MAX_DURATION } from 'src/constants'; import { StorageCore } from 'src/cores/storage.core'; +import { OnJob } from 'src/decorators'; import { AuditDeletesDto, AuditDeletesResponseDto, @@ -21,13 +22,14 @@ import { StorageFolder, UserPathType, } from 'src/enum'; -import { JOBS_ASSET_PAGINATION_SIZE, JobStatus } from 'src/interfaces/job.interface'; +import { JobName, JOBS_ASSET_PAGINATION_SIZE, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { BaseService } from 'src/services/base.service'; import { getAssetFiles } from 'src/utils/asset.util'; import { usePagination } from 'src/utils/pagination'; @Injectable() export class AuditService extends BaseService { + @OnJob({ name: JobName.CLEAN_OLD_AUDIT_LOGS, queue: QueueName.BACKGROUND_TASK }) async handleCleanup(): Promise { await this.auditRepository.removeBefore(DateTime.now().minus(AUDIT_LOG_MAX_DURATION).toJSDate()); return JobStatus.SUCCESS; diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index 00324c909c5df..b0094ae9edba9 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -23,7 +23,6 @@ import { OAuthProfile } from 'src/interfaces/oauth.interface'; import { BaseService } from 'src/services/base.service'; import { isGranted } from 'src/utils/access'; import { HumanReadableSize } from 'src/utils/bytes'; -import { createUser } from 'src/utils/user'; export interface LoginDetails { isSecure: boolean; @@ -115,16 +114,13 @@ export class AuthService extends BaseService { throw new BadRequestException('The server already has an admin'); } - const admin = await createUser( - { userRepo: this.userRepository, cryptoRepo: this.cryptoRepository }, - { - isAdmin: true, - email: dto.email, - name: dto.name, - password: dto.password, - storageLabel: 'admin', - }, - ); + const admin = await this.createUser({ + isAdmin: true, + email: dto.email, + name: dto.name, + password: dto.password, + storageLabel: 'admin', + }); return mapUserAdmin(admin); } @@ -234,16 +230,13 @@ export class AuthService extends BaseService { }); const userName = profile.name ?? `${profile.given_name || ''} ${profile.family_name || ''}`; - user = await createUser( - { userRepo: this.userRepository, cryptoRepo: this.cryptoRepository }, - { - name: userName, - email: profile.email, - oauthId: profile.sub, - quotaSizeInBytes: storageQuota * HumanReadableSize.GiB || null, - storageLabel: storageLabel || null, - }, - ); + user = await this.createUser({ + name: userName, + email: profile.email, + oauthId: profile.sub, + quotaSizeInBytes: storageQuota * HumanReadableSize.GiB || null, + storageLabel: storageLabel || null, + }); } return this.createLoginResponse(user, loginDetails); diff --git a/server/src/services/backup.service.spec.ts b/server/src/services/backup.service.spec.ts new file mode 100644 index 0000000000000..41ba7c2153e1e --- /dev/null +++ b/server/src/services/backup.service.spec.ts @@ -0,0 +1,230 @@ +import { PassThrough } from 'node:stream'; +import { defaults, SystemConfig } from 'src/config'; +import { StorageCore } from 'src/cores/storage.core'; +import { ImmichWorker, StorageFolder } from 'src/enum'; +import { IConfigRepository } from 'src/interfaces/config.interface'; +import { ICronRepository } from 'src/interfaces/cron.interface'; +import { IDatabaseRepository } from 'src/interfaces/database.interface'; +import { JobStatus } from 'src/interfaces/job.interface'; +import { IProcessRepository } from 'src/interfaces/process.interface'; +import { IStorageRepository } from 'src/interfaces/storage.interface'; +import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; +import { BackupService } from 'src/services/backup.service'; +import { systemConfigStub } from 'test/fixtures/system-config.stub'; +import { mockSpawn, newTestService } from 'test/utils'; +import { describe, Mocked } from 'vitest'; + +describe(BackupService.name, () => { + let sut: BackupService; + + let databaseMock: Mocked; + let configMock: Mocked; + let cronMock: Mocked; + let processMock: Mocked; + let storageMock: Mocked; + let systemMock: Mocked; + + beforeEach(() => { + ({ sut, cronMock, configMock, databaseMock, processMock, storageMock, systemMock } = newTestService(BackupService)); + }); + + it('should work', () => { + expect(sut).toBeDefined(); + }); + + describe('onBootstrapEvent', () => { + it('should init cron job and handle config changes', async () => { + databaseMock.tryLock.mockResolvedValue(true); + + await sut.onConfigInit({ newConfig: systemConfigStub.backupEnabled as SystemConfig }); + + expect(cronMock.create).toHaveBeenCalled(); + }); + + it('should not initialize backup database cron job when lock is taken', async () => { + databaseMock.tryLock.mockResolvedValue(false); + + await sut.onConfigInit({ newConfig: systemConfigStub.backupEnabled as SystemConfig }); + + expect(cronMock.create).not.toHaveBeenCalled(); + }); + + it('should not initialise backup database job when running on microservices', async () => { + configMock.getWorker.mockReturnValue(ImmichWorker.MICROSERVICES); + await sut.onConfigInit({ newConfig: systemConfigStub.backupEnabled as SystemConfig }); + + expect(cronMock.create).not.toHaveBeenCalled(); + }); + }); + + describe('onConfigUpdateEvent', () => { + beforeEach(async () => { + databaseMock.tryLock.mockResolvedValue(true); + await sut.onConfigInit({ newConfig: defaults }); + }); + + it('should update cron job if backup is enabled', () => { + sut.onConfigUpdate({ + oldConfig: defaults, + newConfig: { + backup: { + database: { + enabled: true, + cronExpression: '0 1 * * *', + }, + }, + } as SystemConfig, + }); + + expect(cronMock.update).toHaveBeenCalledWith({ name: 'backupDatabase', expression: '0 1 * * *', start: true }); + expect(cronMock.update).toHaveBeenCalled(); + }); + + it('should do nothing if instance does not have the backup database lock', async () => { + databaseMock.tryLock.mockResolvedValue(false); + await sut.onConfigInit({ newConfig: defaults }); + sut.onConfigUpdate({ newConfig: systemConfigStub.backupEnabled as SystemConfig, oldConfig: defaults }); + expect(cronMock.update).not.toHaveBeenCalled(); + }); + }); + + describe('cleanupDatabaseBackups', () => { + it('should do nothing if not reached keepLastAmount', async () => { + systemMock.get.mockResolvedValue(systemConfigStub.backupEnabled); + storageMock.readdir.mockResolvedValue(['immich-db-backup-1.sql.gz']); + await sut.cleanupDatabaseBackups(); + expect(storageMock.unlink).not.toHaveBeenCalled(); + }); + + it('should remove failed backup files', async () => { + systemMock.get.mockResolvedValue(systemConfigStub.backupEnabled); + storageMock.readdir.mockResolvedValue([ + 'immich-db-backup-123.sql.gz.tmp', + 'immich-db-backup-234.sql.gz', + 'immich-db-backup-345.sql.gz.tmp', + ]); + await sut.cleanupDatabaseBackups(); + expect(storageMock.unlink).toHaveBeenCalledTimes(2); + expect(storageMock.unlink).toHaveBeenCalledWith( + `${StorageCore.getBaseFolder(StorageFolder.BACKUPS)}/immich-db-backup-123.sql.gz.tmp`, + ); + expect(storageMock.unlink).toHaveBeenCalledWith( + `${StorageCore.getBaseFolder(StorageFolder.BACKUPS)}/immich-db-backup-345.sql.gz.tmp`, + ); + }); + + it('should remove old backup files over keepLastAmount', async () => { + systemMock.get.mockResolvedValue(systemConfigStub.backupEnabled); + storageMock.readdir.mockResolvedValue(['immich-db-backup-1.sql.gz', 'immich-db-backup-2.sql.gz']); + await sut.cleanupDatabaseBackups(); + expect(storageMock.unlink).toHaveBeenCalledTimes(1); + expect(storageMock.unlink).toHaveBeenCalledWith( + `${StorageCore.getBaseFolder(StorageFolder.BACKUPS)}/immich-db-backup-1.sql.gz`, + ); + }); + + it('should remove old backup files over keepLastAmount and failed backups', async () => { + systemMock.get.mockResolvedValue(systemConfigStub.backupEnabled); + storageMock.readdir.mockResolvedValue([ + 'immich-db-backup-1.sql.gz.tmp', + 'immich-db-backup-2.sql.gz', + 'immich-db-backup-3.sql.gz', + ]); + await sut.cleanupDatabaseBackups(); + expect(storageMock.unlink).toHaveBeenCalledTimes(2); + expect(storageMock.unlink).toHaveBeenCalledWith( + `${StorageCore.getBaseFolder(StorageFolder.BACKUPS)}/immich-db-backup-1.sql.gz.tmp`, + ); + expect(storageMock.unlink).toHaveBeenCalledWith( + `${StorageCore.getBaseFolder(StorageFolder.BACKUPS)}/immich-db-backup-2.sql.gz`, + ); + }); + }); + + describe('handleBackupDatabase', () => { + beforeEach(() => { + storageMock.readdir.mockResolvedValue([]); + processMock.spawn.mockReturnValue(mockSpawn(0, 'data', '')); + storageMock.rename.mockResolvedValue(); + storageMock.unlink.mockResolvedValue(); + systemMock.get.mockResolvedValue(systemConfigStub.backupEnabled); + storageMock.createWriteStream.mockReturnValue(new PassThrough()); + }); + it('should run a database backup successfully', async () => { + const result = await sut.handleBackupDatabase(); + expect(result).toBe(JobStatus.SUCCESS); + expect(storageMock.createWriteStream).toHaveBeenCalled(); + }); + it('should rename file on success', async () => { + const result = await sut.handleBackupDatabase(); + expect(result).toBe(JobStatus.SUCCESS); + expect(storageMock.rename).toHaveBeenCalled(); + }); + it('should fail if pg_dumpall fails', async () => { + processMock.spawn.mockReturnValueOnce(mockSpawn(1, '', 'error')); + const result = await sut.handleBackupDatabase(); + expect(result).toBe(JobStatus.FAILED); + }); + it('should not rename file if pgdump fails and gzip succeeds', async () => { + processMock.spawn.mockReturnValueOnce(mockSpawn(1, '', 'error')); + const result = await sut.handleBackupDatabase(); + expect(result).toBe(JobStatus.FAILED); + expect(storageMock.rename).not.toHaveBeenCalled(); + }); + it('should fail if gzip fails', async () => { + processMock.spawn.mockReturnValueOnce(mockSpawn(0, 'data', '')); + processMock.spawn.mockReturnValueOnce(mockSpawn(1, '', 'error')); + const result = await sut.handleBackupDatabase(); + expect(result).toBe(JobStatus.FAILED); + }); + it('should fail if write stream fails', async () => { + storageMock.createWriteStream.mockImplementation(() => { + throw new Error('error'); + }); + const result = await sut.handleBackupDatabase(); + expect(result).toBe(JobStatus.FAILED); + }); + it('should fail if rename fails', async () => { + storageMock.rename.mockRejectedValue(new Error('error')); + const result = await sut.handleBackupDatabase(); + expect(result).toBe(JobStatus.FAILED); + }); + it('should ignore unlink failing and still return failed job status', async () => { + processMock.spawn.mockReturnValueOnce(mockSpawn(1, '', 'error')); + storageMock.unlink.mockRejectedValue(new Error('error')); + const result = await sut.handleBackupDatabase(); + expect(storageMock.unlink).toHaveBeenCalled(); + expect(result).toBe(JobStatus.FAILED); + }); + it.each` + postgresVersion | expectedVersion + ${'14.10'} | ${14} + ${'14.10.3'} | ${14} + ${'14.10 (Debian 14.10-1.pgdg120+1)'} | ${14} + ${'15.3.3'} | ${15} + ${'16.4.2'} | ${16} + ${'17.15.1'} | ${17} + `( + `should use pg_dumpall $expectedVersion with postgres version $postgresVersion`, + async ({ postgresVersion, expectedVersion }) => { + databaseMock.getPostgresVersion.mockResolvedValue(postgresVersion); + await sut.handleBackupDatabase(); + expect(processMock.spawn).toHaveBeenCalledWith( + `/usr/lib/postgresql/${expectedVersion}/bin/pg_dumpall`, + expect.any(Array), + expect.any(Object), + ); + }, + ); + it.each` + postgresVersion + ${'13.99.99'} + ${'18.0.0'} + `(`should fail if postgres version $postgresVersion is not supported`, async ({ postgresVersion }) => { + databaseMock.getPostgresVersion.mockResolvedValue(postgresVersion); + const result = await sut.handleBackupDatabase(); + expect(processMock.spawn).not.toHaveBeenCalled(); + expect(result).toBe(JobStatus.FAILED); + }); + }); +}); diff --git a/server/src/services/backup.service.ts b/server/src/services/backup.service.ts new file mode 100644 index 0000000000000..daa7d180f1367 --- /dev/null +++ b/server/src/services/backup.service.ts @@ -0,0 +1,192 @@ +import { Injectable } from '@nestjs/common'; +import { default as path } from 'node:path'; +import semver from 'semver'; +import { StorageCore } from 'src/cores/storage.core'; +import { OnEvent, OnJob } from 'src/decorators'; +import { ImmichWorker, StorageFolder } from 'src/enum'; +import { DatabaseLock } from 'src/interfaces/database.interface'; +import { ArgOf } from 'src/interfaces/event.interface'; +import { JobName, JobStatus, QueueName } from 'src/interfaces/job.interface'; +import { BaseService } from 'src/services/base.service'; +import { handlePromiseError } from 'src/utils/misc'; + +@Injectable() +export class BackupService extends BaseService { + private backupLock = false; + + @OnEvent({ name: 'config.init' }) + async onConfigInit({ + newConfig: { + backup: { database }, + }, + }: ArgOf<'config.init'>) { + if (this.worker !== ImmichWorker.API) { + return; + } + + this.backupLock = await this.databaseRepository.tryLock(DatabaseLock.BackupDatabase); + + if (this.backupLock) { + this.cronRepository.create({ + name: 'backupDatabase', + expression: database.cronExpression, + onTick: () => handlePromiseError(this.jobRepository.queue({ name: JobName.BACKUP_DATABASE }), this.logger), + start: database.enabled, + }); + } + } + + @OnEvent({ name: 'config.update', server: true }) + onConfigUpdate({ newConfig: { backup } }: ArgOf<'config.update'>) { + if (!this.backupLock) { + return; + } + + this.cronRepository.update({ + name: 'backupDatabase', + expression: backup.database.cronExpression, + start: backup.database.enabled, + }); + } + + async cleanupDatabaseBackups() { + this.logger.debug(`Database Backup Cleanup Started`); + const { + backup: { database: config }, + } = await this.getConfig({ withCache: false }); + + const backupsFolder = StorageCore.getBaseFolder(StorageFolder.BACKUPS); + const files = await this.storageRepository.readdir(backupsFolder); + const failedBackups = files.filter((file) => file.match(/immich-db-backup-\d+\.sql\.gz\.tmp$/)); + const backups = files + .filter((file) => file.match(/immich-db-backup-\d+\.sql\.gz$/)) + .sort() + .reverse(); + + const toDelete = backups.slice(config.keepLastAmount); + toDelete.push(...failedBackups); + + for (const file of toDelete) { + await this.storageRepository.unlink(path.join(backupsFolder, file)); + } + this.logger.debug(`Database Backup Cleanup Finished, deleted ${toDelete.length} backups`); + } + + @OnJob({ name: JobName.BACKUP_DATABASE, queue: QueueName.BACKUP_DATABASE }) + async handleBackupDatabase(): Promise { + this.logger.debug(`Database Backup Started`); + + const { + database: { config }, + } = this.configRepository.getEnv(); + + const isUrlConnection = config.connectionType === 'url'; + + const databaseParams = isUrlConnection + ? ['--dbname', config.url] + : [ + '--username', + config.username, + '--host', + config.host, + '--port', + `${config.port}`, + '--database', + config.database, + ]; + + databaseParams.push('--clean', '--if-exists'); + + const backupFilePath = path.join( + StorageCore.getBaseFolder(StorageFolder.BACKUPS), + `immich-db-backup-${Date.now()}.sql.gz.tmp`, + ); + + const databaseVersion = await this.databaseRepository.getPostgresVersion(); + const databaseSemver = semver.coerce(databaseVersion); + const databaseMajorVersion = databaseSemver?.major; + + if (!databaseMajorVersion || !databaseSemver || !semver.satisfies(databaseSemver, '>=14.0.0 <18.0.0')) { + this.logger.error(`Database Backup Failure: Unsupported PostgreSQL version: ${databaseVersion}`); + return JobStatus.FAILED; + } + + this.logger.log(`Database Backup Starting. Database Version: ${databaseMajorVersion}`); + + try { + await new Promise((resolve, reject) => { + const pgdump = this.processRepository.spawn( + `/usr/lib/postgresql/${databaseMajorVersion}/bin/pg_dumpall`, + databaseParams, + { + env: { + PATH: process.env.PATH, + PGPASSWORD: isUrlConnection ? undefined : config.password, + }, + }, + ); + + // NOTE: `--rsyncable` is only supported in GNU gzip + const gzip = this.processRepository.spawn(`gzip`, ['--rsyncable']); + pgdump.stdout.pipe(gzip.stdin); + + const fileStream = this.storageRepository.createWriteStream(backupFilePath); + + gzip.stdout.pipe(fileStream); + + pgdump.on('error', (err) => { + this.logger.error('Backup failed with error', err); + reject(err); + }); + + gzip.on('error', (err) => { + this.logger.error('Gzip failed with error', err); + reject(err); + }); + + let pgdumpLogs = ''; + let gzipLogs = ''; + + pgdump.stderr.on('data', (data) => (pgdumpLogs += data)); + gzip.stderr.on('data', (data) => (gzipLogs += data)); + + pgdump.on('exit', (code) => { + if (code !== 0) { + this.logger.error(`Backup failed with code ${code}`); + reject(`Backup failed with code ${code}`); + this.logger.error(pgdumpLogs); + return; + } + if (pgdumpLogs) { + this.logger.debug(`pgdump_all logs\n${pgdumpLogs}`); + } + }); + + gzip.on('exit', (code) => { + if (code !== 0) { + this.logger.error(`Gzip failed with code ${code}`); + reject(`Gzip failed with code ${code}`); + this.logger.error(gzipLogs); + return; + } + if (pgdump.exitCode !== 0) { + this.logger.error(`Gzip exited with code 0 but pgdump exited with ${pgdump.exitCode}`); + return; + } + resolve(); + }); + }); + await this.storageRepository.rename(backupFilePath, backupFilePath.replace('.tmp', '')); + } catch (error) { + this.logger.error('Database Backup Failure', error); + await this.storageRepository + .unlink(backupFilePath) + .catch((error) => this.logger.error('Failed to delete failed backup file', error)); + return JobStatus.FAILED; + } + + this.logger.log(`Database Backup Success`); + await this.cleanupDatabaseBackups(); + return JobStatus.SUCCESS; + } +} diff --git a/server/src/services/base.service.ts b/server/src/services/base.service.ts index 441a81cf91031..3630d69c1804f 100644 --- a/server/src/services/base.service.ts +++ b/server/src/services/base.service.ts @@ -1,6 +1,9 @@ -import { Inject } from '@nestjs/common'; +import { BadRequestException, Inject } from '@nestjs/common'; +import sanitize from 'sanitize-filename'; import { SystemConfig } from 'src/config'; +import { SALT_ROUNDS } from 'src/constants'; import { StorageCore } from 'src/cores/storage.core'; +import { UserEntity } from 'src/entities/user.entity'; import { IAccessRepository } from 'src/interfaces/access.interface'; import { IActivityRepository } from 'src/interfaces/activity.interface'; import { IAlbumUserRepository } from 'src/interfaces/album-user.interface'; @@ -9,6 +12,7 @@ import { IKeyRepository } from 'src/interfaces/api-key.interface'; import { IAssetRepository } from 'src/interfaces/asset.interface'; import { IAuditRepository } from 'src/interfaces/audit.interface'; import { IConfigRepository } from 'src/interfaces/config.interface'; +import { ICronRepository } from 'src/interfaces/cron.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; @@ -25,6 +29,7 @@ import { INotificationRepository } from 'src/interfaces/notification.interface'; import { IOAuthRepository } from 'src/interfaces/oauth.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; +import { IProcessRepository } from 'src/interfaces/process.interface'; import { ISearchRepository } from 'src/interfaces/search.interface'; import { IServerInfoRepository } from 'src/interfaces/server-info.interface'; import { ISessionRepository } from 'src/interfaces/session.interface'; @@ -53,6 +58,7 @@ export class BaseService { @Inject(IAlbumUserRepository) protected albumUserRepository: IAlbumUserRepository, @Inject(IAssetRepository) protected assetRepository: IAssetRepository, @Inject(IConfigRepository) protected configRepository: IConfigRepository, + @Inject(ICronRepository) protected cronRepository: ICronRepository, @Inject(ICryptoRepository) protected cryptoRepository: ICryptoRepository, @Inject(IDatabaseRepository) protected databaseRepository: IDatabaseRepository, @Inject(IEventRepository) protected eventRepository: IEventRepository, @@ -69,6 +75,7 @@ export class BaseService { @Inject(IOAuthRepository) protected oauthRepository: IOAuthRepository, @Inject(IPartnerRepository) protected partnerRepository: IPartnerRepository, @Inject(IPersonRepository) protected personRepository: IPersonRepository, + @Inject(IProcessRepository) protected processRepository: IProcessRepository, @Inject(ISearchRepository) protected searchRepository: ISearchRepository, @Inject(IServerInfoRepository) protected serverInfoRepository: IServerInfoRepository, @Inject(ISessionRepository) protected sessionRepository: ISessionRepository, @@ -96,6 +103,10 @@ export class BaseService { ); } + get worker() { + return this.configRepository.getWorker(); + } + private get configRepos() { return { configRepo: this.configRepository, @@ -119,4 +130,28 @@ export class BaseService { checkAccess(request: AccessRequest) { return checkAccess(this.accessRepository, request); } + + async createUser(dto: Partial & { email: string }): Promise { + const user = await this.userRepository.getByEmail(dto.email); + if (user) { + throw new BadRequestException('User exists'); + } + + if (!dto.isAdmin) { + const localAdmin = await this.userRepository.getAdmin(); + if (!localAdmin) { + throw new BadRequestException('The first registered account must the administrator.'); + } + } + + const payload: Partial = { ...dto }; + if (payload.password) { + payload.password = await this.cryptoRepository.hashBcrypt(payload.password, SALT_ROUNDS); + } + if (payload.storageLabel) { + payload.storageLabel = sanitize(payload.storageLabel.replaceAll('.', '')); + } + + return this.userRepository.create(payload); + } } diff --git a/server/src/services/database.service.spec.ts b/server/src/services/database.service.spec.ts index 96d94453c49a1..958fb158a079f 100644 --- a/server/src/services/database.service.spec.ts +++ b/server/src/services/database.service.spec.ts @@ -60,11 +60,15 @@ describe(DatabaseService.name, () => { configMock.getEnv.mockReturnValue( mockEnvData({ database: { - host: 'database', - port: 5432, - username: 'postgres', - password: 'postgres', - name: 'immich', + config: { + connectionType: 'parts', + type: 'postgres', + host: 'database', + port: 5432, + username: 'postgres', + password: 'postgres', + database: 'immich', + }, skipMigrations: false, vectorExtension: extension, }, @@ -286,11 +290,15 @@ describe(DatabaseService.name, () => { configMock.getEnv.mockReturnValue( mockEnvData({ database: { - host: 'database', - port: 5432, - username: 'postgres', - password: 'postgres', - name: 'immich', + config: { + connectionType: 'parts', + type: 'postgres', + host: 'database', + port: 5432, + username: 'postgres', + password: 'postgres', + database: 'immich', + }, skipMigrations: true, vectorExtension: DatabaseExtension.VECTORS, }, @@ -306,11 +314,15 @@ describe(DatabaseService.name, () => { configMock.getEnv.mockReturnValue( mockEnvData({ database: { - host: 'database', - port: 5432, - username: 'postgres', - password: 'postgres', - name: 'immich', + config: { + connectionType: 'parts', + type: 'postgres', + host: 'database', + port: 5432, + username: 'postgres', + password: 'postgres', + database: 'immich', + }, skipMigrations: true, vectorExtension: DatabaseExtension.VECTOR, }, diff --git a/server/src/services/database.service.ts b/server/src/services/database.service.ts index 363266c6aef6a..b1a270abd8dc7 100644 --- a/server/src/services/database.service.ts +++ b/server/src/services/database.service.ts @@ -9,6 +9,7 @@ import { VectorExtension, VectorIndex, } from 'src/interfaces/database.interface'; +import { BootstrapEventPriority } from 'src/interfaces/event.interface'; import { BaseService } from 'src/services/base.service'; type CreateFailedArgs = { name: string; extension: string; otherName: string }; @@ -64,7 +65,7 @@ const RETRY_DURATION = Duration.fromObject({ seconds: 5 }); export class DatabaseService extends BaseService { private reconnection?: NodeJS.Timeout; - @OnEvent({ name: 'app.bootstrap', priority: -200 }) + @OnEvent({ name: 'app.bootstrap', priority: BootstrapEventPriority.DatabaseService }) async onBootstrap() { const version = await this.databaseRepository.getPostgresVersion(); const current = semver.coerce(version); diff --git a/server/src/services/duplicate.service.spec.ts b/server/src/services/duplicate.service.spec.ts index 095d53dde6570..75af1ef6f1f49 100644 --- a/server/src/services/duplicate.service.spec.ts +++ b/server/src/services/duplicate.service.spec.ts @@ -31,11 +31,23 @@ describe(SearchService.name, () => { describe('getDuplicates', () => { it('should get duplicates', async () => { - assetMock.getDuplicates.mockResolvedValue([assetStub.hasDupe]); + assetMock.getDuplicates.mockResolvedValue([assetStub.hasDupe, assetStub.hasDupe]); await expect(sut.getDuplicates(authStub.admin)).resolves.toEqual([ - { duplicateId: assetStub.hasDupe.duplicateId, assets: [expect.objectContaining({ id: assetStub.hasDupe.id })] }, + { + duplicateId: assetStub.hasDupe.duplicateId, + assets: [ + expect.objectContaining({ id: assetStub.hasDupe.id }), + expect.objectContaining({ id: assetStub.hasDupe.id }), + ], + }, ]); }); + + it('should update assets with duplicateId', async () => { + assetMock.getDuplicates.mockResolvedValue([assetStub.hasDupe]); + await expect(sut.getDuplicates(authStub.admin)).resolves.toEqual([]); + expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.hasDupe.id], { duplicateId: null }); + }); }); describe('handleQueueSearchDuplicates', () => { diff --git a/server/src/services/duplicate.service.ts b/server/src/services/duplicate.service.ts index e76b80b04391c..0d91df5790d4b 100644 --- a/server/src/services/duplicate.service.ts +++ b/server/src/services/duplicate.service.ts @@ -1,10 +1,11 @@ import { Injectable } from '@nestjs/common'; +import { OnJob } from 'src/decorators'; import { mapAsset } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { DuplicateResponseDto, mapDuplicateResponse } from 'src/dtos/duplicate.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { WithoutProperty } from 'src/interfaces/asset.interface'; -import { IBaseJob, IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobName, JobStatus } from 'src/interfaces/job.interface'; +import { JOBS_ASSET_PAGINATION_SIZE, JobName, JobOf, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { AssetDuplicateResult } from 'src/interfaces/search.interface'; import { BaseService } from 'src/services/base.service'; import { getAssetFiles } from 'src/utils/asset.util'; @@ -15,11 +16,28 @@ import { usePagination } from 'src/utils/pagination'; export class DuplicateService extends BaseService { async getDuplicates(auth: AuthDto): Promise { const res = await this.assetRepository.getDuplicates({ userIds: [auth.user.id] }); - - return mapDuplicateResponse(res.map((a) => mapAsset(a, { auth, withStack: true }))); + const uniqueAssetIds: string[] = []; + const duplicates = mapDuplicateResponse(res.map((a) => mapAsset(a, { auth, withStack: true }))).filter( + (duplicate) => { + if (duplicate.assets.length === 1) { + uniqueAssetIds.push(duplicate.assets[0].id); + return false; + } + return true; + }, + ); + if (uniqueAssetIds.length > 0) { + try { + await this.assetRepository.updateAll(uniqueAssetIds, { duplicateId: null }); + } catch (error: any) { + this.logger.error(`Failed to remove duplicateId from assets: ${error.message}`); + } + } + return duplicates; } - async handleQueueSearchDuplicates({ force }: IBaseJob): Promise { + @OnJob({ name: JobName.QUEUE_DUPLICATE_DETECTION, queue: QueueName.DUPLICATE_DETECTION }) + async handleQueueSearchDuplicates({ force }: JobOf): Promise { const { machineLearning } = await this.getConfig({ withCache: false }); if (!isDuplicateDetectionEnabled(machineLearning)) { return JobStatus.SKIPPED; @@ -40,7 +58,8 @@ export class DuplicateService extends BaseService { return JobStatus.SUCCESS; } - async handleSearchDuplicates({ id }: IEntityJob): Promise { + @OnJob({ name: JobName.DUPLICATE_DETECTION, queue: QueueName.DUPLICATE_DETECTION }) + async handleSearchDuplicates({ id }: JobOf): Promise { const { machineLearning } = await this.getConfig({ withCache: true }); if (!isDuplicateDetectionEnabled(machineLearning)) { return JobStatus.SKIPPED; diff --git a/server/src/services/index.ts b/server/src/services/index.ts index 2cfbdb40c21c1..0dd8bdae666a6 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -6,6 +6,7 @@ import { AssetMediaService } from 'src/services/asset-media.service'; import { AssetService } from 'src/services/asset.service'; import { AuditService } from 'src/services/audit.service'; import { AuthService } from 'src/services/auth.service'; +import { BackupService } from 'src/services/backup.service'; import { CliService } from 'src/services/cli.service'; import { DatabaseService } from 'src/services/database.service'; import { DownloadService } from 'src/services/download.service'; @@ -16,7 +17,6 @@ import { MapService } from 'src/services/map.service'; import { MediaService } from 'src/services/media.service'; import { MemoryService } from 'src/services/memory.service'; import { MetadataService } from 'src/services/metadata.service'; -import { MicroservicesService } from 'src/services/microservices.service'; import { NotificationService } from 'src/services/notification.service'; import { PartnerService } from 'src/services/partner.service'; import { PersonService } from 'src/services/person.service'; @@ -48,6 +48,7 @@ export const services = [ AssetService, AuditService, AuthService, + BackupService, CliService, DatabaseService, DownloadService, @@ -58,7 +59,6 @@ export const services = [ MediaService, MemoryService, MetadataService, - MicroservicesService, NotificationService, PartnerService, PersonService, diff --git a/server/src/services/job.service.spec.ts b/server/src/services/job.service.spec.ts index 0353deb39b873..a23b05073c215 100644 --- a/server/src/services/job.service.spec.ts +++ b/server/src/services/job.service.spec.ts @@ -1,38 +1,28 @@ import { BadRequestException } from '@nestjs/common'; -import { defaults } from 'src/config'; +import { defaults, SystemConfig } from 'src/config'; import { ImmichWorker } from 'src/enum'; import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { - IJobRepository, - JobCommand, - JobHandler, - JobItem, - JobName, - JobStatus, - QueueName, -} from 'src/interfaces/job.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; +import { IConfigRepository } from 'src/interfaces/config.interface'; +import { IJobRepository, JobCommand, JobItem, JobName, JobStatus, QueueName } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { ITelemetryRepository } from 'src/interfaces/telemetry.interface'; import { JobService } from 'src/services/job.service'; import { assetStub } from 'test/fixtures/asset.stub'; import { newTestService } from 'test/utils'; -import { Mocked, vitest } from 'vitest'; - -const makeMockHandlers = (status: JobStatus) => { - const mock = vitest.fn().mockResolvedValue(status); - return Object.fromEntries(Object.values(JobName).map((jobName) => [jobName, mock])) as unknown as Record< - JobName, - JobHandler - >; -}; +import { Mocked } from 'vitest'; describe(JobService.name, () => { let sut: JobService; let assetMock: Mocked; + let configMock: Mocked; let jobMock: Mocked; - let systemMock: Mocked; + let loggerMock: Mocked; + let telemetryMock: Mocked; beforeEach(() => { - ({ sut, assetMock, jobMock, systemMock } = newTestService(JobService)); + ({ sut, assetMock, configMock, jobMock, loggerMock, telemetryMock } = newTestService(JobService, {})); + + configMock.getWorker.mockReturnValue(ImmichWorker.MICROSERVICES); }); it('should work', () => { @@ -41,10 +31,9 @@ describe(JobService.name, () => { describe('onConfigUpdate', () => { it('should update concurrency', () => { - sut.onBootstrap(ImmichWorker.MICROSERVICES); - sut.onConfigUpdate({ oldConfig: defaults, newConfig: defaults }); + sut.onConfigUpdate({ newConfig: defaults, oldConfig: {} as SystemConfig }); - expect(jobMock.setConcurrency).toHaveBeenCalledTimes(14); + expect(jobMock.setConcurrency).toHaveBeenCalledTimes(15); expect(jobMock.setConcurrency).toHaveBeenNthCalledWith(5, QueueName.FACIAL_RECOGNITION, 1); expect(jobMock.setConcurrency).toHaveBeenNthCalledWith(7, QueueName.DUPLICATE_DETECTION, 1); expect(jobMock.setConcurrency).toHaveBeenNthCalledWith(8, QueueName.BACKGROUND_TASK, 5); @@ -114,6 +103,7 @@ describe(JobService.name, () => { [QueueName.SIDECAR]: expectedJobStatus, [QueueName.LIBRARY]: expectedJobStatus, [QueueName.NOTIFICATION]: expectedJobStatus, + [QueueName.BACKUP_DATABASE]: expectedJobStatus, }); }); }); @@ -224,11 +214,19 @@ describe(JobService.name, () => { }); }); - describe('init', () => { - it('should register a handler for each queue', async () => { - await sut.init(makeMockHandlers(JobStatus.SUCCESS)); - expect(systemMock.get).toHaveBeenCalled(); - expect(jobMock.addHandler).toHaveBeenCalledTimes(Object.keys(QueueName).length); + describe('onJobStart', () => { + it('should process a successful job', async () => { + jobMock.run.mockResolvedValue(JobStatus.SUCCESS); + + await sut.onJobStart(QueueName.BACKGROUND_TASK, { + name: JobName.DELETE_FILES, + data: { files: ['path/to/file'] }, + }); + + expect(telemetryMock.jobs.addToGauge).toHaveBeenCalledWith('immich.queues.background_task.active', 1); + expect(telemetryMock.jobs.addToGauge).toHaveBeenCalledWith('immich.queues.background_task.active', -1); + expect(telemetryMock.jobs.addToCounter).toHaveBeenCalledWith('immich.jobs.delete_files.success', 1); + expect(loggerMock.error).not.toHaveBeenCalled(); }); const tests: Array<{ item: JobItem; jobs: JobName[] }> = [ @@ -296,8 +294,9 @@ describe(JobService.name, () => { } } - await sut.init(makeMockHandlers(JobStatus.SUCCESS)); - await jobMock.addHandler.mock.calls[0][2](item); + jobMock.run.mockResolvedValue(JobStatus.SUCCESS); + + await sut.onJobStart(QueueName.BACKGROUND_TASK, item); if (jobs.length > 1) { expect(jobMock.queueAll).toHaveBeenCalledWith( @@ -312,8 +311,9 @@ describe(JobService.name, () => { }); it(`should not queue any jobs when ${item.name} fails`, async () => { - await sut.init(makeMockHandlers(JobStatus.FAILED)); - await jobMock.addHandler.mock.calls[0][2](item); + jobMock.run.mockResolvedValue(JobStatus.FAILED); + + await sut.onJobStart(QueueName.BACKGROUND_TASK, item); expect(jobMock.queueAll).not.toHaveBeenCalled(); }); diff --git a/server/src/services/job.service.ts b/server/src/services/job.service.ts index 46771ff046170..0528a4a925421 100644 --- a/server/src/services/job.service.ts +++ b/server/src/services/job.service.ts @@ -4,11 +4,10 @@ import { OnEvent } from 'src/decorators'; import { mapAsset } from 'src/dtos/asset-response.dto'; import { AllJobStatusResponseDto, JobCommandDto, JobCreateDto, JobStatusDto } from 'src/dtos/job.dto'; import { AssetType, ImmichWorker, ManualJobName } from 'src/enum'; -import { ArgOf } from 'src/interfaces/event.interface'; +import { ArgOf, ArgsOf } from 'src/interfaces/event.interface'; import { ConcurrentQueueName, JobCommand, - JobHandler, JobItem, JobName, JobStatus, @@ -39,16 +38,9 @@ const asJobItem = (dto: JobCreateDto): JobItem => { @Injectable() export class JobService extends BaseService { - private isMicroservices = false; - - @OnEvent({ name: 'app.bootstrap' }) - onBootstrap(app: ArgOf<'app.bootstrap'>) { - this.isMicroservices = app === ImmichWorker.MICROSERVICES; - } - - @OnEvent({ name: 'config.update', server: true }) - onConfigUpdate({ newConfig: config, oldConfig }: ArgOf<'config.update'>) { - if (!oldConfig || !this.isMicroservices) { + @OnEvent({ name: 'config.init' }) + onConfigInit({ newConfig: config }: ArgOf<'config.init'>) { + if (this.worker !== ImmichWorker.MICROSERVICES) { return; } @@ -63,6 +55,11 @@ export class JobService extends BaseService { } } + @OnEvent({ name: 'config.update', server: true }) + onConfigUpdate({ newConfig: config }: ArgOf<'config.update'>) { + this.onConfigInit({ newConfig: config }); + } + async create(dto: JobCreateDto): Promise { await this.jobRepository.queue(asJobItem(dto)); } @@ -171,47 +168,31 @@ export class JobService extends BaseService { return this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SYNC_ALL, data: { force } }); } + case QueueName.BACKUP_DATABASE: { + return this.jobRepository.queue({ name: JobName.BACKUP_DATABASE, data: { force } }); + } + default: { throw new BadRequestException(`Invalid job name: ${name}`); } } } - async init(jobHandlers: Record) { - const config = await this.getConfig({ withCache: false }); - for (const queueName of Object.values(QueueName)) { - let concurrency = 1; - - if (this.isConcurrentQueue(queueName)) { - concurrency = config.job[queueName].concurrency; + @OnEvent({ name: 'job.start' }) + async onJobStart(...[queueName, job]: ArgsOf<'job.start'>) { + const queueMetric = `immich.queues.${snakeCase(queueName)}.active`; + this.telemetryRepository.jobs.addToGauge(queueMetric, 1); + try { + const status = await this.jobRepository.run(job); + const jobMetric = `immich.jobs.${job.name.replaceAll('-', '_')}.${status}`; + this.telemetryRepository.jobs.addToCounter(jobMetric, 1); + if (status === JobStatus.SUCCESS || status == JobStatus.SKIPPED) { + await this.onDone(job); } - - this.logger.debug(`Registering ${queueName} with a concurrency of ${concurrency}`); - this.jobRepository.addHandler(queueName, concurrency, async (item: JobItem): Promise => { - const { name, data } = item; - - const handler = jobHandlers[name]; - if (!handler) { - this.logger.warn(`Skipping unknown job: "${name}"`); - return; - } - - const queueMetric = `immich.queues.${snakeCase(queueName)}.active`; - this.telemetryRepository.jobs.addToGauge(queueMetric, 1); - - try { - const status = await handler(data); - const jobMetric = `immich.jobs.${name.replaceAll('-', '_')}.${status}`; - this.telemetryRepository.jobs.addToCounter(jobMetric, 1); - if (status === JobStatus.SUCCESS || status == JobStatus.SKIPPED) { - await this.onDone(item); - } - } catch (error: Error | any) { - this.logger.error(`Unable to run job handler (${queueName}/${name}): ${error}`, error?.stack, data); - } finally { - this.telemetryRepository.jobs.addToGauge(queueMetric, -1); - } - }); + } catch (error: Error | any) { + this.logger.error(`Unable to run job handler (${queueName}/${job.name}): ${error}`, error?.stack, job.data); + } finally { + this.telemetryRepository.jobs.addToGauge(queueMetric, -1); } } @@ -220,6 +201,7 @@ export class JobService extends BaseService { QueueName.FACIAL_RECOGNITION, QueueName.STORAGE_TEMPLATE_MIGRATION, QueueName.DUPLICATE_DETECTION, + QueueName.BACKUP_DATABASE, ].includes(name); } diff --git a/server/src/services/library.service.spec.ts b/server/src/services/library.service.spec.ts index 5258c8d035b52..96fa127e3c1d2 100644 --- a/server/src/services/library.service.spec.ts +++ b/server/src/services/library.service.spec.ts @@ -3,8 +3,10 @@ import { Stats } from 'node:fs'; import { defaults, SystemConfig } from 'src/config'; import { mapLibrary } from 'src/dtos/library.dto'; import { UserEntity } from 'src/entities/user.entity'; -import { AssetType } from 'src/enum'; +import { AssetType, ImmichWorker } from 'src/enum'; import { IAssetRepository } from 'src/interfaces/asset.interface'; +import { IConfigRepository } from 'src/interfaces/config.interface'; +import { ICronRepository } from 'src/interfaces/cron.interface'; import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { IJobRepository, @@ -16,7 +18,6 @@ import { } from 'src/interfaces/job.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { LibraryService } from 'src/services/library.service'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; @@ -35,30 +36,30 @@ describe(LibraryService.name, () => { let sut: LibraryService; let assetMock: Mocked; + let configMock: Mocked; + let cronMock: Mocked; let databaseMock: Mocked; let jobMock: Mocked; let libraryMock: Mocked; let storageMock: Mocked; - let systemMock: Mocked; beforeEach(() => { - ({ sut, assetMock, databaseMock, jobMock, libraryMock, storageMock, systemMock } = newTestService(LibraryService)); + ({ sut, assetMock, configMock, cronMock, databaseMock, jobMock, libraryMock, storageMock } = + newTestService(LibraryService)); databaseMock.tryLock.mockResolvedValue(true); + configMock.getWorker.mockReturnValue(ImmichWorker.MICROSERVICES); }); it('should work', () => { expect(sut).toBeDefined(); }); - describe('onBootstrapEvent', () => { + describe('onConfigInit', () => { it('should init cron job and handle config changes', async () => { - systemMock.get.mockResolvedValue(systemConfigStub.libraryScan); + await sut.onConfigInit({ newConfig: defaults }); - await sut.onBootstrap(); - - expect(jobMock.addCronJob).toHaveBeenCalled(); - expect(systemMock.get).toHaveBeenCalled(); + expect(cronMock.create).toHaveBeenCalled(); await sut.onConfigUpdate({ oldConfig: defaults, @@ -73,7 +74,7 @@ describe(LibraryService.name, () => { } as SystemConfig, }); - expect(jobMock.updateCronJob).toHaveBeenCalledWith('libraryScan', '0 1 * * *', true); + expect(cronMock.update).toHaveBeenCalledWith({ name: 'libraryScan', expression: '0 1 * * *', start: true }); }); it('should initialize watcher for all external libraries', async () => { @@ -82,7 +83,6 @@ describe(LibraryService.name, () => { libraryStub.externalLibraryWithImportPaths2, ]); - systemMock.get.mockResolvedValue(systemConfigStub.libraryWatchEnabled); libraryMock.get.mockImplementation((id) => Promise.resolve( [libraryStub.externalLibraryWithImportPaths1, libraryStub.externalLibraryWithImportPaths2].find( @@ -91,7 +91,7 @@ describe(LibraryService.name, () => { ), ); - await sut.onBootstrap(); + await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); expect(storageMock.watch.mock.calls).toEqual( expect.arrayContaining([ @@ -102,98 +102,78 @@ describe(LibraryService.name, () => { }); it('should not initialize watcher when watching is disabled', async () => { - systemMock.get.mockResolvedValue(systemConfigStub.libraryWatchDisabled); - - await sut.onBootstrap(); + await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchDisabled as SystemConfig }); expect(storageMock.watch).not.toHaveBeenCalled(); }); it('should not initialize watcher when lock is taken', async () => { - systemMock.get.mockResolvedValue(systemConfigStub.libraryWatchEnabled); databaseMock.tryLock.mockResolvedValue(false); - await sut.onBootstrap(); + await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); expect(storageMock.watch).not.toHaveBeenCalled(); }); + + it('should not initialize library scan cron job when lock is taken', async () => { + databaseMock.tryLock.mockResolvedValue(false); + + await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); + + expect(cronMock.create).not.toHaveBeenCalled(); + }); + + it('should not initialize watcher or library scan job when running on api', async () => { + configMock.getWorker.mockReturnValue(ImmichWorker.API); + await sut.onConfigInit({ newConfig: systemConfigStub.libraryScan as SystemConfig }); + + expect(cronMock.create).not.toHaveBeenCalled(); + }); }); describe('onConfigUpdateEvent', () => { beforeEach(async () => { - systemMock.get.mockResolvedValue(defaults); databaseMock.tryLock.mockResolvedValue(true); - await sut.onBootstrap(); - }); - - it('should do nothing if oldConfig is not provided', async () => { - await sut.onConfigUpdate({ newConfig: systemConfigStub.libraryScan as SystemConfig }); - expect(jobMock.updateCronJob).not.toHaveBeenCalled(); + await sut.onConfigInit({ newConfig: defaults }); }); it('should do nothing if instance does not have the watch lock', async () => { databaseMock.tryLock.mockResolvedValue(false); - await sut.onBootstrap(); + await sut.onConfigInit({ newConfig: defaults }); await sut.onConfigUpdate({ newConfig: systemConfigStub.libraryScan as SystemConfig, oldConfig: defaults }); - expect(jobMock.updateCronJob).not.toHaveBeenCalled(); + expect(cronMock.update).not.toHaveBeenCalled(); }); it('should update cron job and enable watching', async () => { libraryMock.getAll.mockResolvedValue([]); await sut.onConfigUpdate({ - newConfig: { - library: { ...systemConfigStub.libraryScan.library, ...systemConfigStub.libraryWatchEnabled.library }, - } as SystemConfig, + newConfig: systemConfigStub.libraryScanAndWatch as SystemConfig, oldConfig: defaults, }); - expect(jobMock.updateCronJob).toHaveBeenCalledWith( - 'libraryScan', - systemConfigStub.libraryScan.library.scan.cronExpression, - systemConfigStub.libraryScan.library.scan.enabled, - ); + expect(cronMock.update).toHaveBeenCalledWith({ + name: 'libraryScan', + expression: systemConfigStub.libraryScan.library.scan.cronExpression, + start: systemConfigStub.libraryScan.library.scan.enabled, + }); }); it('should update cron job and disable watching', async () => { libraryMock.getAll.mockResolvedValue([]); await sut.onConfigUpdate({ - newConfig: { - library: { ...systemConfigStub.libraryScan.library, ...systemConfigStub.libraryWatchEnabled.library }, - } as SystemConfig, + newConfig: systemConfigStub.libraryScanAndWatch as SystemConfig, oldConfig: defaults, }); await sut.onConfigUpdate({ - newConfig: { - library: { ...systemConfigStub.libraryScan.library, ...systemConfigStub.libraryWatchDisabled.library }, - } as SystemConfig, + newConfig: systemConfigStub.libraryScan as SystemConfig, oldConfig: defaults, }); - expect(jobMock.updateCronJob).toHaveBeenCalledWith( - 'libraryScan', - systemConfigStub.libraryScan.library.scan.cronExpression, - systemConfigStub.libraryScan.library.scan.enabled, - ); - }); - }); - - describe('onConfigValidateEvent', () => { - it('should allow a valid cron expression', () => { - expect(() => - sut.onConfigValidate({ - newConfig: { library: { scan: { cronExpression: '0 0 * * *' } } } as SystemConfig, - oldConfig: {} as SystemConfig, - }), - ).not.toThrow(expect.stringContaining('Invalid cron expression')); - }); - - it('should fail for an invalid cron expression', () => { - expect(() => - sut.onConfigValidate({ - newConfig: { library: { scan: { cronExpression: 'foo' } } } as SystemConfig, - oldConfig: {} as SystemConfig, - }), - ).toThrow(/Invalid cron expression.*/); + expect(cronMock.update).toHaveBeenCalledWith({ + name: 'libraryScan', + expression: systemConfigStub.libraryScan.library.scan.cronExpression, + start: systemConfigStub.libraryScan.library.scan.enabled, + }); }); }); @@ -688,12 +668,10 @@ describe(LibraryService.name, () => { libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); libraryMock.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); - systemMock.get.mockResolvedValue(systemConfigStub.libraryWatchEnabled); - const mockClose = vitest.fn(); storageMock.watch.mockImplementation(makeMockWatcher({ close: mockClose })); - await sut.onBootstrap(); + await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); await sut.delete(libraryStub.externalLibraryWithImportPaths1.id); expect(mockClose).toHaveBeenCalled(); @@ -822,12 +800,11 @@ describe(LibraryService.name, () => { }); it('should create watched with import paths', async () => { - systemMock.get.mockResolvedValue(systemConfigStub.libraryWatchEnabled); libraryMock.create.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); libraryMock.getAll.mockResolvedValue([]); - await sut.onBootstrap(); + await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); await sut.create({ ownerId: authStub.admin.user.id, importPaths: libraryStub.externalLibraryWithImportPaths1.importPaths, @@ -887,10 +864,9 @@ describe(LibraryService.name, () => { describe('update', () => { beforeEach(async () => { - systemMock.get.mockResolvedValue(systemConfigStub.libraryWatchEnabled); libraryMock.getAll.mockResolvedValue([]); - await sut.onBootstrap(); + await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); }); it('should throw an error if an import path is invalid', async () => { @@ -929,9 +905,7 @@ describe(LibraryService.name, () => { describe('watching disabled', () => { beforeEach(async () => { - systemMock.get.mockResolvedValue(systemConfigStub.libraryWatchDisabled); - - await sut.onBootstrap(); + await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchDisabled as SystemConfig }); }); it('should not watch library', async () => { @@ -945,9 +919,8 @@ describe(LibraryService.name, () => { describe('watching enabled', () => { beforeEach(async () => { - systemMock.get.mockResolvedValue(systemConfigStub.libraryWatchEnabled); libraryMock.getAll.mockResolvedValue([]); - await sut.onBootstrap(); + await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); }); it('should watch library', async () => { @@ -1099,7 +1072,6 @@ describe(LibraryService.name, () => { libraryStub.externalLibraryWithImportPaths2, ]); - systemMock.get.mockResolvedValue(systemConfigStub.libraryWatchEnabled); libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1); libraryMock.get.mockImplementation((id) => @@ -1113,7 +1085,7 @@ describe(LibraryService.name, () => { const mockClose = vitest.fn(); storageMock.watch.mockImplementation(makeMockWatcher({ close: mockClose })); - await sut.onBootstrap(); + await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); await sut.onShutdown(); expect(mockClose).toHaveBeenCalledTimes(2); diff --git a/server/src/services/library.service.ts b/server/src/services/library.service.ts index e319983d3b3a1..6c5d400c84000 100644 --- a/server/src/services/library.service.ts +++ b/server/src/services/library.service.ts @@ -3,7 +3,7 @@ import { R_OK } from 'node:constants'; import path, { basename, isAbsolute, parse } from 'node:path'; import picomatch from 'picomatch'; import { StorageCore } from 'src/cores/storage.core'; -import { OnEvent } from 'src/decorators'; +import { OnEvent, OnJob } from 'src/decorators'; import { CreateLibraryDto, LibraryResponseDto, @@ -16,47 +16,45 @@ import { } from 'src/dtos/library.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { LibraryEntity } from 'src/entities/library.entity'; -import { AssetType } from 'src/enum'; +import { AssetType, ImmichWorker } from 'src/enum'; import { DatabaseLock } from 'src/interfaces/database.interface'; import { ArgOf } from 'src/interfaces/event.interface'; -import { - IEntityJob, - ILibraryAssetJob, - ILibraryFileJob, - JobName, - JOBS_LIBRARY_PAGINATION_SIZE, - JobStatus, -} from 'src/interfaces/job.interface'; +import { JobName, JobOf, JOBS_LIBRARY_PAGINATION_SIZE, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { BaseService } from 'src/services/base.service'; import { mimeTypes } from 'src/utils/mime-types'; import { handlePromiseError } from 'src/utils/misc'; import { usePagination } from 'src/utils/pagination'; -import { validateCronExpression } from 'src/validation'; @Injectable() export class LibraryService extends BaseService { private watchLibraries = false; - private watchLock = false; + private lock = false; private watchers: Record Promise> = {}; - @OnEvent({ name: 'app.bootstrap' }) - async onBootstrap() { - const config = await this.getConfig({ withCache: false }); - - const { watch, scan } = config.library; + @OnEvent({ name: 'config.init' }) + async onConfigInit({ + newConfig: { + library: { watch, scan }, + }, + }: ArgOf<'config.init'>) { + if (this.worker !== ImmichWorker.MICROSERVICES) { + return; + } // This ensures that library watching only occurs in one microservice - // TODO: we could make the lock be per-library instead of global - this.watchLock = await this.databaseRepository.tryLock(DatabaseLock.LibraryWatch); + this.lock = await this.databaseRepository.tryLock(DatabaseLock.Library); - this.watchLibraries = this.watchLock && watch.enabled; + this.watchLibraries = this.lock && watch.enabled; - this.jobRepository.addCronJob( - 'libraryScan', - scan.cronExpression, - () => handlePromiseError(this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SYNC_ALL }), this.logger), - scan.enabled, - ); + if (this.lock) { + this.cronRepository.create({ + name: 'libraryScan', + expression: scan.cronExpression, + onTick: () => + handlePromiseError(this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SYNC_ALL }), this.logger), + start: scan.enabled, + }); + } if (this.watchLibraries) { await this.watchAll(); @@ -64,12 +62,16 @@ export class LibraryService extends BaseService { } @OnEvent({ name: 'config.update', server: true }) - async onConfigUpdate({ newConfig: { library }, oldConfig }: ArgOf<'config.update'>) { - if (!oldConfig || !this.watchLock) { + async onConfigUpdate({ newConfig: { library } }: ArgOf<'config.update'>) { + if (!this.lock) { return; } - this.jobRepository.updateCronJob('libraryScan', library.scan.cronExpression, library.scan.enabled); + this.cronRepository.update({ + name: 'libraryScan', + expression: library.scan.cronExpression, + start: library.scan.enabled, + }); if (library.watch.enabled !== this.watchLibraries) { // Watch configuration changed, update accordingly @@ -78,14 +80,6 @@ export class LibraryService extends BaseService { } } - @OnEvent({ name: 'config.validate' }) - onConfigValidate({ newConfig }: ArgOf<'config.validate'>) { - const { scan } = newConfig.library; - if (!validateCronExpression(scan.cronExpression)) { - throw new Error(`Invalid cron expression ${scan.cronExpression}`); - } - } - private async watch(id: string): Promise { if (!this.watchLibraries) { return false; @@ -180,7 +174,7 @@ export class LibraryService extends BaseService { } private async unwatchAll() { - if (!this.watchLock) { + if (!this.lock) { return false; } @@ -190,7 +184,7 @@ export class LibraryService extends BaseService { } async watchAll() { - if (!this.watchLock) { + if (!this.lock) { return false; } @@ -218,6 +212,7 @@ export class LibraryService extends BaseService { return libraries.map((library) => mapLibrary(library)); } + @OnJob({ name: JobName.LIBRARY_QUEUE_CLEANUP, queue: QueueName.LIBRARY }) async handleQueueCleanup(): Promise { this.logger.debug('Cleaning up any pending library deletions'); const pendingDeletion = await this.libraryRepository.getAllDeleted(); @@ -335,7 +330,8 @@ export class LibraryService extends BaseService { await this.jobRepository.queue({ name: JobName.LIBRARY_DELETE, data: { id } }); } - async handleDeleteLibrary(job: IEntityJob): Promise { + @OnJob({ name: JobName.LIBRARY_DELETE, queue: QueueName.LIBRARY }) + async handleDeleteLibrary(job: JobOf): Promise { const libraryId = job.id; const assetPagination = usePagination(JOBS_LIBRARY_PAGINATION_SIZE, (pagination) => @@ -369,7 +365,8 @@ export class LibraryService extends BaseService { return JobStatus.SUCCESS; } - async handleSyncFile(job: ILibraryFileJob): Promise { + @OnJob({ name: JobName.LIBRARY_SYNC_FILE, queue: QueueName.LIBRARY }) + async handleSyncFile(job: JobOf): Promise { // Only needs to handle new assets const assetPath = path.normalize(job.assetPath); @@ -453,6 +450,7 @@ export class LibraryService extends BaseService { await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SYNC_ASSETS, data: { id } }); } + @OnJob({ name: JobName.LIBRARY_QUEUE_SYNC_ALL, queue: QueueName.LIBRARY }) async handleQueueSyncAll(): Promise { this.logger.debug(`Refreshing all external libraries`); @@ -478,7 +476,8 @@ export class LibraryService extends BaseService { return JobStatus.SUCCESS; } - async handleSyncAsset(job: ILibraryAssetJob): Promise { + @OnJob({ name: JobName.LIBRARY_SYNC_ASSET, queue: QueueName.LIBRARY }) + async handleSyncAsset(job: JobOf): Promise { const asset = await this.assetRepository.getById(job.id); if (!asset) { return JobStatus.SKIPPED; @@ -533,7 +532,8 @@ export class LibraryService extends BaseService { return JobStatus.SUCCESS; } - async handleQueueSyncFiles(job: IEntityJob): Promise { + @OnJob({ name: JobName.LIBRARY_QUEUE_SYNC_FILES, queue: QueueName.LIBRARY }) + async handleQueueSyncFiles(job: JobOf): Promise { const library = await this.libraryRepository.get(job.id); if (!library) { this.logger.debug(`Library ${job.id} not found, skipping refresh`); @@ -584,7 +584,8 @@ export class LibraryService extends BaseService { return JobStatus.SUCCESS; } - async handleQueueSyncAssets(job: IEntityJob): Promise { + @OnJob({ name: JobName.LIBRARY_QUEUE_SYNC_ASSETS, queue: QueueName.LIBRARY }) + async handleQueueSyncAssets(job: JobOf): Promise { const library = await this.libraryRepository.get(job.id); if (!library) { return JobStatus.SKIPPED; diff --git a/server/src/services/media.service.spec.ts b/server/src/services/media.service.spec.ts index afd21ee9e99e6..069376b8d358e 100644 --- a/server/src/services/media.service.spec.ts +++ b/server/src/services/media.service.spec.ts @@ -1,4 +1,4 @@ -import { Stats } from 'node:fs'; +import type { Stats } from 'node:fs'; import { SystemConfig } from 'src/config'; import { AssetEntity } from 'src/entities/asset.entity'; import { ExifEntity } from 'src/entities/exif.entity'; @@ -9,7 +9,6 @@ import { AudioCodec, Colorspace, ImageFormat, - ToneMapping, TranscodeHWAccel, TranscodePolicy, VideoCodec, @@ -410,7 +409,7 @@ describe(MediaService.name, () => { '-frames:v 1', '-update 1', '-v verbose', - String.raw`-vf fps=12:eof_action=pass:round=down,thumbnail=12,select=gt(scene\,0.1)-eq(prev_selected_n\,n)+isnan(prev_selected_n)+gt(n\,20),trim=end_frame=2,reverse,scale=-2:1440:flags=lanczos+accurate_rnd+full_chroma_int:out_color_matrix=bt601:out_range=pc,format=yuv420p`, + String.raw`-vf fps=12:eof_action=pass:round=down,thumbnail=12,select=gt(scene\,0.1)-eq(prev_selected_n\,n)+isnan(prev_selected_n)+gt(n\,20),trim=end_frame=2,reverse,scale=-2:1440:flags=lanczos+accurate_rnd+full_chroma_int:out_range=pc`, ], twoPass: false, }), @@ -445,7 +444,7 @@ describe(MediaService.name, () => { '-frames:v 1', '-update 1', '-v verbose', - String.raw`-vf fps=12:eof_action=pass:round=down,thumbnail=12,select=gt(scene\,0.1)-eq(prev_selected_n\,n)+isnan(prev_selected_n)+gt(n\,20),trim=end_frame=2,reverse,zscale=t=linear:npl=100,tonemap=hable:desat=0,zscale=p=709:t=601:m=470bg:range=pc,format=yuv420p`, + String.raw`-vf fps=12:eof_action=pass:round=down,thumbnail=12,select=gt(scene\,0.1)-eq(prev_selected_n\,n)+isnan(prev_selected_n)+gt(n\,20),trim=end_frame=2,reverse,tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p`, ], twoPass: false, }), @@ -482,12 +481,28 @@ describe(MediaService.name, () => { '-frames:v 1', '-update 1', '-v verbose', - String.raw`-vf fps=12:eof_action=pass:round=down,thumbnail=12,select=gt(scene\,0.1)-eq(prev_selected_n\,n)+isnan(prev_selected_n)+gt(n\,20),trim=end_frame=2,reverse,zscale=t=linear:npl=100,tonemap=hable:desat=0,zscale=p=709:t=601:m=470bg:range=pc,format=yuv420p`, + String.raw`-vf fps=12:eof_action=pass:round=down,thumbnail=12,select=gt(scene\,0.1)-eq(prev_selected_n\,n)+isnan(prev_selected_n)+gt(n\,20),trim=end_frame=2,reverse,tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p`, ], twoPass: false, }), ); }); + it('should not skip intra frames for MTS file', async () => { + mediaMock.probe.mockResolvedValue(probeStub.videoStreamMTS); + assetMock.getById.mockResolvedValue(assetStub.video); + await sut.handleGenerateThumbnails({ id: assetStub.video.id }); + + expect(mediaMock.transcode).toHaveBeenCalledWith( + '/original/path.ext', + 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg', + expect.objectContaining({ + inputOptions: ['-sws_flags accurate_rnd+full_chroma_int'], + outputOptions: expect.any(Array), + progress: expect.any(Object), + twoPass: false, + }), + ); + }); it('should use scaling divisible by 2 even when using quick sync', async () => { mediaMock.probe.mockResolvedValue(probeStub.videoStream2160p); @@ -1328,7 +1343,7 @@ describe(MediaService.name, () => { '-map 0:0', '-map 0:1', '-v verbose', - '-vf scale=-2:720,format=yuv420p', + '-vf scale=-2:720', '-preset 12', '-crf 23', ]), @@ -1454,7 +1469,7 @@ describe(MediaService.name, () => { '-map 0:1', '-g 256', '-v verbose', - '-vf format=nv12,hwupload_cuda,scale_cuda=-2:720', + '-vf hwupload_cuda,scale_cuda=-2:720:format=nv12', '-preset p1', '-cq:v 23', ]), @@ -1586,7 +1601,7 @@ describe(MediaService.name, () => { inputOptions: expect.arrayContaining(['-hwaccel cuda', '-hwaccel_output_format cuda']), outputOptions: expect.arrayContaining([ expect.stringContaining( - 'tonemap_cuda=desat=0:matrix=bt709:primaries=bt709:range=pc:tonemap=hable:transfer=bt709:format=nv12', + 'tonemap_cuda=desat=0:matrix=bt709:primaries=bt709:range=pc:tonemap=hable:tonemap_mode=lum:transfer=bt709:peak=100:format=nv12', ), ]), twoPass: false, @@ -1594,6 +1609,24 @@ describe(MediaService.name, () => { ); }); + it('should set format to nv12 for nvenc if input is not yuv420p', async () => { + mediaMock.probe.mockResolvedValue(probeStub.videoStream10Bit); + systemMock.get.mockResolvedValue({ + ffmpeg: { accel: TranscodeHWAccel.NVENC, accelDecode: true }, + }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); + expect(mediaMock.transcode).toHaveBeenCalledWith( + '/original/path.ext', + 'upload/encoded-video/user-id/as/se/asset-id.mp4', + expect.objectContaining({ + inputOptions: expect.arrayContaining(['-hwaccel cuda', '-hwaccel_output_format cuda']), + outputOptions: expect.arrayContaining([expect.stringContaining('format=nv12')]), + twoPass: false, + }), + ); + }); + it('should set options for qsv', async () => { storageMock.readdir.mockResolvedValue(['renderD128']); mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); @@ -1616,7 +1649,7 @@ describe(MediaService.name, () => { '-refs 5', '-g 256', '-v verbose', - '-vf format=nv12,hwupload=extra_hw_frames=64,scale_qsv=-1:720:mode=hq', + '-vf hwupload=extra_hw_frames=64,scale_qsv=-1:720:mode=hq:format=nv12', '-preset 7', '-global_quality:v 23', '-maxrate 10000k', @@ -1748,7 +1781,7 @@ describe(MediaService.name, () => { ]), outputOptions: expect.arrayContaining([ expect.stringContaining( - 'hwmap=derive_device=opencl,tonemap_opencl=desat=0:format=nv12:matrix=bt709:primaries=bt709:range=pc:tonemap=hable:transfer=bt709,hwmap=derive_device=qsv:reverse=1,format=qsv', + 'hwmap=derive_device=opencl,tonemap_opencl=desat=0:format=nv12:matrix=bt709:primaries=bt709:transfer=bt709:range=pc:tonemap=hable:tonemap_mode=lum:peak=100,hwmap=derive_device=qsv:reverse=1,format=qsv', ), ]), twoPass: false, @@ -1776,6 +1809,32 @@ describe(MediaService.name, () => { ); }); + it('should set format to nv12 for qsv if input is not yuv420p', async () => { + storageMock.readdir.mockResolvedValue(['renderD128']); + mediaMock.probe.mockResolvedValue(probeStub.videoStream10Bit); + systemMock.get.mockResolvedValue({ + ffmpeg: { accel: TranscodeHWAccel.QSV, accelDecode: true }, + }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + + await sut.handleVideoConversion({ id: assetStub.video.id }); + + expect(mediaMock.transcode).toHaveBeenCalledWith( + '/original/path.ext', + 'upload/encoded-video/user-id/as/se/asset-id.mp4', + expect.objectContaining({ + inputOptions: expect.arrayContaining([ + '-hwaccel qsv', + '-hwaccel_output_format qsv', + '-async_depth 4', + '-threads 1', + ]), + outputOptions: expect.arrayContaining([expect.stringContaining('format=nv12')]), + twoPass: false, + }), + ); + }); + it('should set options for vaapi', async () => { storageMock.readdir.mockResolvedValue(['renderD128']); mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); @@ -1799,7 +1858,7 @@ describe(MediaService.name, () => { '-map 0:1', '-g 256', '-v verbose', - '-vf format=nv12,hwupload,scale_vaapi=-2:720:mode=hq:out_range=pc', + '-vf hwupload=extra_hw_frames=64,scale_vaapi=-2:720:mode=hq:out_range=pc:format=nv12', '-compression_level 7', '-rc_mode 1', ]), @@ -1970,7 +2029,7 @@ describe(MediaService.name, () => { ); }); - it('should use hardware tone-mapping for qsv if hardware decoding is enabled and should tone map', async () => { + it('should use hardware tone-mapping for vaapi if hardware decoding is enabled and should tone map', async () => { storageMock.readdir.mockResolvedValue(['renderD128']); mediaMock.probe.mockResolvedValue(probeStub.videoStreamHDR); systemMock.get.mockResolvedValue({ @@ -1987,7 +2046,7 @@ describe(MediaService.name, () => { inputOptions: expect.arrayContaining(['-hwaccel vaapi', '-hwaccel_output_format vaapi', '-threads 1']), outputOptions: expect.arrayContaining([ expect.stringContaining( - 'hwmap=derive_device=opencl,tonemap_opencl=desat=0:format=nv12:matrix=bt709:primaries=bt709:range=pc:tonemap=hable:transfer=bt709,hwmap=derive_device=vaapi:reverse=1,format=vaapi', + 'hwmap=derive_device=opencl,tonemap_opencl=desat=0:format=nv12:matrix=bt709:primaries=bt709:transfer=bt709:range=pc:tonemap=hable:tonemap_mode=lum:peak=100,hwmap=derive_device=vaapi:reverse=1,format=vaapi', ), ]), twoPass: false, @@ -1995,6 +2054,27 @@ describe(MediaService.name, () => { ); }); + it('should set format to nv12 for vaapi if input is not yuv420p', async () => { + storageMock.readdir.mockResolvedValue(['renderD128']); + mediaMock.probe.mockResolvedValue(probeStub.videoStream10Bit); + systemMock.get.mockResolvedValue({ + ffmpeg: { accel: TranscodeHWAccel.VAAPI, accelDecode: true }, + }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + + await sut.handleVideoConversion({ id: assetStub.video.id }); + + expect(mediaMock.transcode).toHaveBeenCalledWith( + '/original/path.ext', + 'upload/encoded-video/user-id/as/se/asset-id.mp4', + expect.objectContaining({ + inputOptions: expect.arrayContaining(['-hwaccel vaapi', '-hwaccel_output_format vaapi', '-threads 1']), + outputOptions: expect.arrayContaining([expect.stringContaining('format=nv12')]), + twoPass: false, + }), + ); + }); + it('should use preferred device for vaapi when hardware decoding', async () => { storageMock.readdir.mockResolvedValue(['renderD128', 'renderD129', 'renderD130']); mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); @@ -2045,7 +2125,7 @@ describe(MediaService.name, () => { it('should set options for rkmpp', async () => { storageMock.readdir.mockResolvedValue(['renderD128']); - storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => true, isCharacterDevice: () => true }); + storageMock.stat.mockResolvedValue({ isFile: () => true, isCharacterDevice: () => true } as Stats); mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); systemMock.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHWAccel.RKMPP, accelDecode: true } }); assetMock.getByIds.mockResolvedValue([assetStub.video]); @@ -2081,7 +2161,7 @@ describe(MediaService.name, () => { it('should set vbr options for rkmpp when max bitrate is enabled', async () => { storageMock.readdir.mockResolvedValue(['renderD128']); - storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => true, isCharacterDevice: () => true }); + storageMock.stat.mockResolvedValue({ isFile: () => true, isCharacterDevice: () => true } as Stats); mediaMock.probe.mockResolvedValue(probeStub.videoStreamVp9); systemMock.get.mockResolvedValue({ ffmpeg: { @@ -2106,7 +2186,7 @@ describe(MediaService.name, () => { it('should set cqp options for rkmpp when max bitrate is disabled', async () => { storageMock.readdir.mockResolvedValue(['renderD128']); - storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => true, isCharacterDevice: () => true }); + storageMock.stat.mockResolvedValue({ isFile: () => true, isCharacterDevice: () => true } as Stats); mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); systemMock.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHWAccel.RKMPP, accelDecode: true, crf: 30, maxBitrate: '0' }, @@ -2126,7 +2206,7 @@ describe(MediaService.name, () => { it('should set OpenCL tonemapping options for rkmpp when OpenCL is available', async () => { storageMock.readdir.mockResolvedValue(['renderD128']); - storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => true, isCharacterDevice: () => true }); + storageMock.stat.mockResolvedValue({ isFile: () => true, isCharacterDevice: () => true } as Stats); mediaMock.probe.mockResolvedValue(probeStub.videoStreamHDR); systemMock.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHWAccel.RKMPP, accelDecode: true, crf: 30, maxBitrate: '0' }, @@ -2140,7 +2220,7 @@ describe(MediaService.name, () => { inputOptions: expect.arrayContaining(['-hwaccel rkmpp', '-hwaccel_output_format drm_prime', '-afbc rga']), outputOptions: expect.arrayContaining([ expect.stringContaining( - 'scale_rkrga=-2:720:format=p010:afbc=1,hwmap=derive_device=opencl:mode=read,tonemap_opencl=format=nv12:r=pc:p=bt709:t=bt709:m=bt709:tonemap=hable:desat=0,hwmap=derive_device=rkmpp:mode=write:reverse=1,format=drm_prime', + 'scale_rkrga=-2:720:format=p010:afbc=1,hwmap=derive_device=opencl:mode=read,tonemap_opencl=format=nv12:r=pc:p=bt709:t=bt709:m=bt709:tonemap=hable:desat=0:tonemap_mode=lum:peak=100,hwmap=derive_device=rkmpp:mode=write:reverse=1,format=drm_prime', ), ]), twoPass: false, @@ -2150,7 +2230,7 @@ describe(MediaService.name, () => { it('should use software decoding and tone-mapping if hardware decoding is disabled', async () => { storageMock.readdir.mockResolvedValue(['renderD128']); - storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => true, isCharacterDevice: () => true }); + storageMock.stat.mockResolvedValue({ isFile: () => true, isCharacterDevice: () => true } as Stats); mediaMock.probe.mockResolvedValue(probeStub.videoStreamHDR); systemMock.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHWAccel.RKMPP, accelDecode: false, crf: 30, maxBitrate: '0' }, @@ -2164,7 +2244,7 @@ describe(MediaService.name, () => { inputOptions: [], outputOptions: expect.arrayContaining([ expect.stringContaining( - 'zscale=t=linear:npl=100,tonemap=hable:desat=0,zscale=p=709:t=709:m=709:range=pc,format=yuv420p', + 'tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p', ), ]), twoPass: false, @@ -2174,7 +2254,7 @@ describe(MediaService.name, () => { it('should use software decoding and tone-mapping if opencl is not available', async () => { storageMock.readdir.mockResolvedValue(['renderD128']); - storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => false, isCharacterDevice: () => false }); + storageMock.stat.mockResolvedValue({ isFile: () => false, isCharacterDevice: () => false } as Stats); mediaMock.probe.mockResolvedValue(probeStub.videoStreamHDR); systemMock.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHWAccel.RKMPP, accelDecode: true, crf: 30, maxBitrate: '0' }, @@ -2188,7 +2268,7 @@ describe(MediaService.name, () => { inputOptions: [], outputOptions: expect.arrayContaining([ expect.stringContaining( - 'zscale=t=linear:npl=100,tonemap=hable:desat=0,zscale=p=709:t=709:m=709:range=pc,format=yuv420p', + 'tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p', ), ]), twoPass: false, @@ -2209,7 +2289,7 @@ describe(MediaService.name, () => { outputOptions: expect.arrayContaining([ '-c:v h264', '-c:a copy', - '-vf zscale=t=linear:npl=100,tonemap=hable:desat=0,zscale=p=709:t=709:m=709:range=pc,format=yuv420p', + '-vf tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p', ]), twoPass: false, }), @@ -2229,16 +2309,16 @@ describe(MediaService.name, () => { outputOptions: expect.arrayContaining([ '-c:v h264', '-c:a copy', - '-vf zscale=t=linear:npl=100,tonemap=hable:desat=0,zscale=p=709:t=709:m=709:range=pc,format=yuv420p', + '-vf tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p', ]), twoPass: false, }), ); }); - it('should set npl to 250 for reinhard and mobius tone-mapping algorithms', async () => { - mediaMock.probe.mockResolvedValue(probeStub.videoStreamHDR); - systemMock.get.mockResolvedValue({ ffmpeg: { tonemap: ToneMapping.MOBIUS } }); + it('should transcode when policy is required and video is not yuv420p', async () => { + mediaMock.probe.mockResolvedValue(probeStub.videoStream10Bit); + systemMock.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.REQUIRED } }); assetMock.getByIds.mockResolvedValue([assetStub.video]); await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( @@ -2246,11 +2326,7 @@ describe(MediaService.name, () => { 'upload/encoded-video/user-id/as/se/asset-id.mp4', expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining([ - '-c:v h264', - '-c:a copy', - '-vf zscale=t=linear:npl=250,tonemap=mobius:desat=0,zscale=p=709:t=709:m=709:range=pc,format=yuv420p', - ]), + outputOptions: expect.arrayContaining(['-c:v h264', '-c:a copy', '-vf format=yuv420p']), twoPass: false, }), ); diff --git a/server/src/services/media.service.ts b/server/src/services/media.service.ts index 8393f5dc76282..770e26b2436f1 100644 --- a/server/src/services/media.service.ts +++ b/server/src/services/media.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { dirname } from 'node:path'; import { StorageCore } from 'src/cores/storage.core'; +import { OnJob } from 'src/decorators'; import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { @@ -19,11 +20,10 @@ import { } from 'src/enum'; import { UpsertFileOptions, WithoutProperty } from 'src/interfaces/asset.interface'; import { - IBaseJob, - IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobItem, JobName, + JobOf, JobStatus, QueueName, } from 'src/interfaces/job.interface'; @@ -39,7 +39,8 @@ export class MediaService extends BaseService { private maliOpenCL?: boolean; private devices?: string[]; - async handleQueueGenerateThumbnails({ force }: IBaseJob): Promise { + @OnJob({ name: JobName.QUEUE_GENERATE_THUMBNAILS, queue: QueueName.THUMBNAIL_GENERATION }) + async handleQueueGenerateThumbnails({ force }: JobOf): Promise { const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => { return force ? this.assetRepository.getAll(pagination, { @@ -90,6 +91,7 @@ export class MediaService extends BaseService { return JobStatus.SUCCESS; } + @OnJob({ name: JobName.QUEUE_MIGRATION, queue: QueueName.MIGRATION }) async handleQueueMigration(): Promise { const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => this.assetRepository.getAll(pagination), @@ -120,7 +122,8 @@ export class MediaService extends BaseService { return JobStatus.SUCCESS; } - async handleAssetMigration({ id }: IEntityJob): Promise { + @OnJob({ name: JobName.MIGRATE_ASSET, queue: QueueName.MIGRATION }) + async handleAssetMigration({ id }: JobOf): Promise { const { image } = await this.getConfig({ withCache: true }); const [asset] = await this.assetRepository.getByIds([id], { files: true }); if (!asset) { @@ -134,7 +137,8 @@ export class MediaService extends BaseService { return JobStatus.SUCCESS; } - async handleGenerateThumbnails({ id }: IEntityJob): Promise { + @OnJob({ name: JobName.GENERATE_THUMBNAILS, queue: QueueName.THUMBNAIL_GENERATION }) + async handleGenerateThumbnails({ id }: JobOf): Promise { const asset = await this.assetRepository.getById(id, { exifInfo: true, files: true }); if (!asset) { this.logger.warn(`Thumbnail generation failed for asset ${id}: not found`); @@ -210,7 +214,8 @@ export class MediaService extends BaseService { const colorspace = this.isSRGB(asset) ? Colorspace.SRGB : image.colorspace; const processInvalidImages = process.env.IMMICH_PROCESS_INVALID_IMAGES === 'true'; - const decodeOptions = { colorspace, processInvalidImages, size: image.preview.size }; + const orientation = useExtracted && asset.exifInfo?.orientation ? Number(asset.exifInfo.orientation) : undefined; + const decodeOptions = { colorspace, processInvalidImages, size: image.preview.size, orientation }; const { data, info } = await this.mediaRepository.decodeImage(inputPath, decodeOptions); const options = { colorspace, processInvalidImages, raw: info }; @@ -234,7 +239,7 @@ export class MediaService extends BaseService { const thumbnailPath = StorageCore.getImagePath(asset, AssetPathType.THUMBNAIL, image.thumbnail.format); this.storageCore.ensureFolders(previewPath); - const { audioStreams, videoStreams } = await this.mediaRepository.probe(asset.originalPath); + const { format, audioStreams, videoStreams } = await this.mediaRepository.probe(asset.originalPath); const mainVideoStream = this.getMainStream(videoStreams); if (!mainVideoStream) { throw new Error(`No video streams found for asset ${asset.id}`); @@ -243,9 +248,14 @@ export class MediaService extends BaseService { const previewConfig = ThumbnailConfig.create({ ...ffmpeg, targetResolution: image.preview.size.toString() }); const thumbnailConfig = ThumbnailConfig.create({ ...ffmpeg, targetResolution: image.thumbnail.size.toString() }); - - const previewOptions = previewConfig.getCommand(TranscodeTarget.VIDEO, mainVideoStream, mainAudioStream); - const thumbnailOptions = thumbnailConfig.getCommand(TranscodeTarget.VIDEO, mainVideoStream, mainAudioStream); + const previewOptions = previewConfig.getCommand(TranscodeTarget.VIDEO, mainVideoStream, mainAudioStream, format); + const thumbnailOptions = thumbnailConfig.getCommand( + TranscodeTarget.VIDEO, + mainVideoStream, + mainAudioStream, + format, + ); + this.logger.error(format.formatName); await this.mediaRepository.transcode(asset.originalPath, previewPath, previewOptions); await this.mediaRepository.transcode(asset.originalPath, thumbnailPath, thumbnailOptions); @@ -257,7 +267,8 @@ export class MediaService extends BaseService { return { previewPath, thumbnailPath, thumbhash }; } - async handleQueueVideoConversion(job: IBaseJob): Promise { + @OnJob({ name: JobName.QUEUE_VIDEO_CONVERSION, queue: QueueName.VIDEO_CONVERSION }) + async handleQueueVideoConversion(job: JobOf): Promise { const { force } = job; const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => { @@ -275,7 +286,8 @@ export class MediaService extends BaseService { return JobStatus.SUCCESS; } - async handleVideoConversion({ id }: IEntityJob): Promise { + @OnJob({ name: JobName.VIDEO_CONVERSION, queue: QueueName.VIDEO_CONVERSION }) + async handleVideoConversion({ id }: JobOf): Promise { const [asset] = await this.assetRepository.getByIds([id]); if (!asset || asset.type !== AssetType.VIDEO) { return JobStatus.FAILED; @@ -407,7 +419,7 @@ export class MediaService extends BaseService { const isLargerThanTargetBitrate = stream.bitrate > this.parseBitrateToBps(ffmpegConfig.maxBitrate); const isTargetVideoCodec = ffmpegConfig.acceptedVideoCodecs.includes(stream.codecName as VideoCodec); - const isRequired = !isTargetVideoCodec || stream.isHDR; + const isRequired = !isTargetVideoCodec || !stream.pixelFormat.endsWith('420p'); switch (ffmpegConfig.transcode) { case TranscodePolicy.DISABLED: { diff --git a/server/src/services/metadata.service.spec.ts b/server/src/services/metadata.service.spec.ts index cc6eae6e3b511..e3dc19111b5cf 100644 --- a/server/src/services/metadata.service.spec.ts +++ b/server/src/services/metadata.service.spec.ts @@ -3,9 +3,10 @@ import { randomBytes } from 'node:crypto'; import { Stats } from 'node:fs'; import { constants } from 'node:fs/promises'; import { ExifEntity } from 'src/entities/exif.entity'; -import { AssetType, ImmichWorker, SourceType } from 'src/enum'; +import { AssetType, ExifOrientation, ImmichWorker, SourceType } from 'src/enum'; import { IAlbumRepository } from 'src/interfaces/album.interface'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface'; +import { IConfigRepository } from 'src/interfaces/config.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; @@ -17,7 +18,7 @@ import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { ITagRepository } from 'src/interfaces/tag.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; -import { MetadataService, Orientation } from 'src/services/metadata.service'; +import { MetadataService } from 'src/services/metadata.service'; import { assetStub } from 'test/fixtures/asset.stub'; import { fileStub } from 'test/fixtures/file.stub'; import { probeStub } from 'test/fixtures/media.stub'; @@ -32,6 +33,7 @@ describe(MetadataService.name, () => { let albumMock: Mocked; let assetMock: Mocked; + let configMock: Mocked; let cryptoMock: Mocked; let eventMock: Mocked; let jobMock: Mocked; @@ -55,6 +57,7 @@ describe(MetadataService.name, () => { sut, albumMock, assetMock, + configMock, cryptoMock, eventMock, jobMock, @@ -70,6 +73,8 @@ describe(MetadataService.name, () => { mockReadTags(); + configMock.getWorker.mockReturnValue(ImmichWorker.MICROSERVICES); + delete process.env.TZ; }); @@ -83,17 +88,16 @@ describe(MetadataService.name, () => { describe('onBootstrapEvent', () => { it('should pause and resume queue during init', async () => { - await sut.onBootstrap(ImmichWorker.MICROSERVICES); + await sut.onBootstrap(); expect(jobMock.pause).toHaveBeenCalledTimes(1); expect(mapMock.init).toHaveBeenCalledTimes(1); expect(jobMock.resume).toHaveBeenCalledTimes(1); }); - it('should return if reverse geocoding is disabled', async () => { - systemMock.get.mockResolvedValue({ reverseGeocoding: { enabled: false } }); - - await sut.onBootstrap(ImmichWorker.MICROSERVICES); + it('should return if running on api', async () => { + configMock.getWorker.mockReturnValue(ImmichWorker.API); + await sut.onBootstrap(); expect(jobMock.pause).not.toHaveBeenCalled(); expect(mapMock.init).not.toHaveBeenCalled(); @@ -535,7 +539,7 @@ describe(MetadataService.name, () => { expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.video.id], { faces: { person: false } }); expect(assetMock.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ orientation: Orientation.Rotate270CW.toString() }), + expect.objectContaining({ orientation: ExifOrientation.Rotate270CW.toString() }), ); }); diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index a45bcd4252533..d8da095abfb65 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -7,24 +7,16 @@ import { constants } from 'node:fs/promises'; import path from 'node:path'; import { SystemConfig } from 'src/config'; import { StorageCore } from 'src/cores/storage.core'; -import { OnEvent } from 'src/decorators'; +import { OnEvent, OnJob } from 'src/decorators'; import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { AssetEntity } from 'src/entities/asset.entity'; import { ExifEntity } from 'src/entities/exif.entity'; import { PersonEntity } from 'src/entities/person.entity'; -import { AssetType, ImmichWorker, SourceType } from 'src/enum'; +import { AssetType, ExifOrientation, ImmichWorker, SourceType } from 'src/enum'; import { WithoutProperty } from 'src/interfaces/asset.interface'; import { DatabaseLock } from 'src/interfaces/database.interface'; import { ArgOf } from 'src/interfaces/event.interface'; -import { - IBaseJob, - IEntityJob, - ISidecarWriteJob, - JobName, - JOBS_ASSET_PAGINATION_SIZE, - JobStatus, - QueueName, -} from 'src/interfaces/job.interface'; +import { JobName, JobOf, JOBS_ASSET_PAGINATION_SIZE, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { ReverseGeocodeResult } from 'src/interfaces/map.interface'; import { ImmichTags } from 'src/interfaces/metadata.interface'; import { BaseService } from 'src/services/base.service'; @@ -44,17 +36,6 @@ const EXIF_DATE_TAGS: Array = [ 'DateTimeCreated', ]; -export enum Orientation { - Horizontal = 1, - MirrorHorizontal = 2, - Rotate180 = 3, - MirrorVertical = 4, - MirrorHorizontalRotate270CW = 5, - Rotate90CW = 6, - MirrorHorizontalRotate90CW = 7, - Rotate270CW = 8, -} - const validate = (value: T): NonNullable | null => { // handle lists of numbers if (Array.isArray(value)) { @@ -88,12 +69,12 @@ const validateRange = (value: number | undefined, min: number, max: number): Non @Injectable() export class MetadataService extends BaseService { @OnEvent({ name: 'app.bootstrap' }) - async onBootstrap(app: ArgOf<'app.bootstrap'>) { - if (app !== ImmichWorker.MICROSERVICES) { + async onBootstrap() { + if (this.worker !== ImmichWorker.MICROSERVICES) { return; } - const config = await this.getConfig({ withCache: false }); - await this.init(config); + this.logger.log('Bootstrapping metadata service'); + await this.init(); } @OnEvent({ name: 'app.shutdown' }) @@ -101,17 +82,8 @@ export class MetadataService extends BaseService { await this.metadataRepository.teardown(); } - @OnEvent({ name: 'config.update' }) - async onConfigUpdate({ newConfig }: ArgOf<'config.update'>) { - await this.init(newConfig); - } - - private async init({ reverseGeocoding }: SystemConfig) { - const { enabled } = reverseGeocoding; - - if (!enabled) { - return; - } + private async init() { + this.logger.log('Initializing metadata service'); try { await this.jobRepository.pause(QueueName.METADATA_EXTRACTION); @@ -124,7 +96,8 @@ export class MetadataService extends BaseService { } } - async handleLivePhotoLinking(job: IEntityJob): Promise { + @OnJob({ name: JobName.LINK_LIVE_PHOTOS, queue: QueueName.METADATA_EXTRACTION }) + async handleLivePhotoLinking(job: JobOf): Promise { const { id } = job; const [asset] = await this.assetRepository.getByIds([id], { exifInfo: true }); if (!asset?.exifInfo) { @@ -159,7 +132,8 @@ export class MetadataService extends BaseService { return JobStatus.SUCCESS; } - async handleQueueMetadataExtraction(job: IBaseJob): Promise { + @OnJob({ name: JobName.QUEUE_METADATA_EXTRACTION, queue: QueueName.METADATA_EXTRACTION }) + async handleQueueMetadataExtraction(job: JobOf): Promise { const { force } = job; const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => { return force @@ -176,7 +150,8 @@ export class MetadataService extends BaseService { return JobStatus.SUCCESS; } - async handleMetadataExtraction({ id }: IEntityJob): Promise { + @OnJob({ name: JobName.METADATA_EXTRACTION, queue: QueueName.METADATA_EXTRACTION }) + async handleMetadataExtraction({ id }: JobOf): Promise { const { metadata, reverseGeocoding } = await this.getConfig({ withCache: true }); const [asset] = await this.assetRepository.getByIds([id], { faces: { person: false } }); if (!asset) { @@ -192,6 +167,8 @@ export class MetadataService extends BaseService { const { dateTimeOriginal, localDateTime, timeZone, modifyDate } = this.getDates(asset, exifTags); const { latitude, longitude, country, state, city } = await this.getGeo(exifTags, reverseGeocoding); + const { width, height } = this.getImageDimensions(exifTags); + const exifData: Partial = { assetId: asset.id, @@ -209,8 +186,8 @@ export class MetadataService extends BaseService { // image/file fileSizeInByte: stats.size, - exifImageHeight: validate(exifTags.ImageHeight), - exifImageWidth: validate(exifTags.ImageWidth), + exifImageHeight: validate(height), + exifImageWidth: validate(width), orientation: validate(exifTags.Orientation)?.toString() ?? null, projectionType: exifTags.ProjectionType ? String(exifTags.ProjectionType).toUpperCase() : null, bitsPerSample: this.getBitsPerSample(exifTags), @@ -260,7 +237,8 @@ export class MetadataService extends BaseService { return JobStatus.SUCCESS; } - async handleQueueSidecar(job: IBaseJob): Promise { + @OnJob({ name: JobName.QUEUE_SIDECAR, queue: QueueName.SIDECAR }) + async handleQueueSidecar(job: JobOf): Promise { const { force } = job; const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => { return force @@ -280,11 +258,13 @@ export class MetadataService extends BaseService { return JobStatus.SUCCESS; } - handleSidecarSync({ id }: IEntityJob): Promise { + @OnJob({ name: JobName.SIDECAR_SYNC, queue: QueueName.SIDECAR }) + handleSidecarSync({ id }: JobOf): Promise { return this.processSidecar(id, true); } - handleSidecarDiscovery({ id }: IEntityJob): Promise { + @OnJob({ name: JobName.SIDECAR_DISCOVERY, queue: QueueName.SIDECAR }) + handleSidecarDiscovery({ id }: JobOf): Promise { return this.processSidecar(id, false); } @@ -298,7 +278,8 @@ export class MetadataService extends BaseService { await this.jobRepository.queue({ name: JobName.SIDECAR_WRITE, data: { id: assetId, tags: true } }); } - async handleSidecarWrite(job: ISidecarWriteJob): Promise { + @OnJob({ name: JobName.SIDECAR_WRITE, queue: QueueName.SIDECAR }) + async handleSidecarWrite(job: JobOf): Promise { const { id, description, dateTimeOriginal, latitude, longitude, rating, tags } = job; const [asset] = await this.assetRepository.getByIds([id], { tags: true }); if (!asset) { @@ -334,6 +315,19 @@ export class MetadataService extends BaseService { return JobStatus.SUCCESS; } + private getImageDimensions(exifTags: ImmichTags): { width?: number; height?: number } { + /* + * The "true" values for width and height are a bit hidden, depending on the camera model and file format. + * For RAW images in the CR2 or RAF format, the "ImageSize" value seems to be correct, + * but ImageWidth and ImageHeight are not correct (they contain the dimensions of the preview image). + */ + let [width, height] = exifTags.ImageSize?.split('x').map((dim) => Number.parseInt(dim) || undefined) || []; + if (!width || !height) { + [width, height] = [exifTags.ImageWidth, exifTags.ImageHeight]; + } + return { width, height }; + } + private async getExifTags(asset: AssetEntity): Promise { const mediaTags = await this.metadataRepository.readTags(asset.originalPath); const sidecarTags = asset.sidecarPath ? await this.metadataRepository.readTags(asset.sidecarPath) : {}; @@ -671,19 +665,19 @@ export class MetadataService extends BaseService { if (videoStreams[0]) { switch (videoStreams[0].rotation) { case -90: { - tags.Orientation = Orientation.Rotate90CW; + tags.Orientation = ExifOrientation.Rotate90CW; break; } case 0: { - tags.Orientation = Orientation.Horizontal; + tags.Orientation = ExifOrientation.Horizontal; break; } case 90: { - tags.Orientation = Orientation.Rotate270CW; + tags.Orientation = ExifOrientation.Rotate270CW; break; } case 180: { - tags.Orientation = Orientation.Rotate180; + tags.Orientation = ExifOrientation.Rotate180; break; } } diff --git a/server/src/services/microservices.service.ts b/server/src/services/microservices.service.ts deleted file mode 100644 index 275103d05c4b0..0000000000000 --- a/server/src/services/microservices.service.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { OnEvent } from 'src/decorators'; -import { ImmichWorker } from 'src/enum'; -import { ArgOf } from 'src/interfaces/event.interface'; -import { IDeleteFilesJob, JobName } from 'src/interfaces/job.interface'; -import { AssetService } from 'src/services/asset.service'; -import { AuditService } from 'src/services/audit.service'; -import { DuplicateService } from 'src/services/duplicate.service'; -import { JobService } from 'src/services/job.service'; -import { LibraryService } from 'src/services/library.service'; -import { MediaService } from 'src/services/media.service'; -import { MetadataService } from 'src/services/metadata.service'; -import { NotificationService } from 'src/services/notification.service'; -import { PersonService } from 'src/services/person.service'; -import { SessionService } from 'src/services/session.service'; -import { SmartInfoService } from 'src/services/smart-info.service'; -import { StorageTemplateService } from 'src/services/storage-template.service'; -import { StorageService } from 'src/services/storage.service'; -import { TagService } from 'src/services/tag.service'; -import { TrashService } from 'src/services/trash.service'; -import { UserService } from 'src/services/user.service'; -import { VersionService } from 'src/services/version.service'; - -@Injectable() -export class MicroservicesService { - constructor( - private auditService: AuditService, - private assetService: AssetService, - private jobService: JobService, - private libraryService: LibraryService, - private mediaService: MediaService, - private metadataService: MetadataService, - private notificationService: NotificationService, - private personService: PersonService, - private smartInfoService: SmartInfoService, - private sessionService: SessionService, - private storageTemplateService: StorageTemplateService, - private storageService: StorageService, - private tagService: TagService, - private trashService: TrashService, - private userService: UserService, - private duplicateService: DuplicateService, - private versionService: VersionService, - ) {} - - @OnEvent({ name: 'app.bootstrap' }) - async onBootstrap(app: ArgOf<'app.bootstrap'>) { - if (app !== ImmichWorker.MICROSERVICES) { - return; - } - - await this.jobService.init({ - [JobName.ASSET_DELETION]: (data) => this.assetService.handleAssetDeletion(data), - [JobName.ASSET_DELETION_CHECK]: () => this.assetService.handleAssetDeletionCheck(), - [JobName.DELETE_FILES]: (data: IDeleteFilesJob) => this.storageService.handleDeleteFiles(data), - [JobName.CLEAN_OLD_AUDIT_LOGS]: () => this.auditService.handleCleanup(), - [JobName.CLEAN_OLD_SESSION_TOKENS]: () => this.sessionService.handleCleanup(), - [JobName.USER_DELETE_CHECK]: () => this.userService.handleUserDeleteCheck(), - [JobName.USER_DELETION]: (data) => this.userService.handleUserDelete(data), - [JobName.USER_SYNC_USAGE]: () => this.userService.handleUserSyncUsage(), - [JobName.QUEUE_SMART_SEARCH]: (data) => this.smartInfoService.handleQueueEncodeClip(data), - [JobName.SMART_SEARCH]: (data) => this.smartInfoService.handleEncodeClip(data), - [JobName.QUEUE_DUPLICATE_DETECTION]: (data) => this.duplicateService.handleQueueSearchDuplicates(data), - [JobName.DUPLICATE_DETECTION]: (data) => this.duplicateService.handleSearchDuplicates(data), - [JobName.STORAGE_TEMPLATE_MIGRATION]: () => this.storageTemplateService.handleMigration(), - [JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE]: (data) => this.storageTemplateService.handleMigrationSingle(data), - [JobName.QUEUE_MIGRATION]: () => this.mediaService.handleQueueMigration(), - [JobName.MIGRATE_ASSET]: (data) => this.mediaService.handleAssetMigration(data), - [JobName.MIGRATE_PERSON]: (data) => this.personService.handlePersonMigration(data), - [JobName.QUEUE_GENERATE_THUMBNAILS]: (data) => this.mediaService.handleQueueGenerateThumbnails(data), - [JobName.GENERATE_THUMBNAILS]: (data) => this.mediaService.handleGenerateThumbnails(data), - [JobName.QUEUE_VIDEO_CONVERSION]: (data) => this.mediaService.handleQueueVideoConversion(data), - [JobName.VIDEO_CONVERSION]: (data) => this.mediaService.handleVideoConversion(data), - [JobName.QUEUE_METADATA_EXTRACTION]: (data) => this.metadataService.handleQueueMetadataExtraction(data), - [JobName.METADATA_EXTRACTION]: (data) => this.metadataService.handleMetadataExtraction(data), - [JobName.LINK_LIVE_PHOTOS]: (data) => this.metadataService.handleLivePhotoLinking(data), - [JobName.QUEUE_FACE_DETECTION]: (data) => this.personService.handleQueueDetectFaces(data), - [JobName.FACE_DETECTION]: (data) => this.personService.handleDetectFaces(data), - [JobName.QUEUE_FACIAL_RECOGNITION]: (data) => this.personService.handleQueueRecognizeFaces(data), - [JobName.FACIAL_RECOGNITION]: (data) => this.personService.handleRecognizeFaces(data), - [JobName.GENERATE_PERSON_THUMBNAIL]: (data) => this.personService.handleGeneratePersonThumbnail(data), - [JobName.PERSON_CLEANUP]: () => this.personService.handlePersonCleanup(), - [JobName.QUEUE_SIDECAR]: (data) => this.metadataService.handleQueueSidecar(data), - [JobName.SIDECAR_DISCOVERY]: (data) => this.metadataService.handleSidecarDiscovery(data), - [JobName.SIDECAR_SYNC]: (data) => this.metadataService.handleSidecarSync(data), - [JobName.SIDECAR_WRITE]: (data) => this.metadataService.handleSidecarWrite(data), - [JobName.LIBRARY_QUEUE_SYNC_ALL]: () => this.libraryService.handleQueueSyncAll(), - [JobName.LIBRARY_QUEUE_SYNC_FILES]: (data) => this.libraryService.handleQueueSyncFiles(data), //Queues all files paths on disk - [JobName.LIBRARY_SYNC_FILE]: (data) => this.libraryService.handleSyncFile(data), //Handles a single path on disk //Watcher calls for new files - [JobName.LIBRARY_QUEUE_SYNC_ASSETS]: (data) => this.libraryService.handleQueueSyncAssets(data), //Queues all library assets - [JobName.LIBRARY_SYNC_ASSET]: (data) => this.libraryService.handleSyncAsset(data), //Handles all library assets // Watcher calls for unlink and changed - [JobName.LIBRARY_DELETE]: (data) => this.libraryService.handleDeleteLibrary(data), - [JobName.LIBRARY_QUEUE_CLEANUP]: () => this.libraryService.handleQueueCleanup(), - [JobName.SEND_EMAIL]: (data) => this.notificationService.handleSendEmail(data), - [JobName.NOTIFY_ALBUM_INVITE]: (data) => this.notificationService.handleAlbumInvite(data), - [JobName.NOTIFY_ALBUM_UPDATE]: (data) => this.notificationService.handleAlbumUpdate(data), - [JobName.NOTIFY_SIGNUP]: (data) => this.notificationService.handleUserSignup(data), - [JobName.TAG_CLEANUP]: () => this.tagService.handleTagCleanup(), - [JobName.VERSION_CHECK]: () => this.versionService.handleVersionCheck(), - [JobName.QUEUE_TRASH_EMPTY]: () => this.trashService.handleQueueEmptyTrash(), - }); - } -} diff --git a/server/src/services/notification.service.spec.ts b/server/src/services/notification.service.spec.ts index d07d06443aee1..76da12bbd6abb 100644 --- a/server/src/services/notification.service.spec.ts +++ b/server/src/services/notification.service.spec.ts @@ -77,7 +77,7 @@ describe(NotificationService.name, () => { describe('onConfigUpdate', () => { it('should emit client and server events', () => { - const update = { newConfig: defaults }; + const update = { oldConfig: defaults, newConfig: defaults }; expect(sut.onConfigUpdate(update)).toBeUndefined(); expect(eventMock.clientBroadcast).toHaveBeenCalledWith('on_config_update'); expect(eventMock.serverSend).toHaveBeenCalledWith('config.update', update); diff --git a/server/src/services/notification.service.ts b/server/src/services/notification.service.ts index c3c7727468240..e7c0201963036 100644 --- a/server/src/services/notification.service.ts +++ b/server/src/services/notification.service.ts @@ -1,17 +1,16 @@ import { BadRequestException, Injectable } from '@nestjs/common'; -import { OnEvent } from 'src/decorators'; +import { OnEvent, OnJob } from 'src/decorators'; import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto'; import { AlbumEntity } from 'src/entities/album.entity'; import { ArgOf } from 'src/interfaces/event.interface'; import { - IEmailJob, IEntityJob, - INotifyAlbumInviteJob, INotifyAlbumUpdateJob, - INotifySignupJob, JobItem, JobName, + JobOf, JobStatus, + QueueName, } from 'src/interfaces/job.interface'; import { EmailImageAttachment, EmailTemplate } from 'src/interfaces/notification.interface'; import { BaseService } from 'src/services/base.service'; @@ -176,7 +175,8 @@ export class NotificationService extends BaseService { return { messageId }; } - async handleUserSignup({ id, tempPassword }: INotifySignupJob) { + @OnJob({ name: JobName.NOTIFY_SIGNUP, queue: QueueName.NOTIFICATION }) + async handleUserSignup({ id, tempPassword }: JobOf) { const user = await this.userRepository.get(id, { withDeleted: false }); if (!user) { return JobStatus.SKIPPED; @@ -207,7 +207,8 @@ export class NotificationService extends BaseService { return JobStatus.SUCCESS; } - async handleAlbumInvite({ id, recipientId }: INotifyAlbumInviteJob) { + @OnJob({ name: JobName.NOTIFY_ALBUM_INVITE, queue: QueueName.NOTIFICATION }) + async handleAlbumInvite({ id, recipientId }: JobOf) { const album = await this.albumRepository.getById(id, { withAssets: false }); if (!album) { return JobStatus.SKIPPED; @@ -254,7 +255,8 @@ export class NotificationService extends BaseService { return JobStatus.SUCCESS; } - async handleAlbumUpdate({ id, recipientIds }: INotifyAlbumUpdateJob) { + @OnJob({ name: JobName.NOTIFY_ALBUM_UPDATE, queue: QueueName.NOTIFICATION }) + async handleAlbumUpdate({ id, recipientIds }: JobOf) { const album = await this.albumRepository.getById(id, { withAssets: false }); if (!album) { @@ -312,7 +314,8 @@ export class NotificationService extends BaseService { return JobStatus.SUCCESS; } - async handleSendEmail(data: IEmailJob): Promise { + @OnJob({ name: JobName.SEND_EMAIL, queue: QueueName.NOTIFICATION }) + async handleSendEmail(data: JobOf): Promise { const { notifications } = await this.getConfig({ withCache: false }); if (!notifications.smtp.enabled) { return JobStatus.SKIPPED; diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts index e5f016d8ef24d..5b6e721eab0bc 100644 --- a/server/src/services/person.service.ts +++ b/server/src/services/person.service.ts @@ -1,6 +1,7 @@ import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { FACE_THUMBNAIL_SIZE } from 'src/constants'; import { StorageCore } from 'src/cores/storage.core'; +import { OnJob } from 'src/decorators'; import { BulkIdErrorReason, BulkIdResponseDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { @@ -33,13 +34,10 @@ import { } from 'src/enum'; import { WithoutProperty } from 'src/interfaces/asset.interface'; import { - IBaseJob, - IDeferrableJob, - IEntityJob, - INightlyJob, JOBS_ASSET_PAGINATION_SIZE, JobItem, JobName, + JobOf, JobStatus, QueueName, } from 'src/interfaces/job.interface'; @@ -231,13 +229,15 @@ export class PersonService extends BaseService { this.logger.debug(`Deleted ${people.length} people`); } + @OnJob({ name: JobName.PERSON_CLEANUP, queue: QueueName.BACKGROUND_TASK }) async handlePersonCleanup(): Promise { const people = await this.personRepository.getAllWithoutFaces(); await this.delete(people); return JobStatus.SUCCESS; } - async handleQueueDetectFaces({ force }: IBaseJob): Promise { + @OnJob({ name: JobName.QUEUE_FACE_DETECTION, queue: QueueName.FACE_DETECTION }) + async handleQueueDetectFaces({ force }: JobOf): Promise { const { machineLearning } = await this.getConfig({ withCache: false }); if (!isFacialRecognitionEnabled(machineLearning)) { return JobStatus.SKIPPED; @@ -272,7 +272,8 @@ export class PersonService extends BaseService { return JobStatus.SUCCESS; } - async handleDetectFaces({ id }: IEntityJob): Promise { + @OnJob({ name: JobName.FACE_DETECTION, queue: QueueName.FACE_DETECTION }) + async handleDetectFaces({ id }: JobOf): Promise { const { machineLearning } = await this.getConfig({ withCache: true }); if (!isFacialRecognitionEnabled(machineLearning)) { return JobStatus.SKIPPED; @@ -376,7 +377,8 @@ export class PersonService extends BaseService { return intersection / union; } - async handleQueueRecognizeFaces({ force, nightly }: INightlyJob): Promise { + @OnJob({ name: JobName.QUEUE_FACIAL_RECOGNITION, queue: QueueName.FACIAL_RECOGNITION }) + async handleQueueRecognizeFaces({ force, nightly }: JobOf): Promise { const { machineLearning } = await this.getConfig({ withCache: false }); if (!isFacialRecognitionEnabled(machineLearning)) { return JobStatus.SKIPPED; @@ -426,7 +428,8 @@ export class PersonService extends BaseService { return JobStatus.SUCCESS; } - async handleRecognizeFaces({ id, deferred }: IDeferrableJob): Promise { + @OnJob({ name: JobName.FACIAL_RECOGNITION, queue: QueueName.FACIAL_RECOGNITION }) + async handleRecognizeFaces({ id, deferred }: JobOf): Promise { const { machineLearning } = await this.getConfig({ withCache: true }); if (!isFacialRecognitionEnabled(machineLearning)) { return JobStatus.SKIPPED; @@ -509,7 +512,8 @@ export class PersonService extends BaseService { return JobStatus.SUCCESS; } - async handlePersonMigration({ id }: IEntityJob): Promise { + @OnJob({ name: JobName.MIGRATE_PERSON, queue: QueueName.MIGRATION }) + async handlePersonMigration({ id }: JobOf): Promise { const person = await this.personRepository.getById(id); if (!person) { return JobStatus.FAILED; @@ -520,7 +524,8 @@ export class PersonService extends BaseService { return JobStatus.SUCCESS; } - async handleGeneratePersonThumbnail(data: IEntityJob): Promise { + @OnJob({ name: JobName.GENERATE_PERSON_THUMBNAIL, queue: QueueName.THUMBNAIL_GENERATION }) + async handleGeneratePersonThumbnail(data: JobOf): Promise { const { machineLearning, metadata, image } = await this.getConfig({ withCache: true }); if (!isFacialRecognitionEnabled(machineLearning) && !isFaceImportEnabled(metadata)) { return JobStatus.SKIPPED; diff --git a/server/src/services/search.service.spec.ts b/server/src/services/search.service.spec.ts index e0b03f31aee3b..0f95d88083265 100644 --- a/server/src/services/search.service.spec.ts +++ b/server/src/services/search.service.spec.ts @@ -47,14 +47,9 @@ describe(SearchService.name, () => { fieldName: 'exifInfo.city', items: [{ value: 'Paris', data: assetStub.image.id }], }); - assetMock.getAssetIdByTag.mockResolvedValue({ - fieldName: 'smartInfo.tags', - items: [{ value: 'train', data: assetStub.imageFrom2015.id }], - }); assetMock.getByIdsWithAllRelations.mockResolvedValue([assetStub.image, assetStub.imageFrom2015]); const expectedResponse = [ { fieldName: 'exifInfo.city', items: [{ value: 'Paris', data: mapAsset(assetStub.image) }] }, - { fieldName: 'smartInfo.tags', items: [{ value: 'train', data: mapAsset(assetStub.imageFrom2015) }] }, ]; const result = await sut.getExploreData(authStub.user1); diff --git a/server/src/services/search.service.ts b/server/src/services/search.service.ts index 03ffbe97db14e..04d3addb6353e 100644 --- a/server/src/services/search.service.ts +++ b/server/src/services/search.service.ts @@ -34,10 +34,8 @@ export class SearchService extends BaseService { async getExploreData(auth: AuthDto): Promise[]> { const options = { maxFields: 12, minAssetsPerField: 5 }; - const results = await Promise.all([ - this.assetRepository.getAssetIdByCity(auth.user.id, options), - this.assetRepository.getAssetIdByTag(auth.user.id, options), - ]); + const result = await this.assetRepository.getAssetIdByCity(auth.user.id, options); + const results = [result]; const assetIds = new Set(results.flatMap((field) => field.items.map((item) => item.data))); const assets = await this.assetRepository.getByIdsWithAllRelations([...assetIds]); const assetMap = new Map(assets.map((asset) => [asset.id, mapAsset(asset)])); diff --git a/server/src/services/server.service.spec.ts b/server/src/services/server.service.spec.ts index ab6eb3b1a4f05..475d1d6193258 100644 --- a/server/src/services/server.service.spec.ts +++ b/server/src/services/server.service.spec.ts @@ -185,6 +185,8 @@ describe(ServerService.name, () => { photos: 10, videos: 11, usage: 12_345, + usagePhotos: 1, + usageVideos: 11_345, quotaSizeInBytes: 0, }, { @@ -193,6 +195,8 @@ describe(ServerService.name, () => { photos: 10, videos: 20, usage: 123_456, + usagePhotos: 100, + usageVideos: 23_456, quotaSizeInBytes: 0, }, { @@ -201,6 +205,8 @@ describe(ServerService.name, () => { photos: 100, videos: 0, usage: 987_654, + usagePhotos: 900, + usageVideos: 87_654, quotaSizeInBytes: 0, }, ]); @@ -209,11 +215,15 @@ describe(ServerService.name, () => { photos: 120, videos: 31, usage: 1_123_455, + usagePhotos: 1001, + usageVideos: 122_455, usageByUser: [ { photos: 10, quotaSizeInBytes: 0, usage: 12_345, + usagePhotos: 1, + usageVideos: 11_345, userName: '1 User', userId: 'user1', videos: 11, @@ -222,6 +232,8 @@ describe(ServerService.name, () => { photos: 10, quotaSizeInBytes: 0, usage: 123_456, + usagePhotos: 100, + usageVideos: 23_456, userName: '2 User', userId: 'user2', videos: 20, @@ -230,6 +242,8 @@ describe(ServerService.name, () => { photos: 100, quotaSizeInBytes: 0, usage: 987_654, + usagePhotos: 900, + usageVideos: 87_654, userName: '3 User', userId: 'user3', videos: 0, diff --git a/server/src/services/server.service.ts b/server/src/services/server.service.ts index 3fc319a2fd938..7df322a84e5d9 100644 --- a/server/src/services/server.service.ts +++ b/server/src/services/server.service.ts @@ -126,11 +126,16 @@ export class ServerService extends BaseService { usage.photos = user.photos; usage.videos = user.videos; usage.usage = user.usage; + usage.usagePhotos = user.usagePhotos; + usage.usageVideos = user.usageVideos; usage.quotaSizeInBytes = user.quotaSizeInBytes; serverStats.photos += usage.photos; serverStats.videos += usage.videos; serverStats.usage += usage.usage; + serverStats.usagePhotos += usage.usagePhotos; + serverStats.usageVideos += usage.usageVideos; + serverStats.usageByUser.push(usage); } diff --git a/server/src/services/session.service.ts b/server/src/services/session.service.ts index 2e27942c663a1..68df7828ad784 100644 --- a/server/src/services/session.service.ts +++ b/server/src/services/session.service.ts @@ -1,14 +1,16 @@ import { Injectable } from '@nestjs/common'; import { DateTime } from 'luxon'; +import { OnJob } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { SessionResponseDto, mapSession } from 'src/dtos/session.dto'; import { Permission } from 'src/enum'; -import { JobStatus } from 'src/interfaces/job.interface'; +import { JobName, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { BaseService } from 'src/services/base.service'; @Injectable() export class SessionService extends BaseService { - async handleCleanup() { + @OnJob({ name: JobName.CLEAN_OLD_SESSION_TOKENS, queue: QueueName.BACKGROUND_TASK }) + async handleCleanup(): Promise { const sessions = await this.sessionRepository.search({ updatedBefore: DateTime.now().minus({ days: 90 }).toJSDate(), }); diff --git a/server/src/services/smart-info.service.spec.ts b/server/src/services/smart-info.service.spec.ts index f53822a9e2251..4ae595919450f 100644 --- a/server/src/services/smart-info.service.spec.ts +++ b/server/src/services/smart-info.service.spec.ts @@ -1,6 +1,7 @@ import { SystemConfig } from 'src/config'; import { ImmichWorker } from 'src/enum'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface'; +import { IConfigRepository } from 'src/interfaces/config.interface'; import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; @@ -22,12 +23,14 @@ describe(SmartInfoService.name, () => { let machineLearningMock: Mocked; let searchMock: Mocked; let systemMock: Mocked; + let configMock: Mocked; beforeEach(() => { - ({ sut, assetMock, databaseMock, jobMock, machineLearningMock, searchMock, systemMock } = + ({ sut, assetMock, databaseMock, jobMock, machineLearningMock, searchMock, systemMock, configMock } = newTestService(SmartInfoService)); assetMock.getByIds.mockResolvedValue([assetStub.image]); + configMock.getWorker.mockReturnValue(ImmichWorker.MICROSERVICES); }); it('should work', () => { @@ -63,11 +66,11 @@ describe(SmartInfoService.name, () => { }); }); - describe('onBootstrapEvent', () => { + describe('onConfigInit', () => { it('should return if not microservices', async () => { - await sut.onBootstrap(ImmichWorker.API); + configMock.getWorker.mockReturnValue(ImmichWorker.API); + await sut.onConfigInit({ newConfig: systemConfigStub.machineLearningEnabled as SystemConfig }); - expect(systemMock.get).not.toHaveBeenCalled(); expect(searchMock.getDimensionSize).not.toHaveBeenCalled(); expect(searchMock.setDimensionSize).not.toHaveBeenCalled(); expect(searchMock.deleteAllSearchEmbeddings).not.toHaveBeenCalled(); @@ -78,11 +81,8 @@ describe(SmartInfoService.name, () => { }); it('should return if machine learning is disabled', async () => { - systemMock.get.mockResolvedValue(systemConfigStub.machineLearningDisabled); + await sut.onConfigInit({ newConfig: systemConfigStub.machineLearningDisabled as SystemConfig }); - await sut.onBootstrap(ImmichWorker.MICROSERVICES); - - expect(systemMock.get).toHaveBeenCalledTimes(1); expect(searchMock.getDimensionSize).not.toHaveBeenCalled(); expect(searchMock.setDimensionSize).not.toHaveBeenCalled(); expect(searchMock.deleteAllSearchEmbeddings).not.toHaveBeenCalled(); @@ -95,9 +95,8 @@ describe(SmartInfoService.name, () => { it('should return if model and DB dimension size are equal', async () => { searchMock.getDimensionSize.mockResolvedValue(512); - await sut.onBootstrap(ImmichWorker.MICROSERVICES); + await sut.onConfigInit({ newConfig: systemConfigStub.machineLearningEnabled as SystemConfig }); - expect(systemMock.get).toHaveBeenCalledTimes(1); expect(searchMock.getDimensionSize).toHaveBeenCalledTimes(1); expect(searchMock.setDimensionSize).not.toHaveBeenCalled(); expect(searchMock.deleteAllSearchEmbeddings).not.toHaveBeenCalled(); @@ -111,9 +110,8 @@ describe(SmartInfoService.name, () => { searchMock.getDimensionSize.mockResolvedValue(768); jobMock.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); - await sut.onBootstrap(ImmichWorker.MICROSERVICES); + await sut.onConfigInit({ newConfig: systemConfigStub.machineLearningEnabled as SystemConfig }); - expect(systemMock.get).toHaveBeenCalledTimes(1); expect(searchMock.getDimensionSize).toHaveBeenCalledTimes(1); expect(searchMock.setDimensionSize).toHaveBeenCalledWith(512); expect(jobMock.getQueueStatus).toHaveBeenCalledTimes(1); @@ -126,9 +124,8 @@ describe(SmartInfoService.name, () => { searchMock.getDimensionSize.mockResolvedValue(768); jobMock.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: true }); - await sut.onBootstrap(ImmichWorker.MICROSERVICES); + await sut.onConfigInit({ newConfig: systemConfigStub.machineLearningEnabled as SystemConfig }); - expect(systemMock.get).toHaveBeenCalledTimes(1); expect(searchMock.getDimensionSize).toHaveBeenCalledTimes(1); expect(searchMock.setDimensionSize).toHaveBeenCalledWith(512); expect(jobMock.getQueueStatus).toHaveBeenCalledTimes(1); @@ -139,6 +136,22 @@ describe(SmartInfoService.name, () => { }); describe('onConfigUpdateEvent', () => { + it('should return if not microservices', async () => { + configMock.getWorker.mockReturnValue(ImmichWorker.API); + await sut.onConfigUpdate({ + newConfig: systemConfigStub.machineLearningEnabled as SystemConfig, + oldConfig: systemConfigStub.machineLearningEnabled as SystemConfig, + }); + + expect(searchMock.getDimensionSize).not.toHaveBeenCalled(); + expect(searchMock.setDimensionSize).not.toHaveBeenCalled(); + expect(searchMock.deleteAllSearchEmbeddings).not.toHaveBeenCalled(); + expect(jobMock.getQueueStatus).not.toHaveBeenCalled(); + expect(jobMock.pause).not.toHaveBeenCalled(); + expect(jobMock.waitForQueueCompletion).not.toHaveBeenCalled(); + expect(jobMock.resume).not.toHaveBeenCalled(); + }); + it('should return if machine learning is disabled', async () => { systemMock.get.mockResolvedValue(systemConfigStub.machineLearningDisabled); diff --git a/server/src/services/smart-info.service.ts b/server/src/services/smart-info.service.ts index 778f40c931618..9e1eb5b31b686 100644 --- a/server/src/services/smart-info.service.ts +++ b/server/src/services/smart-info.service.ts @@ -1,18 +1,11 @@ import { Injectable } from '@nestjs/common'; import { SystemConfig } from 'src/config'; -import { OnEvent } from 'src/decorators'; +import { OnEvent, OnJob } from 'src/decorators'; import { ImmichWorker } from 'src/enum'; import { WithoutProperty } from 'src/interfaces/asset.interface'; import { DatabaseLock } from 'src/interfaces/database.interface'; import { ArgOf } from 'src/interfaces/event.interface'; -import { - IBaseJob, - IEntityJob, - JOBS_ASSET_PAGINATION_SIZE, - JobName, - JobStatus, - QueueName, -} from 'src/interfaces/job.interface'; +import { JOBS_ASSET_PAGINATION_SIZE, JobName, JobOf, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { BaseService } from 'src/services/base.service'; import { getAssetFiles } from 'src/utils/asset.util'; import { getCLIPModelInfo, isSmartSearchEnabled } from 'src/utils/misc'; @@ -20,17 +13,12 @@ import { usePagination } from 'src/utils/pagination'; @Injectable() export class SmartInfoService extends BaseService { - @OnEvent({ name: 'app.bootstrap' }) - async onBootstrap(app: ArgOf<'app.bootstrap'>) { - if (app !== ImmichWorker.MICROSERVICES) { - return; - } - - const config = await this.getConfig({ withCache: false }); - await this.init(config); + @OnEvent({ name: 'config.init' }) + async onConfigInit({ newConfig }: ArgOf<'config.init'>) { + await this.init(newConfig); } - @OnEvent({ name: 'config.update' }) + @OnEvent({ name: 'config.update', server: true }) async onConfigUpdate({ oldConfig, newConfig }: ArgOf<'config.update'>) { await this.init(newConfig, oldConfig); } @@ -47,7 +35,7 @@ export class SmartInfoService extends BaseService { } private async init(newConfig: SystemConfig, oldConfig?: SystemConfig) { - if (!isSmartSearchEnabled(newConfig.machineLearning)) { + if (this.worker !== ImmichWorker.MICROSERVICES || !isSmartSearchEnabled(newConfig.machineLearning)) { return; } @@ -86,7 +74,8 @@ export class SmartInfoService extends BaseService { }); } - async handleQueueEncodeClip({ force }: IBaseJob): Promise { + @OnJob({ name: JobName.QUEUE_SMART_SEARCH, queue: QueueName.SMART_SEARCH }) + async handleQueueEncodeClip({ force }: JobOf): Promise { const { machineLearning } = await this.getConfig({ withCache: false }); if (!isSmartSearchEnabled(machineLearning)) { return JobStatus.SKIPPED; @@ -111,7 +100,8 @@ export class SmartInfoService extends BaseService { return JobStatus.SUCCESS; } - async handleEncodeClip({ id }: IEntityJob): Promise { + @OnJob({ name: JobName.SMART_SEARCH, queue: QueueName.SMART_SEARCH }) + async handleEncodeClip({ id }: JobOf): Promise { const { machineLearning } = await this.getConfig({ withCache: true }); if (!isSmartSearchEnabled(machineLearning)) { return JobStatus.SKIPPED; diff --git a/server/src/services/storage-template.service.spec.ts b/server/src/services/storage-template.service.spec.ts index fd063bd50d419..728e891d05379 100644 --- a/server/src/services/storage-template.service.spec.ts +++ b/server/src/services/storage-template.service.spec.ts @@ -38,7 +38,7 @@ describe(StorageTemplateService.name, () => { systemMock.get.mockResolvedValue({ storageTemplate: { enabled: true } }); - sut.onConfigUpdate({ newConfig: defaults }); + sut.onConfigInit({ newConfig: defaults }); }); describe('onConfigValidate', () => { @@ -171,7 +171,7 @@ describe(StorageTemplateService.name, () => { const config = structuredClone(defaults); config.storageTemplate.template = '{{y}}/{{#if album}}{{album}}{{else}}other/{{MM}}{{/if}}/{{filename}}'; - sut.onConfigUpdate({ oldConfig: defaults, newConfig: config }); + sut.onConfigInit({ newConfig: config }); userMock.get.mockResolvedValue(user); assetMock.getByIds.mockResolvedValueOnce([asset]); @@ -192,7 +192,7 @@ describe(StorageTemplateService.name, () => { const user = userStub.user1; const config = structuredClone(defaults); config.storageTemplate.template = '{{y}}/{{#if album}}{{album}}{{else}}other//{{MM}}{{/if}}/{{filename}}'; - sut.onConfigUpdate({ oldConfig: defaults, newConfig: config }); + sut.onConfigInit({ newConfig: config }); userMock.get.mockResolvedValue(user); assetMock.getByIds.mockResolvedValueOnce([asset]); diff --git a/server/src/services/storage-template.service.ts b/server/src/services/storage-template.service.ts index d2394356609b8..e8e4bd12a5569 100644 --- a/server/src/services/storage-template.service.ts +++ b/server/src/services/storage-template.service.ts @@ -4,13 +4,13 @@ import { DateTime } from 'luxon'; import path from 'node:path'; import sanitize from 'sanitize-filename'; import { StorageCore } from 'src/cores/storage.core'; -import { OnEvent } from 'src/decorators'; +import { OnEvent, OnJob } from 'src/decorators'; import { SystemConfigTemplateStorageOptionDto } from 'src/dtos/system-config.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { AssetPathType, AssetType, StorageFolder } from 'src/enum'; import { DatabaseLock } from 'src/interfaces/database.interface'; import { ArgOf } from 'src/interfaces/event.interface'; -import { IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobStatus } from 'src/interfaces/job.interface'; +import { JobName, JobOf, JOBS_ASSET_PAGINATION_SIZE, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { BaseService } from 'src/services/base.service'; import { getLivePhotoMotionFilename } from 'src/utils/file'; import { usePagination } from 'src/utils/pagination'; @@ -74,8 +74,8 @@ export class StorageTemplateService extends BaseService { return this._template; } - @OnEvent({ name: 'config.update', server: true }) - onConfigUpdate({ newConfig }: ArgOf<'config.update'>) { + @OnEvent({ name: 'config.init' }) + onConfigInit({ newConfig }: ArgOf<'config.init'>) { const template = newConfig.storageTemplate.template; if (!this._template || template !== this.template.raw) { this.logger.debug(`Compiling new storage template: ${template}`); @@ -83,6 +83,11 @@ export class StorageTemplateService extends BaseService { } } + @OnEvent({ name: 'config.update', server: true }) + onConfigUpdate({ newConfig }: ArgOf<'config.update'>) { + this.onConfigInit({ newConfig }); + } + @OnEvent({ name: 'config.validate' }) onConfigValidate({ newConfig }: ArgOf<'config.validate'>) { try { @@ -108,7 +113,8 @@ export class StorageTemplateService extends BaseService { return { ...storageTokens, presetOptions: storagePresets }; } - async handleMigrationSingle({ id }: IEntityJob): Promise { + @OnJob({ name: JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE, queue: QueueName.STORAGE_TEMPLATE_MIGRATION }) + async handleMigrationSingle({ id }: JobOf): Promise { const config = await this.getConfig({ withCache: true }); const storageTemplateEnabled = config.storageTemplate.enabled; if (!storageTemplateEnabled) { @@ -137,6 +143,7 @@ export class StorageTemplateService extends BaseService { return JobStatus.SUCCESS; } + @OnJob({ name: JobName.STORAGE_TEMPLATE_MIGRATION, queue: QueueName.STORAGE_TEMPLATE_MIGRATION }) async handleMigration(): Promise { this.logger.log('Starting storage template migration'); const { storageTemplate } = await this.getConfig({ withCache: true }); diff --git a/server/src/services/storage.service.spec.ts b/server/src/services/storage.service.spec.ts index a4903a3987b10..dd97a063aeda5 100644 --- a/server/src/services/storage.service.spec.ts +++ b/server/src/services/storage.service.spec.ts @@ -3,7 +3,8 @@ import { IConfigRepository } from 'src/interfaces/config.interface'; import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; -import { ImmichStartupError, StorageService } from 'src/services/storage.service'; +import { StorageService } from 'src/services/storage.service'; +import { ImmichStartupError } from 'src/utils/misc'; import { mockEnvData } from 'test/repositories/config.repository.mock'; import { newTestService } from 'test/utils'; import { Mocked } from 'vitest'; @@ -30,20 +31,64 @@ describe(StorageService.name, () => { await expect(sut.onBootstrap()).resolves.toBeUndefined(); - expect(systemMock.set).toHaveBeenCalledWith(SystemMetadataKey.SYSTEM_FLAGS, { mountFiles: true }); + expect(systemMock.set).toHaveBeenCalledWith(SystemMetadataKey.SYSTEM_FLAGS, { + mountChecks: { + backups: true, + 'encoded-video': true, + library: true, + profile: true, + thumbs: true, + upload: true, + }, + }); expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/encoded-video'); expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/library'); expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/profile'); expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs'); + expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/upload'); + expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/backups'); expect(storageMock.createFile).toHaveBeenCalledWith('upload/encoded-video/.immich', expect.any(Buffer)); expect(storageMock.createFile).toHaveBeenCalledWith('upload/library/.immich', expect.any(Buffer)); expect(storageMock.createFile).toHaveBeenCalledWith('upload/profile/.immich', expect.any(Buffer)); expect(storageMock.createFile).toHaveBeenCalledWith('upload/thumbs/.immich', expect.any(Buffer)); expect(storageMock.createFile).toHaveBeenCalledWith('upload/upload/.immich', expect.any(Buffer)); + expect(storageMock.createFile).toHaveBeenCalledWith('upload/backups/.immich', expect.any(Buffer)); + }); + + it('should enable mount folder checking for a new folder type', async () => { + systemMock.get.mockResolvedValue({ + mountChecks: { + backups: false, + 'encoded-video': true, + library: false, + profile: true, + thumbs: true, + upload: true, + }, + }); + + await expect(sut.onBootstrap()).resolves.toBeUndefined(); + + expect(systemMock.set).toHaveBeenCalledWith(SystemMetadataKey.SYSTEM_FLAGS, { + mountChecks: { + backups: true, + 'encoded-video': true, + library: true, + profile: true, + thumbs: true, + upload: true, + }, + }); + expect(storageMock.mkdirSync).toHaveBeenCalledTimes(2); + expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/library'); + expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/backups'); + expect(storageMock.createFile).toHaveBeenCalledTimes(2); + expect(storageMock.createFile).toHaveBeenCalledWith('upload/library/.immich', expect.any(Buffer)); + expect(storageMock.createFile).toHaveBeenCalledWith('upload/backups/.immich', expect.any(Buffer)); }); it('should throw an error if .immich is missing', async () => { - systemMock.get.mockResolvedValue({ mountFiles: true }); + systemMock.get.mockResolvedValue({ mountChecks: { upload: true } }); storageMock.readFile.mockRejectedValue(new Error("ENOENT: no such file or directory, open '/app/.immich'")); await expect(sut.onBootstrap()).rejects.toThrow('Failed to read'); @@ -53,7 +98,7 @@ describe(StorageService.name, () => { }); it('should throw an error if .immich is present but read-only', async () => { - systemMock.get.mockResolvedValue({ mountFiles: true }); + systemMock.get.mockResolvedValue({ mountChecks: { upload: true } }); storageMock.overwriteFile.mockRejectedValue(new Error("ENOENT: no such file or directory, open '/app/.immich'")); await expect(sut.onBootstrap()).rejects.toThrow('Failed to write'); @@ -64,7 +109,7 @@ describe(StorageService.name, () => { it('should skip mount file creation if file already exists', async () => { const error = new Error('Error creating file') as any; error.code = 'EEXIST'; - systemMock.get.mockResolvedValue({ mountFiles: false }); + systemMock.get.mockResolvedValue({ mountChecks: {} }); storageMock.createFile.mockRejectedValue(error); await expect(sut.onBootstrap()).resolves.toBeUndefined(); @@ -73,7 +118,7 @@ describe(StorageService.name, () => { }); it('should throw an error if mount file could not be created', async () => { - systemMock.get.mockResolvedValue({ mountFiles: false }); + systemMock.get.mockResolvedValue({ mountChecks: {} }); storageMock.createFile.mockRejectedValue(new Error('Error creating file')); await expect(sut.onBootstrap()).rejects.toBeInstanceOf(ImmichStartupError); @@ -81,7 +126,7 @@ describe(StorageService.name, () => { }); it('should startup if checks are disabled', async () => { - systemMock.get.mockResolvedValue({ mountFiles: true }); + systemMock.get.mockResolvedValue({ mountChecks: { upload: true } }); configMock.getEnv.mockReturnValue( mockEnvData({ storage: { ignoreMountCheckErrors: true }, diff --git a/server/src/services/storage.service.ts b/server/src/services/storage.service.ts index e8620b4371dd0..ce26df486977e 100644 --- a/server/src/services/storage.service.ts +++ b/server/src/services/storage.service.ts @@ -1,14 +1,13 @@ import { Injectable } from '@nestjs/common'; import { join } from 'node:path'; import { StorageCore } from 'src/cores/storage.core'; -import { OnEvent } from 'src/decorators'; +import { OnEvent, OnJob } from 'src/decorators'; +import { SystemFlags } from 'src/entities/system-metadata.entity'; import { StorageFolder, SystemMetadataKey } from 'src/enum'; import { DatabaseLock } from 'src/interfaces/database.interface'; -import { IDeleteFilesJob, JobStatus } from 'src/interfaces/job.interface'; +import { JobName, JobOf, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { BaseService } from 'src/services/base.service'; - -export class ImmichStartupError extends Error {} -export const isStartUpError = (error: unknown): error is ImmichStartupError => error instanceof ImmichStartupError; +import { ImmichStartupError } from 'src/utils/misc'; const docsMessage = `Please see https://immich.app/docs/administration/system-integrity#folder-checks for more information.`; @@ -19,25 +18,36 @@ export class StorageService extends BaseService { const envData = this.configRepository.getEnv(); await this.databaseRepository.withLock(DatabaseLock.SystemFileMounts, async () => { - const flags = (await this.systemMetadataRepository.get(SystemMetadataKey.SYSTEM_FLAGS)) || { mountFiles: false }; - const enabled = flags.mountFiles ?? false; + const flags = + (await this.systemMetadataRepository.get(SystemMetadataKey.SYSTEM_FLAGS)) || + ({ mountChecks: {} } as SystemFlags); + + if (!flags.mountChecks) { + flags.mountChecks = {}; + } - this.logger.log(`Verifying system mount folder checks (enabled=${enabled})`); + let updated = false; + + this.logger.log(`Verifying system mount folder checks, current state: ${JSON.stringify(flags)}`); try { // check each folder exists and is writable for (const folder of Object.values(StorageFolder)) { - if (!enabled) { + if (!flags.mountChecks[folder]) { this.logger.log(`Writing initial mount file for the ${folder} folder`); await this.createMountFile(folder); } await this.verifyReadAccess(folder); await this.verifyWriteAccess(folder); + + if (!flags.mountChecks[folder]) { + flags.mountChecks[folder] = true; + updated = true; + } } - if (!flags.mountFiles) { - flags.mountFiles = true; + if (updated) { await this.systemMetadataRepository.set(SystemMetadataKey.SYSTEM_FLAGS, flags); this.logger.log('Successfully enabled system mount folders checks'); } @@ -54,7 +64,8 @@ export class StorageService extends BaseService { }); } - async handleDeleteFiles(job: IDeleteFilesJob) { + @OnJob({ name: JobName.DELETE_FILES, queue: QueueName.BACKGROUND_TASK }) + async handleDeleteFiles(job: JobOf): Promise { const { files } = job; // TODO: one job per file diff --git a/server/src/services/system-config.service.spec.ts b/server/src/services/system-config.service.spec.ts index f81abc4795d40..f9ee42eb035fd 100644 --- a/server/src/services/system-config.service.spec.ts +++ b/server/src/services/system-config.service.spec.ts @@ -44,6 +44,13 @@ const updatedConfig = Object.freeze({ [QueueName.VIDEO_CONVERSION]: { concurrency: 1 }, [QueueName.NOTIFICATION]: { concurrency: 5 }, }, + backup: { + database: { + enabled: true, + cronExpression: '0 02 * * *', + keepLastAmount: 14, + }, + }, ffmpeg: { crf: 30, threads: 0, @@ -58,7 +65,6 @@ const updatedConfig = Object.freeze({ bframes: -1, refs: 0, gopSize: 0, - npl: 0, temporalAQ: false, cqMode: CQMode.AUTO, twoPass: false, @@ -237,6 +243,47 @@ describe(SystemConfigService.name, () => { expect(systemMock.readFile).toHaveBeenCalledWith('immich-config.json'); }); + it('should transform booleans', async () => { + configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); + systemMock.readFile.mockResolvedValue(JSON.stringify({ ffmpeg: { twoPass: 'false' } })); + + await expect(sut.getSystemConfig()).resolves.toMatchObject({ + ffmpeg: expect.objectContaining({ twoPass: false }), + }); + }); + + it('should transform numbers', async () => { + configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); + systemMock.readFile.mockResolvedValue(JSON.stringify({ ffmpeg: { threads: '42' } })); + + await expect(sut.getSystemConfig()).resolves.toMatchObject({ + ffmpeg: expect.objectContaining({ threads: 42 }), + }); + }); + + it('should accept valid cron expressions', async () => { + configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); + systemMock.readFile.mockResolvedValue(JSON.stringify({ library: { scan: { cronExpression: '0 0 * * *' } } })); + + await expect(sut.getSystemConfig()).resolves.toMatchObject({ + library: { + scan: { + enabled: true, + cronExpression: '0 0 * * *', + }, + }, + }); + }); + + it('should reject invalid cron expressions', async () => { + configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); + systemMock.readFile.mockResolvedValue(JSON.stringify({ library: { scan: { cronExpression: 'foo' } } })); + + await expect(sut.getSystemConfig()).rejects.toThrow( + 'library.scan.cronExpression has failed the following constraints: cronValidator', + ); + }); + it('should log errors with the config file', async () => { configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); diff --git a/server/src/services/system-config.service.ts b/server/src/services/system-config.service.ts index 8f19b221733a5..b5ae42e0989d1 100644 --- a/server/src/services/system-config.service.ts +++ b/server/src/services/system-config.service.ts @@ -4,17 +4,17 @@ import _ from 'lodash'; import { defaults } from 'src/config'; import { OnEvent } from 'src/decorators'; import { SystemConfigDto, mapConfig } from 'src/dtos/system-config.dto'; -import { ArgOf } from 'src/interfaces/event.interface'; +import { ArgOf, BootstrapEventPriority } from 'src/interfaces/event.interface'; import { BaseService } from 'src/services/base.service'; import { clearConfigCache } from 'src/utils/config'; import { toPlainObject } from 'src/utils/object'; @Injectable() export class SystemConfigService extends BaseService { - @OnEvent({ name: 'app.bootstrap', priority: -100 }) + @OnEvent({ name: 'app.bootstrap', priority: BootstrapEventPriority.SystemConfig }) async onBootstrap() { const config = await this.getConfig({ withCache: false }); - await this.eventRepository.emit('config.update', { newConfig: config }); + await this.eventRepository.emit('config.init', { newConfig: config }); } async getSystemConfig(): Promise { @@ -26,14 +26,18 @@ export class SystemConfigService extends BaseService { return mapConfig(defaults); } - @OnEvent({ name: 'config.update', server: true }) - onConfigUpdate({ newConfig: { logging } }: ArgOf<'config.update'>) { + @OnEvent({ name: 'config.init' }) + onConfigInit({ newConfig: { logging } }: ArgOf<'config.init'>) { const { logLevel: envLevel } = this.configRepository.getEnv(); const configLevel = logging.enabled ? logging.level : false; const level = envLevel ?? configLevel; this.logger.setLogLevel(level); this.logger.log(`LogLevel=${level} ${envLevel ? '(set via IMMICH_LOG_LEVEL)' : '(set via system config)'}`); - // TODO only do this if the event is a socket.io event + } + + @OnEvent({ name: 'config.update', server: true }) + onConfigUpdate({ newConfig }: ArgOf<'config.update'>) { + this.onConfigInit({ newConfig }); clearConfigCache(); } diff --git a/server/src/services/tag.service.ts b/server/src/services/tag.service.ts index 5534d74efa63e..2aca400cc71e2 100644 --- a/server/src/services/tag.service.ts +++ b/server/src/services/tag.service.ts @@ -1,4 +1,5 @@ import { BadRequestException, Injectable } from '@nestjs/common'; +import { OnJob } from 'src/decorators'; import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { @@ -12,7 +13,7 @@ import { } from 'src/dtos/tag.dto'; import { TagEntity } from 'src/entities/tag.entity'; import { Permission } from 'src/enum'; -import { JobStatus } from 'src/interfaces/job.interface'; +import { JobName, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { AssetTagItem } from 'src/interfaces/tag.interface'; import { BaseService } from 'src/services/base.service'; import { addAssets, removeAssets } from 'src/utils/asset.util'; @@ -131,6 +132,7 @@ export class TagService extends BaseService { return results; } + @OnJob({ name: JobName.TAG_CLEANUP, queue: QueueName.BACKGROUND_TASK }) async handleTagCleanup() { await this.tagRepository.deleteEmptyTags(); return JobStatus.SUCCESS; diff --git a/server/src/services/trash.service.ts b/server/src/services/trash.service.ts index 91c359392eecd..621dee0f8176d 100644 --- a/server/src/services/trash.service.ts +++ b/server/src/services/trash.service.ts @@ -1,9 +1,9 @@ -import { OnEvent } from 'src/decorators'; +import { OnEvent, OnJob } from 'src/decorators'; import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { TrashResponseDto } from 'src/dtos/trash.dto'; import { Permission } from 'src/enum'; -import { JOBS_ASSET_PAGINATION_SIZE, JobName, JobStatus } from 'src/interfaces/job.interface'; +import { JOBS_ASSET_PAGINATION_SIZE, JobName, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { BaseService } from 'src/services/base.service'; import { usePagination } from 'src/utils/pagination'; @@ -44,6 +44,7 @@ export class TrashService extends BaseService { await this.jobRepository.queue({ name: JobName.QUEUE_TRASH_EMPTY, data: {} }); } + @OnJob({ name: JobName.QUEUE_TRASH_EMPTY, queue: QueueName.BACKGROUND_TASK }) async handleQueueEmptyTrash() { let count = 0; const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => diff --git a/server/src/services/user-admin.service.ts b/server/src/services/user-admin.service.ts index 84a5b5842d9b9..a4be671c2237c 100644 --- a/server/src/services/user-admin.service.ts +++ b/server/src/services/user-admin.service.ts @@ -15,7 +15,6 @@ import { JobName } from 'src/interfaces/job.interface'; import { UserFindOptions } from 'src/interfaces/user.interface'; import { BaseService } from 'src/services/base.service'; import { getPreferences, getPreferencesPartial, mergePreferences } from 'src/utils/preferences'; -import { createUser } from 'src/utils/user'; @Injectable() export class UserAdminService extends BaseService { @@ -25,17 +24,18 @@ export class UserAdminService extends BaseService { } async create(dto: UserAdminCreateDto): Promise { - const { notify, ...rest } = dto; + const { notify, ...userDto } = dto; const config = await this.getConfig({ withCache: false }); - if (!config.oauth.enabled && !rest.password) { + if (!config.oauth.enabled && !userDto.password) { throw new BadRequestException('password is required'); } - const user = await createUser({ userRepo: this.userRepository, cryptoRepo: this.cryptoRepository }, rest); + + const user = await this.createUser(userDto); await this.eventRepository.emit('user.signup', { notify: !!notify, id: user.id, - tempPassword: user.shouldChangePassword ? rest.password : undefined, + tempPassword: user.shouldChangePassword ? userDto.password : undefined, }); return mapUserAdmin(user); diff --git a/server/src/services/user.service.ts b/server/src/services/user.service.ts index f67d04cbd3405..926482fb9c89a 100644 --- a/server/src/services/user.service.ts +++ b/server/src/services/user.service.ts @@ -2,6 +2,7 @@ import { BadRequestException, Injectable, NotFoundException } from '@nestjs/comm import { DateTime } from 'luxon'; import { SALT_ROUNDS } from 'src/constants'; import { StorageCore } from 'src/cores/storage.core'; +import { OnJob } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto'; import { UserPreferencesResponseDto, UserPreferencesUpdateDto, mapPreferences } from 'src/dtos/user-preferences.dto'; @@ -10,7 +11,7 @@ import { UserAdminResponseDto, UserResponseDto, UserUpdateMeDto, mapUser, mapUse import { UserMetadataEntity } from 'src/entities/user-metadata.entity'; import { UserEntity } from 'src/entities/user.entity'; import { CacheControl, StorageFolder, UserMetadataKey } from 'src/enum'; -import { IEntityJob, JobName, JobStatus } from 'src/interfaces/job.interface'; +import { JobName, JobOf, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { UserFindOptions } from 'src/interfaces/user.interface'; import { BaseService } from 'src/services/base.service'; import { ImmichFileResponse } from 'src/utils/file'; @@ -163,11 +164,13 @@ export class UserService extends BaseService { return licenseData; } + @OnJob({ name: JobName.USER_SYNC_USAGE, queue: QueueName.BACKGROUND_TASK }) async handleUserSyncUsage(): Promise { await this.userRepository.syncUsage(); return JobStatus.SUCCESS; } + @OnJob({ name: JobName.USER_DELETE_CHECK, queue: QueueName.BACKGROUND_TASK }) async handleUserDeleteCheck(): Promise { const users = await this.userRepository.getDeletedUsers(); const config = await this.getConfig({ withCache: false }); @@ -181,7 +184,8 @@ export class UserService extends BaseService { return JobStatus.SUCCESS; } - async handleUserDelete({ id, force }: IEntityJob): Promise { + @OnJob({ name: JobName.USER_DELETION, queue: QueueName.BACKGROUND_TASK }) + async handleUserDelete({ id, force }: JobOf): Promise { const config = await this.getConfig({ withCache: false }); const user = await this.userRepository.get(id, { withDeleted: true }); if (!user) { diff --git a/server/src/services/version.service.ts b/server/src/services/version.service.ts index 231ced1a950af..ff4fa3c6bf636 100644 --- a/server/src/services/version.service.ts +++ b/server/src/services/version.service.ts @@ -2,13 +2,13 @@ import { Injectable } from '@nestjs/common'; import { DateTime } from 'luxon'; import semver, { SemVer } from 'semver'; import { serverVersion } from 'src/constants'; -import { OnEvent } from 'src/decorators'; +import { OnEvent, OnJob } from 'src/decorators'; import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto'; import { VersionCheckMetadata } from 'src/entities/system-metadata.entity'; import { ImmichEnvironment, SystemMetadataKey } from 'src/enum'; import { DatabaseLock } from 'src/interfaces/database.interface'; import { ArgOf } from 'src/interfaces/event.interface'; -import { JobName, JobStatus } from 'src/interfaces/job.interface'; +import { JobName, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { BaseService } from 'src/services/base.service'; const asNotification = ({ checkedAt, releaseVersion }: VersionCheckMetadata): ReleaseNotification => { @@ -48,6 +48,7 @@ export class VersionService extends BaseService { await this.jobRepository.queue({ name: JobName.VERSION_CHECK, data: {} }); } + @OnJob({ name: JobName.VERSION_CHECK, queue: QueueName.BACKGROUND_TASK }) async handleVersionCheck(): Promise { try { this.logger.debug('Running version check'); diff --git a/server/src/utils/config.ts b/server/src/utils/config.ts index 3a6e079e873f8..ce8a2da83985d 100644 --- a/server/src/utils/config.ts +++ b/server/src/utils/config.ts @@ -1,5 +1,5 @@ import AsyncLock from 'async-lock'; -import { plainToInstance } from 'class-transformer'; +import { instanceToPlain, plainToInstance } from 'class-transformer'; import { validate } from 'class-validator'; import { load as loadYaml } from 'js-yaml'; import * as _ from 'lodash'; @@ -87,13 +87,13 @@ const buildConfig = async (repos: RepoDeps) => { : await metadataRepo.get(SystemMetadataKey.SYSTEM_CONFIG); // merge with defaults - const config = _.cloneDeep(defaults); + const rawConfig = _.cloneDeep(defaults); for (const property of getKeysDeep(partial)) { - _.set(config, property, _.get(partial, property)); + _.set(rawConfig, property, _.get(partial, property)); } // check for extra properties - const unknownKeys = _.cloneDeep(config); + const unknownKeys = _.cloneDeep(rawConfig); for (const property of getKeysDeep(defaults)) { unsetDeep(unknownKeys, property); } @@ -103,7 +103,8 @@ const buildConfig = async (repos: RepoDeps) => { } // validate full config - const errors = await validate(plainToInstance(SystemConfigDto, config)); + const instance = plainToInstance(SystemConfigDto, rawConfig); + const errors = await validate(instance); if (errors.length > 0) { if (configFile) { throw new Error(`Invalid value(s) in file: ${errors}`); @@ -112,6 +113,9 @@ const buildConfig = async (repos: RepoDeps) => { } } + // return config with class-transform changes + const config = instanceToPlain(instance) as SystemConfig; + if (config.server.externalDomain.length > 0) { config.server.externalDomain = new URL(config.server.externalDomain).origin; } diff --git a/server/src/utils/database.ts b/server/src/utils/database.ts index 55e4fcb0e555b..ad2198b38c571 100644 --- a/server/src/utils/database.ts +++ b/server/src/utils/database.ts @@ -90,7 +90,6 @@ export function searchAssetBuilder( isNotInAlbum, withFaces, withPeople, - withSmartInfo, personIds, withStacked, trashedAfter, @@ -123,10 +122,6 @@ export function searchAssetBuilder( builder.leftJoinAndSelect('faces.person', 'person'); } - if (withSmartInfo) { - builder.leftJoinAndSelect(`${builder.alias}.smartInfo`, 'smartInfo'); - } - if (personIds && personIds.length > 0) { const cte = builder .createQueryBuilder() diff --git a/server/src/utils/date-time.ts b/server/src/utils/date-time.ts new file mode 100644 index 0000000000000..e1578cbb19ad2 --- /dev/null +++ b/server/src/utils/date-time.ts @@ -0,0 +1,5 @@ +import { AssetEntity } from 'src/entities/asset.entity'; + +export const getAssetDateTime = (asset: AssetEntity | undefined) => { + return asset?.exifInfo?.dateTimeOriginal || asset?.fileCreatedAt; +}; diff --git a/server/src/utils/logger.ts b/server/src/utils/logger.ts index 2e33a7bcb557a..cf66404d69657 100644 --- a/server/src/utils/logger.ts +++ b/server/src/utils/logger.ts @@ -16,7 +16,7 @@ export const logGlobalError = (logger: ILoggerRepository, error: Error) => { } if (error instanceof Error) { - logger.error(`Unknown error: ${error}`); + logger.error(`Unknown error: ${error}`, error?.stack); return; } }; diff --git a/server/src/utils/media.ts b/server/src/utils/media.ts index 03d57296d83b8..98d3c7fdbb208 100644 --- a/server/src/utils/media.ts +++ b/server/src/utils/media.ts @@ -6,6 +6,7 @@ import { TranscodeCommand, VideoCodecHWConfig, VideoCodecSWConfig, + VideoFormat, VideoStreamInfo, } from 'src/interfaces/media.interface'; @@ -77,9 +78,14 @@ export class BaseConfig implements VideoCodecSWConfig { return handler; } - getCommand(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) { + getCommand( + target: TranscodeTarget, + videoStream: VideoStreamInfo, + audioStream?: AudioStreamInfo, + format?: VideoFormat, + ) { const options = { - inputOptions: this.getBaseInputOptions(videoStream), + inputOptions: this.getBaseInputOptions(videoStream, format), outputOptions: [...this.getBaseOutputOptions(target, videoStream, audioStream), '-v verbose'], twoPass: this.eligibleForTwoPass(), progress: { frameCount: videoStream.frameCount, percentInterval: 5 }, @@ -101,7 +107,7 @@ export class BaseConfig implements VideoCodecSWConfig { } // eslint-disable-next-line @typescript-eslint/no-unused-vars - getBaseInputOptions(videoStream: VideoStreamInfo): string[] { + getBaseInputOptions(videoStream: VideoStreamInfo, format?: VideoFormat): string[] { return this.getInputThreadOptions(); } @@ -149,7 +155,11 @@ export class BaseConfig implements VideoCodecSWConfig { options.push(`scale=${this.getScaling(videoStream)}`); } - options.push(...this.getToneMapping(videoStream), 'format=yuv420p'); + options.push(...this.getToneMapping(videoStream)); + if (options.length === 0 && !videoStream.pixelFormat.endsWith('420p')) { + options.push(`format=yuv420p`); + } + return options; } @@ -271,33 +281,20 @@ export class BaseConfig implements VideoCodecSWConfig { getColors() { return { - primaries: '709', - transfer: '709', - matrix: '709', + primaries: 'bt709', + transfer: 'bt709', + matrix: 'bt709', }; } - getNPL() { - if (this.config.npl <= 0) { - // since hable already outputs a darker image, we use a lower npl value for it - return this.config.tonemap === ToneMapping.HABLE ? 100 : 250; - } else { - return this.config.npl; - } - } - getToneMapping(videoStream: VideoStreamInfo) { if (!this.shouldToneMap(videoStream)) { return []; } - const colors = this.getColors(); - - return [ - `zscale=t=linear:npl=${this.getNPL()}`, - `tonemap=${this.config.tonemap}:desat=0`, - `zscale=p=${colors.primaries}:t=${colors.transfer}:m=${colors.matrix}:range=pc`, - ]; + const { primaries, transfer, matrix } = this.getColors(); + const options = `tonemapx=tonemap=${this.config.tonemap}:desat=0:p=${primaries}:t=${transfer}:m=${matrix}:r=pc:peak=100:format=yuv420p`; + return [options]; } getAudioCodec(): string { @@ -386,8 +383,11 @@ export class ThumbnailConfig extends BaseConfig { return new ThumbnailConfig(config); } - getBaseInputOptions(): string[] { - return ['-skip_frame nointra', '-sws_flags accurate_rnd+full_chroma_int']; + getBaseInputOptions(videoStream: VideoStreamInfo, format?: VideoFormat): string[] { + // skip_frame nointra skips all frames for some MPEG-TS files. Look at ffmpeg tickets 7950 and 7895 for more details. + return format?.formatName === 'mpegts' + ? ['-sws_flags accurate_rnd+full_chroma_int'] + : ['-skip_frame nointra', '-sws_flags accurate_rnd+full_chroma_int']; } getBaseOutputOptions() { @@ -395,19 +395,14 @@ export class ThumbnailConfig extends BaseConfig { } getFilterOptions(videoStream: VideoStreamInfo): string[] { - const options = [ + return [ 'fps=12:eof_action=pass:round=down', 'thumbnail=12', String.raw`select=gt(scene\,0.1)-eq(prev_selected_n\,n)+isnan(prev_selected_n)+gt(n\,20)`, 'trim=end_frame=2', 'reverse', + ...super.getFilterOptions(videoStream), ]; - if (this.shouldScale(videoStream)) { - options.push(`scale=${this.getScaling(videoStream)}`); - } - - options.push(...this.getToneMapping(videoStream), 'format=yuv420p'); - return options; } getPresetOptions() { @@ -423,19 +418,7 @@ export class ThumbnailConfig extends BaseConfig { } getScaling(videoStream: VideoStreamInfo) { - let options = super.getScaling(videoStream) + ':flags=lanczos+accurate_rnd+full_chroma_int'; - if (!this.shouldToneMap(videoStream)) { - options += ':out_color_matrix=bt601:out_range=pc'; - } - return options; - } - - getColors() { - return { - primaries: '709', - transfer: '601', - matrix: '470bg', - }; + return super.getScaling(videoStream) + ':flags=lanczos+accurate_rnd+full_chroma_int:out_range=pc'; } } @@ -559,9 +542,9 @@ export class NvencSwDecodeConfig extends BaseHWConfig { getFilterOptions(videoStream: VideoStreamInfo) { const options = this.getToneMapping(videoStream); - options.push('format=nv12', 'hwupload_cuda'); + options.push('hwupload_cuda'); if (this.shouldScale(videoStream)) { - options.push(`scale_cuda=${this.getScaling(videoStream)}`); + options.push(`scale_cuda=${this.getScaling(videoStream)}:format=nv12`); } return options; @@ -622,6 +605,8 @@ export class NvencHwDecodeConfig extends NvencSwDecodeConfig { options.push(...this.getToneMapping(videoStream)); if (options.length > 0) { options[options.length - 1] += ':format=nv12'; + } else if (!videoStream.pixelFormat.endsWith('420p')) { + options.push('format=nv12'); } return options; } @@ -631,14 +616,16 @@ export class NvencHwDecodeConfig extends NvencSwDecodeConfig { return []; } - const colors = this.getColors(); + const { matrix, primaries, transfer } = this.getColors(); const tonemapOptions = [ 'desat=0', - `matrix=${colors.matrix}`, - `primaries=${colors.primaries}`, + `matrix=${matrix}`, + `primaries=${primaries}`, 'range=pc', `tonemap=${this.config.tonemap}`, - `transfer=${colors.transfer}`, + 'tonemap_mode=lum', + `transfer=${transfer}`, + 'peak=100', ]; return [`tonemap_cuda=${tonemapOptions.join(':')}`]; @@ -651,14 +638,6 @@ export class NvencHwDecodeConfig extends NvencSwDecodeConfig { getOutputThreadOptions() { return []; } - - getColors() { - return { - primaries: 'bt709', - transfer: 'bt709', - matrix: 'bt709', - }; - } } export class QsvSwDecodeConfig extends BaseHWConfig { @@ -687,9 +666,9 @@ export class QsvSwDecodeConfig extends BaseHWConfig { getFilterOptions(videoStream: VideoStreamInfo) { const options = this.getToneMapping(videoStream); - options.push('format=nv12', 'hwupload=extra_hw_frames=64'); + options.push('hwupload=extra_hw_frames=64'); if (this.shouldScale(videoStream)) { - options.push(`scale_qsv=${this.getScaling(videoStream)}:mode=hq`); + options.push(`scale_qsv=${this.getScaling(videoStream)}:mode=hq:format=nv12`); } return options; } @@ -764,15 +743,18 @@ export class QsvHwDecodeConfig extends QsvSwDecodeConfig { getFilterOptions(videoStream: VideoStreamInfo) { const options = []; - if (this.shouldScale(videoStream) || !this.shouldToneMap(videoStream)) { + const tonemapOptions = this.getToneMapping(videoStream); + if (this.shouldScale(videoStream) || tonemapOptions.length === 0) { let scaling = `scale_qsv=${this.getScaling(videoStream)}:async_depth=4:mode=hq`; - if (!this.shouldToneMap(videoStream)) { + if (tonemapOptions.length === 0) { scaling += ':format=nv12'; } options.push(scaling); } - - options.push(...this.getToneMapping(videoStream)); + options.push(...tonemapOptions); + if (options.length === 0 && !videoStream.pixelFormat.endsWith('420p')) { + options.push('format=nv12'); + } return options; } @@ -781,15 +763,17 @@ export class QsvHwDecodeConfig extends QsvSwDecodeConfig { return []; } - const colors = this.getColors(); + const { matrix, primaries, transfer } = this.getColors(); const tonemapOptions = [ 'desat=0', 'format=nv12', - `matrix=${colors.matrix}`, - `primaries=${colors.primaries}`, + `matrix=${matrix}`, + `primaries=${primaries}`, + `transfer=${transfer}`, 'range=pc', `tonemap=${this.config.tonemap}`, - `transfer=${colors.transfer}`, + 'tonemap_mode=lum', + 'peak=100', ]; return [ @@ -802,14 +786,6 @@ export class QsvHwDecodeConfig extends QsvSwDecodeConfig { getInputThreadOptions() { return [`-threads 1`]; } - - getColors() { - return { - primaries: 'bt709', - transfer: 'bt709', - matrix: 'bt709', - }; - } } export class VaapiSwDecodeConfig extends BaseHWConfig { @@ -828,9 +804,9 @@ export class VaapiSwDecodeConfig extends BaseHWConfig { getFilterOptions(videoStream: VideoStreamInfo) { const options = this.getToneMapping(videoStream); - options.push('format=nv12', 'hwupload'); + options.push('hwupload=extra_hw_frames=64'); if (this.shouldScale(videoStream)) { - options.push(`scale_vaapi=${this.getScaling(videoStream)}:mode=hq:out_range=pc`); + options.push(`scale_vaapi=${this.getScaling(videoStream)}:mode=hq:out_range=pc:format=nv12`); } return options; @@ -901,15 +877,18 @@ export class VaapiHwDecodeConfig extends VaapiSwDecodeConfig { getFilterOptions(videoStream: VideoStreamInfo) { const options = []; - if (this.shouldScale(videoStream) || !this.shouldToneMap(videoStream)) { + const tonemapOptions = this.getToneMapping(videoStream); + if (this.shouldScale(videoStream) || tonemapOptions.length === 0) { let scaling = `scale_vaapi=${this.getScaling(videoStream)}:mode=hq:out_range=pc`; - if (!this.shouldToneMap(videoStream)) { + if (tonemapOptions.length === 0) { scaling += ':format=nv12'; } options.push(scaling); } - - options.push(...this.getToneMapping(videoStream)); + options.push(...tonemapOptions); + if (options.length === 0 && !videoStream.pixelFormat.endsWith('420p')) { + options.push('format=nv12'); + } return options; } @@ -918,15 +897,17 @@ export class VaapiHwDecodeConfig extends VaapiSwDecodeConfig { return []; } - const colors = this.getColors(); + const { matrix, primaries, transfer } = this.getColors(); const tonemapOptions = [ 'desat=0', 'format=nv12', - `matrix=${colors.matrix}`, - `primaries=${colors.primaries}`, + `matrix=${matrix}`, + `primaries=${primaries}`, + `transfer=${transfer}`, 'range=pc', `tonemap=${this.config.tonemap}`, - `transfer=${colors.transfer}`, + 'tonemap_mode=lum', + 'peak=100', ]; return [ @@ -939,14 +920,6 @@ export class VaapiHwDecodeConfig extends VaapiSwDecodeConfig { getInputThreadOptions() { return [`-threads 1`]; } - - getColors() { - return { - primaries: 'bt709', - transfer: 'bt709', - matrix: 'bt709', - }; - } } export class RkmppSwDecodeConfig extends BaseHWConfig { @@ -1014,11 +987,11 @@ export class RkmppHwDecodeConfig extends RkmppSwDecodeConfig { getFilterOptions(videoStream: VideoStreamInfo) { if (this.shouldToneMap(videoStream)) { - const colors = this.getColors(); + const { primaries, transfer, matrix } = this.getColors(); return [ `scale_rkrga=${this.getScaling(videoStream)}:format=p010:afbc=1`, 'hwmap=derive_device=opencl:mode=read', - `tonemap_opencl=format=nv12:r=pc:p=${colors.primaries}:t=${colors.transfer}:m=${colors.matrix}:tonemap=${this.config.tonemap}:desat=0`, + `tonemap_opencl=format=nv12:r=pc:p=${primaries}:t=${transfer}:m=${matrix}:tonemap=${this.config.tonemap}:desat=0:tonemap_mode=lum:peak=100`, 'hwmap=derive_device=rkmpp:mode=write:reverse=1', 'format=drm_prime', ]; @@ -1027,12 +1000,4 @@ export class RkmppHwDecodeConfig extends RkmppSwDecodeConfig { } return []; } - - getColors() { - return { - primaries: 'bt709', - transfer: 'bt709', - matrix: 'bt709', - }; - } } diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts index 6e435e68a8dc7..6a64923a3bf7b 100644 --- a/server/src/utils/misc.ts +++ b/server/src/utils/misc.ts @@ -15,6 +15,32 @@ import { CLIP_MODEL_INFO, serverVersion } from 'src/constants'; import { ImmichCookie, ImmichHeader, MetadataKey } from 'src/enum'; import { ILoggerRepository } from 'src/interfaces/logger.interface'; +export class ImmichStartupError extends Error {} +export const isStartUpError = (error: unknown): error is ImmichStartupError => error instanceof ImmichStartupError; + +export const getKeyByValue = (object: Record, value: unknown) => + Object.keys(object).find((key) => object[key] === value); + +export const getMethodNames = (instance: any) => { + const ctx = Object.getPrototypeOf(instance); + const methods: string[] = []; + for (const property of Object.getOwnPropertyNames(ctx)) { + const descriptor = Object.getOwnPropertyDescriptor(ctx, property); + if (!descriptor || descriptor.get || descriptor.set) { + continue; + } + + const handler = instance[property]; + if (typeof handler !== 'function') { + continue; + } + + methods.push(property); + } + + return methods; +}; + export const getExternalDomain = (server: SystemConfig['server'], port: number) => server.externalDomain || `http://localhost:${port}`; diff --git a/server/src/utils/user.ts b/server/src/utils/user.ts deleted file mode 100644 index c7029a1ecae4b..0000000000000 --- a/server/src/utils/user.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import sanitize from 'sanitize-filename'; -import { SALT_ROUNDS } from 'src/constants'; -import { UserEntity } from 'src/entities/user.entity'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; -import { IUserRepository } from 'src/interfaces/user.interface'; - -type RepoDeps = { userRepo: IUserRepository; cryptoRepo: ICryptoRepository }; - -export const createUser = async ( - { userRepo, cryptoRepo }: RepoDeps, - dto: Partial & { email: string }, -): Promise => { - const user = await userRepo.getByEmail(dto.email); - if (user) { - throw new BadRequestException('User exists'); - } - - if (!dto.isAdmin) { - const localAdmin = await userRepo.getAdmin(); - if (!localAdmin) { - throw new BadRequestException('The first registered account must the administrator.'); - } - } - - const payload: Partial = { ...dto }; - if (payload.password) { - payload.password = await cryptoRepo.hashBcrypt(payload.password, SALT_ROUNDS); - } - if (payload.storageLabel) { - payload.storageLabel = sanitize(payload.storageLabel.replaceAll('.', '')); - } - - return userRepo.create(payload); -}; diff --git a/server/src/validation.ts b/server/src/validation.ts index 81b309d66358f..177e439919b92 100644 --- a/server/src/validation.ts +++ b/server/src/validation.ts @@ -16,15 +16,19 @@ import { IsOptional, IsString, IsUUID, + Validate, ValidateBy, ValidateIf, ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, buildMessage, isDateString, } from 'class-validator'; import { CronJob } from 'cron'; import { DateTime } from 'luxon'; import sanitize from 'sanitize-filename'; +import { isIP, isIPRange } from 'validator'; @Injectable() export class ParseMeUUIDPipe extends ParseUUIDPipe { @@ -155,16 +159,20 @@ export const ValidateBoolean = (options?: BooleanOptions) => { return applyDecorators(...decorators); }; -export function validateCronExpression(expression: string) { - try { - new CronJob(expression, () => {}); - } catch { - return false; +@ValidatorConstraint({ name: 'cronValidator' }) +class CronValidator implements ValidatorConstraintInterface { + validate(expression: string): boolean { + try { + new CronJob(expression, () => {}); + return true; + } catch { + return false; + } } - - return true; } +export const IsCronExpression = () => Validate(CronValidator, { message: 'Invalid cron expression' }); + type IValue = { value: unknown }; export const toEmail = ({ value }: IValue) => (typeof value === 'string' ? value.toLowerCase() : value); @@ -228,3 +236,32 @@ export function MaxDateString( validationOptions, ); } + +type IsIPRangeOptions = { requireCIDR?: boolean }; +export function IsIPRange(options: IsIPRangeOptions, validationOptions?: ValidationOptions): PropertyDecorator { + const { requireCIDR } = { requireCIDR: true, ...options }; + + return ValidateBy( + { + name: 'isIPRange', + validator: { + validate: (value): boolean => { + if (isIPRange(value)) { + return true; + } + + if (!requireCIDR && isIP(value)) { + return true; + } + + return false; + }, + defaultMessage: buildMessage( + (eachPrefix) => eachPrefix + '$property must be an ip address, or ip address range', + validationOptions, + ), + }, + }, + validationOptions, + ); +} diff --git a/server/src/workers/api.ts b/server/src/workers/api.ts index 6451f1b79293e..5196e7595ccea 100644 --- a/server/src/workers/api.ts +++ b/server/src/workers/api.ts @@ -13,14 +13,13 @@ import { WebSocketAdapter } from 'src/middleware/websocket.adapter'; import { ConfigRepository } from 'src/repositories/config.repository'; import { bootstrapTelemetry } from 'src/repositories/telemetry.repository'; import { ApiService } from 'src/services/api.service'; -import { isStartUpError } from 'src/services/storage.service'; -import { useSwagger } from 'src/utils/misc'; +import { isStartUpError, useSwagger } from 'src/utils/misc'; async function bootstrap() { process.title = 'immich-api'; const { telemetry, network } = new ConfigRepository().getEnv(); - if (telemetry.enabled) { + if (telemetry.metrics.size > 0) { bootstrapTelemetry(telemetry.apiPort); } diff --git a/server/src/workers/microservices.ts b/server/src/workers/microservices.ts index df4abb01da8ab..0fa056d5d43ea 100644 --- a/server/src/workers/microservices.ts +++ b/server/src/workers/microservices.ts @@ -7,11 +7,11 @@ import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { WebSocketAdapter } from 'src/middleware/websocket.adapter'; import { ConfigRepository } from 'src/repositories/config.repository'; import { bootstrapTelemetry } from 'src/repositories/telemetry.repository'; -import { isStartUpError } from 'src/services/storage.service'; +import { isStartUpError } from 'src/utils/misc'; export async function bootstrap() { const { telemetry } = new ConfigRepository().getEnv(); - if (telemetry.enabled) { + if (telemetry.metrics.size > 0) { bootstrapTelemetry(telemetry.microservicesPort); } diff --git a/server/test/fixtures/media.stub.ts b/server/test/fixtures/media.stub.ts index cdcdfd4d5e5d9..de11c23f0a961 100644 --- a/server/test/fixtures/media.stub.ts +++ b/server/test/fixtures/media.stub.ts @@ -17,6 +17,7 @@ const probeStubDefaultVideoStream: VideoStreamInfo[] = [ rotation: 0, isHDR: false, bitrate: 0, + pixelFormat: 'yuv420p', }, ]; @@ -43,6 +44,7 @@ export const probeStub = { rotation: 0, isHDR: false, bitrate: 0, + pixelFormat: 'yuv420p', }, { index: 1, @@ -53,6 +55,7 @@ export const probeStub = { rotation: 0, isHDR: false, bitrate: 0, + pixelFormat: 'yuv420p', }, ], }), @@ -68,6 +71,7 @@ export const probeStub = { rotation: 0, isHDR: false, bitrate: 0, + pixelFormat: 'yuv420p', }, ], }), @@ -83,6 +87,7 @@ export const probeStub = { rotation: 0, isHDR: false, bitrate: 0, + pixelFormat: 'yuv420p', }, ], }), @@ -90,6 +95,13 @@ export const probeStub = { ...probeStubDefault, videoStreams: [{ ...probeStubDefaultVideoStream[0], bitrate: 40_000_000 }], }), + videoStreamMTS: Object.freeze({ + ...probeStubDefault, + format: { + ...probeStubDefaultFormat, + formatName: 'mpegts', + }, + }), videoStreamHDR: Object.freeze({ ...probeStubDefault, videoStreams: [ @@ -102,6 +114,23 @@ export const probeStub = { rotation: 0, isHDR: true, bitrate: 0, + pixelFormat: 'yuv420p10le', + }, + ], + }), + videoStream10Bit: Object.freeze({ + ...probeStubDefault, + videoStreams: [ + { + index: 0, + height: 480, + width: 480, + codecName: 'h264', + frameCount: 100, + rotation: 0, + isHDR: false, + bitrate: 0, + pixelFormat: 'yuv420p10le', }, ], }), @@ -117,6 +146,7 @@ export const probeStub = { rotation: 90, isHDR: false, bitrate: 0, + pixelFormat: 'yuv420p', }, ], }), @@ -132,6 +162,7 @@ export const probeStub = { rotation: 0, isHDR: false, bitrate: 0, + pixelFormat: 'yuv420p', }, ], }), @@ -147,6 +178,7 @@ export const probeStub = { rotation: 0, isHDR: false, bitrate: 0, + pixelFormat: 'yuv420p', }, ], }), diff --git a/server/test/fixtures/shared-link.stub.ts b/server/test/fixtures/shared-link.stub.ts index e446a6180b65a..a8b8e02d742b6 100644 --- a/server/test/fixtures/shared-link.stub.ts +++ b/server/test/fixtures/shared-link.stub.ts @@ -62,10 +62,6 @@ const assetResponse: AssetResponseDto = { updatedAt: today, isFavorite: false, isArchived: false, - smartInfo: { - tags: [], - objects: ['a', 'b', 'c'], - }, duration: '0:00:00.00000', exifInfo: assetInfo, livePhotoVideoId: null, @@ -205,12 +201,6 @@ export const sharedLinkStub = { isArchived: false, isExternal: false, isOffline: false, - smartInfo: { - assetId: 'id_1', - tags: [], - objects: ['a', 'b', 'c'], - asset: null as any, - }, files: [], thumbhash: null, encodedVideoPath: '', diff --git a/server/test/fixtures/system-config.stub.ts b/server/test/fixtures/system-config.stub.ts index be21fc40607af..10a0de77b04b7 100644 --- a/server/test/fixtures/system-config.stub.ts +++ b/server/test/fixtures/system-config.stub.ts @@ -54,6 +54,9 @@ export const systemConfigStub = { }, libraryWatchEnabled: { library: { + scan: { + enabled: false, + }, watch: { enabled: true, }, @@ -61,6 +64,9 @@ export const systemConfigStub = { }, libraryWatchDisabled: { library: { + scan: { + enabled: false, + }, watch: { enabled: false, }, @@ -72,6 +78,29 @@ export const systemConfigStub = { enabled: true, cronExpression: '0 0 * * *', }, + watch: { + enabled: false, + }, + }, + }, + libraryScanAndWatch: { + library: { + scan: { + enabled: true, + cronExpression: '0 0 * * *', + }, + watch: { + enabled: true, + }, + }, + }, + backupEnabled: { + backup: { + database: { + enabled: true, + cronExpression: '0 0 * * *', + keepLastAmount: 1, + }, }, }, machineLearningDisabled: { @@ -79,4 +108,13 @@ export const systemConfigStub = { enabled: false, }, }, + machineLearningEnabled: { + machineLearning: { + enabled: true, + clip: { + modelName: 'ViT-B-16__openai', + enabled: true, + }, + }, + }, } satisfies Record>; diff --git a/server/test/repositories/asset.repository.mock.ts b/server/test/repositories/asset.repository.mock.ts index 982273ff69b96..928a7956c5f0c 100644 --- a/server/test/repositories/asset.repository.mock.ts +++ b/server/test/repositories/asset.repository.mock.ts @@ -33,7 +33,6 @@ export const newAssetRepositoryMock = (): Mocked => { getTimeBucket: vitest.fn(), getTimeBuckets: vitest.fn(), getAssetIdByCity: vitest.fn(), - getAssetIdByTag: vitest.fn(), getAllForUserFullSync: vitest.fn(), getChangedDeltaSync: vitest.fn(), getDuplicates: vitest.fn(), diff --git a/server/test/repositories/config.repository.mock.ts b/server/test/repositories/config.repository.mock.ts index bb3cfcebb956c..df26f7f72565f 100644 --- a/server/test/repositories/config.repository.mock.ts +++ b/server/test/repositories/config.repository.mock.ts @@ -15,12 +15,23 @@ const envData: EnvData = { queues: [{ name: 'queue-1' }], }, + cls: { + config: {}, + }, + database: { - host: 'database', - port: 5432, - username: 'postgres', - password: 'postgres', - name: 'immich', + config: { + connectionType: 'parts', + database: 'immich', + type: 'postgres', + host: 'database', + port: 5432, + username: 'postgres', + password: 'postgres', + name: 'immich', + synchronize: false, + migrationsRun: true, + }, skipMigrations: false, vectorExtension: DatabaseExtension.VECTORS, @@ -73,11 +84,7 @@ const envData: EnvData = { telemetry: { apiPort: 8081, microservicesPort: 8082, - enabled: false, - hostMetrics: false, - apiMetrics: false, - jobMetrics: false, - repoMetrics: false, + metrics: new Set(), }, workers: [ImmichWorker.API, ImmichWorker.MICROSERVICES], @@ -89,5 +96,6 @@ export const mockEnvData = (config: Partial) => ({ ...envData, ...confi export const newConfigRepositoryMock = (): Mocked => { return { getEnv: vitest.fn().mockReturnValue(mockEnvData({})), + getWorker: vitest.fn().mockReturnValue(ImmichWorker.API), }; }; diff --git a/server/test/repositories/cron.repository.mock.ts b/server/test/repositories/cron.repository.mock.ts new file mode 100644 index 0000000000000..2b0784e8ac27e --- /dev/null +++ b/server/test/repositories/cron.repository.mock.ts @@ -0,0 +1,9 @@ +import { ICronRepository } from 'src/interfaces/cron.interface'; +import { Mocked, vitest } from 'vitest'; + +export const newCronRepositoryMock = (): Mocked => { + return { + create: vitest.fn(), + update: vitest.fn(), + }; +}; diff --git a/server/test/repositories/event.repository.mock.ts b/server/test/repositories/event.repository.mock.ts index 23f5408005182..a425ddef3a7bc 100644 --- a/server/test/repositories/event.repository.mock.ts +++ b/server/test/repositories/event.repository.mock.ts @@ -4,7 +4,6 @@ import { Mocked, vitest } from 'vitest'; export const newEventRepositoryMock = (): Mocked => { return { setup: vitest.fn(), - on: vitest.fn() as any, emit: vitest.fn() as any, clientSend: vitest.fn() as any, clientBroadcast: vitest.fn() as any, diff --git a/server/test/repositories/job.repository.mock.ts b/server/test/repositories/job.repository.mock.ts index cfa1826dd8809..e9557af59bae7 100644 --- a/server/test/repositories/job.repository.mock.ts +++ b/server/test/repositories/job.repository.mock.ts @@ -3,9 +3,9 @@ import { Mocked, vitest } from 'vitest'; export const newJobRepositoryMock = (): Mocked => { return { - addHandler: vitest.fn(), - addCronJob: vitest.fn(), - updateCronJob: vitest.fn(), + setup: vitest.fn(), + startWorkers: vitest.fn(), + run: vitest.fn(), setConcurrency: vitest.fn(), empty: vitest.fn(), pause: vitest.fn(), diff --git a/server/test/repositories/process.repository.mock.ts b/server/test/repositories/process.repository.mock.ts new file mode 100644 index 0000000000000..9a3c5a30b61f6 --- /dev/null +++ b/server/test/repositories/process.repository.mock.ts @@ -0,0 +1,8 @@ +import { IProcessRepository } from 'src/interfaces/process.interface'; +import { Mocked, vitest } from 'vitest'; + +export const newProcessRepositoryMock = (): Mocked => { + return { + spawn: vitest.fn(), + }; +}; diff --git a/server/test/repositories/storage.repository.mock.ts b/server/test/repositories/storage.repository.mock.ts index 5226e0bb1e985..0af16a8d1756f 100644 --- a/server/test/repositories/storage.repository.mock.ts +++ b/server/test/repositories/storage.repository.mock.ts @@ -49,6 +49,7 @@ export const newStorageRepositoryMock = (reset = true): Mocked; type Constructor> = { @@ -50,12 +57,13 @@ type Constructor> = { export const newTestService = ( Service: Constructor, - overrides?: RepositoryOverrides, + overrides?: Overrides, ) => { const { metadataRepository } = overrides || {}; const accessMock = newAccessRepositoryMock(); const loggerMock = newLoggerRepositoryMock(); + const cronMock = newCronRepositoryMock(); const cryptoMock = newCryptoRepositoryMock(); const activityMock = newActivityRepositoryMock(); const auditMock = newAuditRepositoryMock(); @@ -78,6 +86,7 @@ export const newTestService = ( const oauthMock = newOAuthRepositoryMock(); const partnerMock = newPartnerRepositoryMock(); const personMock = newPersonRepositoryMock(); + const processMock = newProcessRepositoryMock(); const searchMock = newSearchRepositoryMock(); const serverInfoMock = newServerInfoRepositoryMock(); const sessionMock = newSessionRepositoryMock(); @@ -101,6 +110,7 @@ export const newTestService = ( albumUserMock, assetMock, configMock, + cronMock, cryptoMock, databaseMock, eventMock, @@ -117,6 +127,7 @@ export const newTestService = ( oauthMock, partnerMock, personMock, + processMock, searchMock, serverInfoMock, sessionMock, @@ -136,6 +147,7 @@ export const newTestService = ( sut, accessMock, loggerMock, + cronMock, cryptoMock, activityMock, auditMock, @@ -158,6 +170,7 @@ export const newTestService = ( oauthMock, partnerMock, personMock, + processMock, searchMock, serverInfoMock, sessionMock, @@ -203,3 +216,37 @@ export const newRandomImage = () => { return value; }; + +export const mockSpawn = vitest.fn((exitCode: number, stdout: string, stderr: string, error?: unknown) => { + return { + stdout: new Readable({ + read() { + this.push(stdout); // write mock data to stdout + this.push(null); // end stream + }, + }), + stderr: new Readable({ + read() { + this.push(stderr); // write mock data to stderr + this.push(null); // end stream + }, + }), + stdin: new Writable({ + write(chunk, encoding, callback) { + callback(); + }, + }), + exitCode, + on: vitest.fn((event, callback: any) => { + if (event === 'close') { + callback(0); + } + if (event === 'error' && error) { + callback(error); + } + if (event === 'exit') { + callback(exitCode); + } + }), + } as unknown as ChildProcessWithoutNullStreams; +}); diff --git a/web/.nvmrc b/web/.nvmrc index 2a393af592b8c..7af24b7ddbde0 100644 --- a/web/.nvmrc +++ b/web/.nvmrc @@ -1 +1 @@ -20.18.0 +22.11.0 diff --git a/web/Dockerfile b/web/Dockerfile index cac593b17f02d..72bd9344da371 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20.18.0-alpine3.20@sha256:c13b26e7e602ef2f1074aef304ce6e9b7dd284c419b35d89fcf3cc8e44a8def9 +FROM node:22.11.0-alpine3.20@sha256:dc8ba2f61dd86c44e43eb25a7812ad03c5b1b224a19fc6f77e1eb9e5669f0b82 RUN apk add --no-cache tini USER node diff --git a/web/package-lock.json b/web/package-lock.json index 45d1198cc7a21..e9f26bee8067d 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "immich-web", - "version": "1.118.2", + "version": "1.120.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "immich-web", - "version": "1.118.2", + "version": "1.120.2", "license": "GNU Affero General Public License version 3", "dependencies": { "@formatjs/icu-messageformat-parser": "^2.7.8", @@ -16,16 +16,16 @@ "@photo-sphere-viewer/core": "^5.7.1", "@photo-sphere-viewer/equirectangular-video-adapter": "^5.7.2", "@photo-sphere-viewer/video-plugin": "^5.7.2", - "@zoom-image/svelte": "^0.2.6", + "@zoom-image/svelte": "^0.3.0", "dom-to-image": "^2.6.0", "handlebars": "^4.7.8", "intl-messageformat": "^10.5.14", "justified-layout": "^4.1.0", "lodash-es": "^4.17.21", "luxon": "^3.4.4", - "socket.io-client": "^4.7.4", + "socket.io-client": "~4.7.5", "svelte-gestures": "^5.0.4", - "svelte-i18n": "^4.0.0", + "svelte-i18n": "^4.0.1", "svelte-local-storage-store": "^0.6.4", "svelte-maplibre": "^0.9.13", "thumbhash": "^0.1.1" @@ -35,12 +35,12 @@ "@eslint/js": "^9.8.0", "@faker-js/faker": "^9.0.0", "@socket.io/component-emitter": "^3.1.0", - "@sveltejs/adapter-static": "^3.0.1", - "@sveltejs/enhanced-img": "^0.3.0", - "@sveltejs/kit": "^2.5.18", - "@sveltejs/vite-plugin-svelte": "^3.1.2", + "@sveltejs/adapter-static": "^3.0.5", + "@sveltejs/enhanced-img": "^0.3.9", + "@sveltejs/kit": "^2.7.2", + "@sveltejs/vite-plugin-svelte": "^4.0.0", "@testing-library/jest-dom": "^6.4.2", - "@testing-library/svelte": "^5.2.0", + "@testing-library/svelte": "^5.2.4", "@testing-library/user-event": "^14.5.2", "@types/dom-to-image": "^2.6.7", "@types/justified-layout": "^4.1.4", @@ -53,7 +53,7 @@ "dotenv": "^16.4.5", "eslint": "^9.0.0", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-svelte": "^2.43.0", + "eslint-plugin-svelte": "^2.45.1", "eslint-plugin-unicorn": "^55.0.0", "factory.ts": "^1.4.1", "globals": "^15.9.0", @@ -63,24 +63,24 @@ "prettier-plugin-sort-json": "^4.0.0", "prettier-plugin-svelte": "^3.2.6", "rollup-plugin-visualizer": "^5.12.0", - "svelte": "^4.2.19", + "svelte": "^5.1.5", "svelte-check": "^4.0.0", "tailwindcss": "^3.4.1", "tslib": "^2.6.2", "typescript": "^5.5.0", - "vite": "^5.1.4", + "vite": "^5.4.4", "vitest": "^2.0.5" } }, "../open-api/typescript-sdk": { "name": "@immich/sdk", - "version": "1.118.2", + "version": "1.120.2", "license": "GNU Affero General Public License version 3", "dependencies": { "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^20.16.12", + "@types/node": "^22.9.0", "typescript": "^5.3.3" } }, @@ -138,19 +138,21 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -170,12 +172,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", - "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.25.2" + "@babel/types": "^7.26.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -197,14 +200,14 @@ } }, "node_modules/@babel/types": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", - "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -658,9 +661,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", - "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", + "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -736,9 +739,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.12.0.tgz", - "integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", + "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", "dev": true, "license": "MIT", "engines": { @@ -769,9 +772,9 @@ } }, "node_modules/@faker-js/faker": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.0.3.tgz", - "integrity": "sha512-lWrrK4QNlFSU+13PL9jMbMKLJYXDFu3tQfayBsMXX7KL/GiQeqfB1CzHkqD5UHBUtPAuPo6XwGbMFNdVMZObRA==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.1.0.tgz", + "integrity": "sha512-GJvX9iM9PBtKScJVlXQ0tWpihK3i0pha/XAhzQa1hPK/ILLa1Wq3I63Ij7lRtqTwmdTxRCyrUhLC5Sly9SLbug==", "dev": true, "funding": [ { @@ -786,53 +789,53 @@ } }, "node_modules/@formatjs/ecma402-abstract": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.0.tgz", - "integrity": "sha512-IpM+ev1E4QLtstniOE29W1rqH9eTdx5hQdNL8pzrflMj/gogfaoONZqL83LUeQScHAvyMbpqP5C9MzNf+fFwhQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.1.tgz", + "integrity": "sha512-O4ywpkdJybrjFc9zyL8qK5aklleIAi5O4nYhBVJaOFtCkNrnU+lKFeJOFC48zpsZQmR8Aok2V79hGpHnzbmFpg==", "license": "MIT", "dependencies": { - "@formatjs/fast-memoize": "2.2.1", - "@formatjs/intl-localematcher": "0.5.5", - "tslib": "^2.7.0" + "@formatjs/fast-memoize": "2.2.2", + "@formatjs/intl-localematcher": "0.5.6", + "tslib": "2" } }, "node_modules/@formatjs/fast-memoize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.1.tgz", - "integrity": "sha512-XS2RcOSyWxmUB7BUjj3mlPH0exsUzlf6QfhhijgI941WaJhVxXQ6mEWkdUFIdnKi3TuTYxRdelsgv3mjieIGIA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.2.tgz", + "integrity": "sha512-mzxZcS0g1pOzwZTslJOBTmLzDXseMLLvnh25ymRilCm8QLMObsQ7x/rj9GNrH0iUhZMlFisVOD6J1n6WQqpKPQ==", "license": "MIT", "dependencies": { - "tslib": "^2.7.0" + "tslib": "2" } }, "node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.7.10", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.10.tgz", - "integrity": "sha512-wlQfqCZ7PURkUNL2+8VTEFavPovtADU/isSKLFvDbdFmV7QPZIYqFMkhklaDYgMyLSBJa/h2MVQ2aFvoEJhxgg==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.9.1.tgz", + "integrity": "sha512-7AYk4tjnLi5wBkxst2w7qFj38JLMJoqzj7BhdEl7oTlsWMlqwgx4p9oMmmvpXWTSDGNwOKBRc1SfwMh5MOHeNg==", "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "2.2.0", - "@formatjs/icu-skeleton-parser": "1.8.4", - "tslib": "^2.7.0" + "@formatjs/ecma402-abstract": "2.2.1", + "@formatjs/icu-skeleton-parser": "1.8.5", + "tslib": "2" } }, "node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.4.tgz", - "integrity": "sha512-LMQ1+Wk1QSzU4zpd5aSu7+w5oeYhupRwZnMQckLPRYhSjf2/8JWQ882BauY9NyHxs5igpuQIXZDgfkaH3PoATg==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.5.tgz", + "integrity": "sha512-zRZ/e3B5qY2+JCLs7puTzWS1Jb+t/K+8Jur/gEZpA2EjWeLDE17nsx8thyo9P48Mno7UmafnPupV2NCJXX17Dg==", "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "2.2.0", - "tslib": "^2.7.0" + "@formatjs/ecma402-abstract": "2.2.1", + "tslib": "2" } }, "node_modules/@formatjs/intl-localematcher": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.5.tgz", - "integrity": "sha512-t5tOGMgZ/i5+ALl2/offNqAQq/lfUnKLEw0mXQI4N4bqpedhrSE+fyKLpwnd22sK0dif6AV+ufQcTsKShB9J1g==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.6.tgz", + "integrity": "sha512-roz1+Ba5e23AHX6KUAWmLEyTRZegM5YDuxuvkHCyK3RJddf/UXB2f+s7pOMm9ktfPGla0g+mQXOn5vsuYirnaA==", "license": "MIT", "dependencies": { - "tslib": "^2.7.0" + "tslib": "2" } }, "node_modules/@humanfs/core": { @@ -1635,30 +1638,30 @@ } }, "node_modules/@photo-sphere-viewer/core": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/core/-/core-5.11.0.tgz", - "integrity": "sha512-SjEVBBMNj5v3aSLglpTd88q+cyDw0Lke7mK3kN3p+KsQK8X0OG7GsbtI7sGj81zSCbHmV3c0DgUaXMv+xdGFaw==", + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/core/-/core-5.11.1.tgz", + "integrity": "sha512-bxWnoQGYjXfmHGee4OSkoYLZmdgqvJWMn7wmpK0V0Vf46Fqu+TJ4Yt8+dY2PgpM89HoKzNr15Dzt6jqOfjkFxQ==", "license": "MIT", "dependencies": { "three": "^0.169.0" } }, "node_modules/@photo-sphere-viewer/equirectangular-video-adapter": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/equirectangular-video-adapter/-/equirectangular-video-adapter-5.11.0.tgz", - "integrity": "sha512-xs5+vT5jYjBtEsguuJe6CVJNGxtwopb3+Zs4z/VJg0oNm2mTZF2TQu2RkSlRJZTE6a/IfZ7Zv1fJcN3Yiv7AVg==", + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/equirectangular-video-adapter/-/equirectangular-video-adapter-5.11.1.tgz", + "integrity": "sha512-fkWuVeArtZSWd0z282/J82YSc+oernQaE/cpo0soVaStaNbS1V35iSnPlaBKw40qX6tucJWYw15QwM8xgPC2IQ==", "license": "MIT", "peerDependencies": { - "@photo-sphere-viewer/core": "5.11.0" + "@photo-sphere-viewer/core": "5.11.1" } }, "node_modules/@photo-sphere-viewer/video-plugin": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/video-plugin/-/video-plugin-5.11.0.tgz", - "integrity": "sha512-6ApAKvwDRgVZOk4N/3SJ14IFpQ6V0QDDtknXxLI5JqU1yAvBcyb7goa5XDbyTWXYUaPKH06Fz+8UruWRzCsBXw==", + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/video-plugin/-/video-plugin-5.11.1.tgz", + "integrity": "sha512-02spWwv9bjyI6inNdZsczX/qdMICVV9B8lWX/J4iNBaiUCHqPKmk8CeZbRyC/Uh3OHSusSJHyW0FDEOf6qjjww==", "license": "MIT", "peerDependencies": { - "@photo-sphere-viewer/core": "5.11.0" + "@photo-sphere-viewer/core": "5.11.1" } }, "node_modules/@pkgjs/parseargs": { @@ -1935,9 +1938,9 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" }, "node_modules/@sveltejs/adapter-static": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.5.tgz", - "integrity": "sha512-kFJR7RxeB6FBvrKZWAEzIALatgy11ISaaZbcPup8JdWUdrmmfUHHTJ738YHJTEfnCiiXi6aX8Q6ePY7tnSMD6Q==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.6.tgz", + "integrity": "sha512-MGJcesnJWj7FxDcB/GbrdYD3q24Uk0PIL4QIX149ku+hlJuj//nxUbb0HxUTpjkecWfHjVveSUnUaQWnPRXlpg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1945,9 +1948,9 @@ } }, "node_modules/@sveltejs/enhanced-img": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@sveltejs/enhanced-img/-/enhanced-img-0.3.9.tgz", - "integrity": "sha512-hDhoIbkDAY08II/1DWeY2lGMY9nhETC96B2HTbxoI6EDqxLErBDKqnRN3QQRuMJATxPGVNhCKUkuARi4TCLtpQ==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@sveltejs/enhanced-img/-/enhanced-img-0.3.10.tgz", + "integrity": "sha512-1JxjthN6yb1md3rSFbHRDBi/Jj2R9EjE06vh9zbWgYxm5d4UUphTzNICJTis8bkIDQilbAokrkaQtfRpKSE7qg==", "dev": true, "license": "MIT", "dependencies": { @@ -1961,9 +1964,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.7.1.tgz", - "integrity": "sha512-TBVnkwgYQT3EafGQK6Eyh5FlLEBlRhCmqPTwcdOs+QdnyUc3eCAxRWtXlFxIWtmk6pqv11zdng8qTpThdTogew==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.7.3.tgz", + "integrity": "sha512-Vx7nq5MJ86I8qXYsVidC5PX6xm+uxt8DydvOdmJoyOK7LvGP18OFEG359yY+aa51t6pENvqZAMqAREQQx1OI2Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1994,43 +1997,42 @@ } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.2.tgz", - "integrity": "sha512-Txsm1tJvtiYeLUVRNqxZGKR/mI+CzuIQuc2gn+YCs9rMTowpNZ2Nqt53JdL8KF9bLhAf2ruR/dr9eZCwdTriRA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-4.0.0.tgz", + "integrity": "sha512-kpVJwF+gNiMEsoHaw+FJL76IYiwBikkxYU83+BpqQLdVMff19KeRKLd2wisS8niNBMJ2omv5gG+iGDDwd8jzag==", "dev": true, "license": "MIT", "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^2.1.0", - "debug": "^4.3.4", + "@sveltejs/vite-plugin-svelte-inspector": "^3.0.0-next.0||^3.0.0", + "debug": "^4.3.7", "deepmerge": "^4.3.1", "kleur": "^4.1.5", - "magic-string": "^0.30.10", - "svelte-hmr": "^0.16.0", - "vitefu": "^0.2.5" + "magic-string": "^0.30.12", + "vitefu": "^1.0.3" }, "engines": { - "node": "^18.0.0 || >=20" + "node": "^18.0.0 || ^20.0.0 || >=22" }, "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.0", + "svelte": "^5.0.0-next.96 || ^5.0.0", "vite": "^5.0.0" } }, "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.1.0.tgz", - "integrity": "sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-3.0.1.tgz", + "integrity": "sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.3.4" + "debug": "^4.3.7" }, "engines": { - "node": "^18.0.0 || >=20" + "node": "^18.0.0 || ^20.0.0 || >=22" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.0.0", - "svelte": "^4.0.0 || ^5.0.0-next.0", + "@sveltejs/vite-plugin-svelte": "^4.0.0-next.0||^4.0.0", + "svelte": "^5.0.0-next.96 || ^5.0.0", "vite": "^5.0.0" } }, @@ -2125,9 +2127,9 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.2.tgz", - "integrity": "sha512-P6GJD4yqc9jZLbe98j/EkyQDTPgqftohZF5FBkHY5BUERZmcf4HeO2k0XaefEg329ux2p21i1A1DmyQ1kKw2Jw==", + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", + "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", "dev": true, "license": "MIT", "dependencies": { @@ -2226,11 +2228,10 @@ } }, "node_modules/@testing-library/svelte": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-5.2.3.tgz", - "integrity": "sha512-y5eaD2Vp3hb729dAv3dOYNoZ9uNM0bQ0rd5AfXDWPvI+HiGmjl8ZMOuKzBopveyAkci1Kplb6kS53uZhPGEK+w==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-5.2.4.tgz", + "integrity": "sha512-EFdy73+lULQgMJ1WolAymrxWWrPv9DWyDuDFKKlUip2PA/EXuHptzfYOKWljccFWDKhhGOu3dqNmoc2f/h/Ecg==", "dev": true, - "license": "MIT", "dependencies": { "@testing-library/dom": "^10.0.0" }, @@ -2385,17 +2386,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.10.0.tgz", - "integrity": "sha512-phuB3hoP7FFKbRXxjl+DRlQDuJqhpOnm5MmtROXyWi3uS/Xg2ZXqiQfcG2BJHiN4QKyzdOJi3NEn/qTnjUlkmQ==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.12.2.tgz", + "integrity": "sha512-gQxbxM8mcxBwaEmWdtLCIGLfixBMHhQjBqR8sVWNTPpcj45WlYL2IObS/DNMLH1DBP0n8qz+aiiLTGfopPEebw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.10.0", - "@typescript-eslint/type-utils": "8.10.0", - "@typescript-eslint/utils": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0", + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/type-utils": "8.12.2", + "@typescript-eslint/utils": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2419,16 +2420,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.10.0.tgz", - "integrity": "sha512-E24l90SxuJhytWJ0pTQydFT46Nk0Z+bsLKo/L8rtQSL93rQ6byd1V/QbDpHUTdLPOMsBCcYXZweADNCfOCmOAg==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.12.2.tgz", + "integrity": "sha512-MrvlXNfGPLH3Z+r7Tk+Z5moZAc0dzdVjTgUgwsdGweH7lydysQsnSww3nAmsq8blFuRD5VRlAr9YdEFw3e6PBw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.10.0", - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/typescript-estree": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0", + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/typescript-estree": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "debug": "^4.3.4" }, "engines": { @@ -2448,14 +2449,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.10.0.tgz", - "integrity": "sha512-AgCaEjhfql9MDKjMUxWvH7HjLeBqMCBfIaBbzzIcBbQPZE7CPh1m6FF+L75NUMJFMLYhCywJXIDEMa3//1A0dw==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.12.2.tgz", + "integrity": "sha512-gPLpLtrj9aMHOvxJkSbDBmbRuYdtiEbnvO25bCMza3DhMjTQw0u7Y1M+YR5JPbMsXXnSPuCf5hfq0nEkQDL/JQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0" + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2466,14 +2467,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.10.0.tgz", - "integrity": "sha512-PCpUOpyQSpxBn230yIcK+LeCQaXuxrgCm2Zk1S+PTIRJsEfU6nJ0TtwyH8pIwPK/vJoA+7TZtzyAJSGBz+s/dg==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.12.2.tgz", + "integrity": "sha512-bwuU4TAogPI+1q/IJSKuD4shBLc/d2vGcRT588q+jzayQyjVK2X6v/fbR4InY2U2sgf8MEvVCqEWUzYzgBNcGQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.10.0", - "@typescript-eslint/utils": "8.10.0", + "@typescript-eslint/typescript-estree": "8.12.2", + "@typescript-eslint/utils": "8.12.2", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -2491,9 +2492,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.10.0.tgz", - "integrity": "sha512-k/E48uzsfJCRRbGLapdZgrX52csmWJ2rcowwPvOZ8lwPUv3xW6CcFeJAXgx4uJm+Ge4+a4tFOkdYvSpxhRhg1w==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.12.2.tgz", + "integrity": "sha512-VwDwMF1SZ7wPBUZwmMdnDJ6sIFk4K4s+ALKLP6aIQsISkPv8jhiw65sAK6SuWODN/ix+m+HgbYDkH+zLjrzvOA==", "dev": true, "license": "MIT", "engines": { @@ -2505,14 +2506,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.10.0.tgz", - "integrity": "sha512-3OE0nlcOHaMvQ8Xu5gAfME3/tWVDpb/HxtpUZ1WeOAksZ/h/gwrBzCklaGzwZT97/lBbbxJ16dMA98JMEngW4w==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.12.2.tgz", + "integrity": "sha512-mME5MDwGe30Pq9zKPvyduyU86PH7aixwqYR2grTglAdB+AN8xXQ1vFGpYaUSJ5o5P/5znsSBeNcs5g5/2aQwow==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2560,16 +2561,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.10.0.tgz", - "integrity": "sha512-Oq4uZ7JFr9d1ZunE/QKy5egcDRXT/FrS2z/nlxzPua2VHFtmMvFNDvpq1m/hq0ra+T52aUezfcjGRIB7vNJF9w==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.12.2.tgz", + "integrity": "sha512-UTTuDIX3fkfAz6iSVa5rTuSfWIYZ6ATtEocQ/umkRSyC9O919lbZ8dcH7mysshrCdrAM03skJOEYaBugxN+M6A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.10.0", - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/typescript-estree": "8.10.0" + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/typescript-estree": "8.12.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2583,13 +2584,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.10.0.tgz", - "integrity": "sha512-k8nekgqwr7FadWk548Lfph6V3r9OVqjzAIVskE7orMZR23cGJjAOVazsZSJW+ElyjfTM4wx/1g88Mi70DDtG9A==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.12.2.tgz", + "integrity": "sha512-PChz8UaKQAVNHghsHcPyx1OMHoFRUEA7rJSK/mDhdq85bk+PLsUHUBqTQTFt18VJZbmxBovM65fezlheQRsSDA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.10.0", + "@typescript-eslint/types": "8.12.2", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -2601,21 +2602,21 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.3.tgz", - "integrity": "sha512-2OJ3c7UPoFSmBZwqD2VEkUw6A/tzPF0LmW0ZZhhB8PFxuc+9IBG/FaSM+RLEenc7ljzFvGN+G0nGQoZnh7sy2A==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.4.tgz", + "integrity": "sha512-FPKQuJfR6VTfcNMcGpqInmtJuVXFSCd9HQltYncfR01AzXhLucMEtQ5SinPdZxsT5x/5BK7I5qFJ5/ApGCmyTQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.6", + "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.11", - "magicast": "^0.3.4", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", "std-env": "^3.7.0", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" @@ -2624,8 +2625,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.3", - "vitest": "2.1.3" + "@vitest/browser": "2.1.4", + "vitest": "2.1.4" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2634,15 +2635,15 @@ } }, "node_modules/@vitest/expect": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.3.tgz", - "integrity": "sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.4.tgz", + "integrity": "sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.3", - "@vitest/utils": "2.1.3", - "chai": "^5.1.1", + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -2650,22 +2651,21 @@ } }, "node_modules/@vitest/mocker": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.3.tgz", - "integrity": "sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.4.tgz", + "integrity": "sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.3", + "@vitest/spy": "2.1.4", "estree-walker": "^3.0.3", - "magic-string": "^0.30.11" + "magic-string": "^0.30.12" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/spy": "2.1.3", - "msw": "^2.3.5", + "msw": "^2.4.9", "vite": "^5.0.0" }, "peerDependenciesMeta": { @@ -2678,9 +2678,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.3.tgz", - "integrity": "sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.4.tgz", + "integrity": "sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==", "dev": true, "license": "MIT", "dependencies": { @@ -2691,13 +2691,13 @@ } }, "node_modules/@vitest/runner": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.3.tgz", - "integrity": "sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.4.tgz", + "integrity": "sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.3", + "@vitest/utils": "2.1.4", "pathe": "^1.1.2" }, "funding": { @@ -2705,14 +2705,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.3.tgz", - "integrity": "sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.4.tgz", + "integrity": "sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.3", - "magic-string": "^0.30.11", + "@vitest/pretty-format": "2.1.4", + "magic-string": "^0.30.12", "pathe": "^1.1.2" }, "funding": { @@ -2720,27 +2720,27 @@ } }, "node_modules/@vitest/spy": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.3.tgz", - "integrity": "sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.4.tgz", + "integrity": "sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==", "dev": true, "license": "MIT", "dependencies": { - "tinyspy": "^3.0.0" + "tinyspy": "^3.0.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.3.tgz", - "integrity": "sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.4.tgz", + "integrity": "sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.3", - "loupe": "^3.1.1", + "@vitest/pretty-format": "2.1.4", + "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -2761,9 +2761,9 @@ } }, "node_modules/@zoom-image/svelte": { - "version": "0.2.23", - "resolved": "https://registry.npmjs.org/@zoom-image/svelte/-/svelte-0.2.23.tgz", - "integrity": "sha512-InurNGmo0cZ72R9Al3YjjAsaFAMrWqMA64rLhkLG79KZQOmC4HLcDEGBPGE0aSXHdg03QUGxqa+Yut5430r/1Q==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@zoom-image/svelte/-/svelte-0.3.0.tgz", + "integrity": "sha512-0dfAPgpGRm+j6d3fn044swV7r821l2ZFJZmR0WqUATUUaPZ3GbDkDyrVuZGmP7s4QAk/Nvs1F3+cBhcMWt9Zfw==", "license": "MIT", "dependencies": { "@zoom-image/core": "0.39.0" @@ -2773,13 +2773,13 @@ "url": "https://github.com/sponsors/willnguyen1312" }, "peerDependencies": { - "svelte": "^3.0.0 || ^4.0.0" + "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0" } }, "node_modules/acorn": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", - "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -2797,6 +2797,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-typescript": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz", + "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==", + "license": "MIT", + "peerDependencies": { + "acorn": ">=8.9.0" + } + }, "node_modules/agent-base": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", @@ -2883,6 +2892,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, "dependencies": { "dequal": "^2.0.3" } @@ -2960,11 +2970,12 @@ } }, "node_modules/axobject-query": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz", - "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==", - "dependencies": { - "dequal": "^2.0.3" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" } }, "node_modules/balanced-match": { @@ -3122,9 +3133,9 @@ "license": "CC-BY-4.0" }, "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, "license": "MIT", "dependencies": { @@ -3271,18 +3282,6 @@ "node": ">=6" } }, - "node_modules/code-red": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", - "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15", - "@types/estree": "^1.0.1", - "acorn": "^8.10.0", - "estree-walker": "^3.0.3", - "periscopic": "^3.1.0" - } - }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -3404,18 +3403,6 @@ "node": ">= 8" } }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", @@ -3505,11 +3492,12 @@ } }, "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -3656,22 +3644,21 @@ "dev": true }, "node_modules/engine.io-client": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.1.tgz", - "integrity": "sha512-aYuoak7I+R83M/BBPIOs2to51BmFIpC1wZe6zZzMrT2llVsHy5cvcmdsJgP2Qz6smHu+sD9oexiSUAVd8OfBPw==", - "license": "MIT", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz", + "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", "ws": "~8.17.1", - "xmlhttprequest-ssl": "~2.1.1" + "xmlhttprequest-ssl": "~2.0.0" } }, "node_modules/engine.io-parser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", - "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "engines": { "node": ">=10.0.0" } @@ -3806,18 +3793,18 @@ } }, "node_modules/eslint": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz", - "integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz", + "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.6.0", + "@eslint/core": "^0.7.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.12.0", + "@eslint/js": "9.13.0", "@eslint/plugin-kit": "^0.2.0", "@humanfs/node": "^0.16.5", "@humanwhocodes/module-importer": "^1.0.1", @@ -3895,9 +3882,9 @@ } }, "node_modules/eslint-plugin-svelte": { - "version": "2.45.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.45.1.tgz", - "integrity": "sha512-mYAKNDRji0YWl7o00KQi0enREcrtzcN7xwK/8lwk5uLRoKLjzPXc+WjngsYpPV35I3AF7UlXc1+JfyNMJS+njA==", + "version": "2.46.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.46.0.tgz", + "integrity": "sha512-1A7iEMkzmCZ9/Iz+EAfOGYL8IoIG6zeKEq1SmpxGeM5SXmoQq+ZNnCpXFVJpsxPWYx8jIVGMerQMzX20cqUl0g==", "dev": true, "license": "MIT", "dependencies": { @@ -3905,13 +3892,13 @@ "@jridgewell/sourcemap-codec": "^1.4.15", "eslint-compat-utils": "^0.5.1", "esutils": "^2.0.3", - "known-css-properties": "^0.34.0", + "known-css-properties": "^0.35.0", "postcss": "^8.4.38", "postcss-load-config": "^3.1.4", "postcss-safe-parser": "^6.0.0", "postcss-selector-parser": "^6.1.0", "semver": "^7.6.2", - "svelte-eslint-parser": "^0.42.0" + "svelte-eslint-parser": "^0.43.0" }, "engines": { "node": "^14.17.0 || >=16.0.0" @@ -3921,7 +3908,7 @@ }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0", - "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.191" + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "peerDependenciesMeta": { "svelte": { @@ -4078,9 +4065,9 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", - "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -4095,9 +4082,9 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", - "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4108,15 +4095,15 @@ } }, "node_modules/eslint/node_modules/espree": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", - "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.12.0", + "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.1.0" + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4151,8 +4138,7 @@ "node_modules/esm-env": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz", - "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==", - "dev": true + "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==" }, "node_modules/esniff": { "version": "2.0.1", @@ -4197,6 +4183,16 @@ "node": ">=0.10" } }, + "node_modules/esrap": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz", + "integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1" + } + }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -4222,6 +4218,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, "dependencies": { "@types/estree": "^1.0.0" } @@ -4244,6 +4241,16 @@ "es5-ext": "~0.10.14" } }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -4816,15 +4823,15 @@ } }, "node_modules/intl-messageformat": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.0.tgz", - "integrity": "sha512-2P06M9jFTqJnEQzE072VGPjbAx6ZG1YysgopAwc8ui0ajSjtwX1MeQ6bXFXIzKcNENJTizKkcJIcZ0zlpl1zSg==", + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.3.tgz", + "integrity": "sha512-AAo/3oyh7ROfPhDuh7DxTTydh97OC+lv7h1Eq5LuHWuLsUMKOhtzTYuyXlUReuwZ9vANDHo4CS1bGRrn7TZRtg==", "license": "BSD-3-Clause", "dependencies": { - "@formatjs/ecma402-abstract": "2.2.0", - "@formatjs/fast-memoize": "2.2.1", - "@formatjs/icu-messageformat-parser": "2.7.10", - "tslib": "^2.7.0" + "@formatjs/ecma402-abstract": "2.2.1", + "@formatjs/fast-memoize": "2.2.2", + "@formatjs/icu-messageformat-parser": "2.9.1", + "tslib": "2" } }, "node_modules/is-arrayish": { @@ -4962,6 +4969,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "license": "MIT", "dependencies": { "@types/estree": "*" } @@ -5224,9 +5232,9 @@ } }, "node_modules/known-css-properties": { - "version": "0.34.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.34.0.tgz", - "integrity": "sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.35.0.tgz", + "integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==", "dev": true, "license": "MIT" }, @@ -5329,22 +5337,23 @@ } }, "node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/magicast": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", - "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/parser": "^7.24.4", - "@babel/types": "^7.24.0", + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, @@ -5475,11 +5484,6 @@ "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" } }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" - }, "node_modules/memoizee": { "version": "0.4.17", "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", @@ -5601,9 +5605,10 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/murmurhash-js": { "version": "1.0.0", @@ -5943,16 +5948,6 @@ "pbf": "bin/pbf" } }, - "node_modules/periscopic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", - "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^3.0.0", - "is-reference": "^3.0.0" - } - }, "node_modules/picocolors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", @@ -6860,14 +6855,13 @@ } }, "node_modules/socket.io-client": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.0.tgz", - "integrity": "sha512-C0jdhD5yQahMws9alf/yvtsMGTaIDBnZ8Rb5HU56svyq0l5LIrGzIDZZD5pHQlmzxLuU91Gz+VpQMKgCTNYtkw==", - "license": "MIT", + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", - "engine.io-client": "~6.6.1", + "engine.io-client": "~6.5.2", "socket.io-parser": "~4.2.4" }, "engines": { @@ -6930,6 +6924,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -7176,28 +7171,27 @@ } }, "node_modules/svelte": { - "version": "4.2.19", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz", - "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.1.8.tgz", + "integrity": "sha512-MCZVSNNqlgwKUSEZEsq2nILhzI70qv1jJy9fG9nf3I8CyJhJ2vxtPybDuP5HdB7Q9Az0WliFmqUeLEQdnY1j+g==", "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.1", - "@jridgewell/sourcemap-codec": "^1.4.15", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/estree": "^1.0.1", - "acorn": "^8.9.0", - "aria-query": "^5.3.0", - "axobject-query": "^4.0.0", - "code-red": "^1.0.3", - "css-tree": "^2.3.1", - "estree-walker": "^3.0.3", - "is-reference": "^3.0.1", + "@ampproject/remapping": "^2.3.0", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@types/estree": "^1.0.5", + "acorn": "^8.12.1", + "acorn-typescript": "^1.4.13", + "aria-query": "^5.3.1", + "axobject-query": "^4.1.0", + "esm-env": "^1.0.0", + "esrap": "^1.2.2", + "is-reference": "^3.0.2", "locate-character": "^3.0.0", - "magic-string": "^0.30.4", - "periscopic": "^3.1.0" + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/svelte-check": { @@ -7285,9 +7279,9 @@ } }, "node_modules/svelte-eslint-parser": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.42.0.tgz", - "integrity": "sha512-e7LyqFPTuF43ZYhKOf0Gq1lzP+G64iWVJXAIcwVxohGx5FFyqdUkw7DEXNjZ+Fm+TAA98zPmDqWvgD1OpyMi5A==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz", + "integrity": "sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==", "dev": true, "license": "MIT", "dependencies": { @@ -7304,7 +7298,7 @@ "url": "https://github.com/sponsors/ota-meshi" }, "peerDependencies": { - "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.191" + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "peerDependenciesMeta": { "svelte": { @@ -7318,22 +7312,11 @@ "integrity": "sha512-kElJnoZrQtlkXE0O/RcKioz9NP0Sxx05j31ohyosNkydo6NOEsZB85mhoaCxOQNjxN+QPumYWfmIUsznYFjihA==", "license": "MIT" }, - "node_modules/svelte-hmr": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz", - "integrity": "sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==", - "dev": true, - "engines": { - "node": "^12.20 || ^14.13.1 || >= 16" - }, - "peerDependencies": { - "svelte": "^3.19.0 || ^4.0.0" - } - }, "node_modules/svelte-i18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/svelte-i18n/-/svelte-i18n-4.0.0.tgz", - "integrity": "sha512-4vivjKZADUMRIhTs38JuBNy3unbnh9AFRxWFLxq62P4NHic+/BaIZZlAsvqsCdnp7IdJf5EoSiH6TNdItcjA6g==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/svelte-i18n/-/svelte-i18n-4.0.1.tgz", + "integrity": "sha512-jaykGlGT5PUaaq04JWbJREvivlCnALtT+m87Kbm0fxyYHynkQaxQMnIKHLm2WeIuBRoljzwgyvz0Z6/CMwfdmQ==", + "license": "MIT", "dependencies": { "cli-color": "^2.0.3", "deepmerge": "^4.2.2", @@ -7350,7 +7333,7 @@ "node": ">= 16" }, "peerDependencies": { - "svelte": "^3 || ^4" + "svelte": "^3 || ^4 || ^5" } }, "node_modules/svelte-i18n/node_modules/@esbuild/aix-ppc64": { @@ -7360,6 +7343,7 @@ "cpu": [ "ppc64" ], + "license": "MIT", "optional": true, "os": [ "aix" @@ -7375,6 +7359,7 @@ "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -7390,6 +7375,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -7405,6 +7391,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -7420,6 +7407,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -7435,6 +7423,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -7450,6 +7439,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -7465,6 +7455,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -7480,6 +7471,7 @@ "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -7495,6 +7487,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -7510,6 +7503,7 @@ "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -7525,6 +7519,7 @@ "cpu": [ "loong64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -7540,6 +7535,7 @@ "cpu": [ "mips64el" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -7555,6 +7551,7 @@ "cpu": [ "ppc64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -7570,6 +7567,7 @@ "cpu": [ "riscv64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -7585,6 +7583,7 @@ "cpu": [ "s390x" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -7600,6 +7599,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -7615,6 +7615,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -7630,6 +7631,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -7645,6 +7647,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "sunos" @@ -7660,6 +7663,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -7675,6 +7679,7 @@ "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -7690,6 +7695,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -7703,6 +7709,7 @@ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -7738,7 +7745,8 @@ "node_modules/svelte-i18n/node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" }, "node_modules/svelte-local-storage-store": { "version": "0.6.4", @@ -7795,6 +7803,15 @@ "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0-next.1" } }, + "node_modules/svelte/node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -8025,17 +8042,18 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", - "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", "dev": true, "license": "MIT" }, "node_modules/tinypool": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.0.tgz", - "integrity": "sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" } @@ -8064,15 +8082,6 @@ "node": ">=14.0.0" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -8299,9 +8308,9 @@ } }, "node_modules/vite": { - "version": "5.4.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz", - "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==", + "version": "5.4.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", + "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8372,14 +8381,14 @@ } }, "node_modules/vite-node": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.3.tgz", - "integrity": "sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.4.tgz", + "integrity": "sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.6", + "debug": "^4.3.7", "pathe": "^1.1.2", "vite": "^5.0.0" }, @@ -8394,12 +8403,17 @@ } }, "node_modules/vitefu": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz", - "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.3.tgz", + "integrity": "sha512-iKKfOMBHob2WxEJbqbJjHAkmYgvFDPhuqrO82om83S8RLk+17FtyMBfcyeH8GqD0ihShtkMW/zzJgiA51hCNCQ==", "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*" + ], "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0-beta.0" }, "peerDependenciesMeta": { "vite": { @@ -8408,30 +8422,31 @@ } }, "node_modules/vitest": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.3.tgz", - "integrity": "sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "2.1.3", - "@vitest/mocker": "2.1.3", - "@vitest/pretty-format": "^2.1.3", - "@vitest/runner": "2.1.3", - "@vitest/snapshot": "2.1.3", - "@vitest/spy": "2.1.3", - "@vitest/utils": "2.1.3", - "chai": "^5.1.1", - "debug": "^4.3.6", - "magic-string": "^0.30.11", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.4.tgz", + "integrity": "sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.4", + "@vitest/mocker": "2.1.4", + "@vitest/pretty-format": "^2.1.4", + "@vitest/runner": "2.1.4", + "@vitest/snapshot": "2.1.4", + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.7.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.0", - "tinypool": "^1.0.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.1.3", + "vite-node": "2.1.4", "why-is-node-running": "^2.3.0" }, "bin": { @@ -8446,8 +8461,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.3", - "@vitest/ui": "2.1.3", + "@vitest/browser": "2.1.4", + "@vitest/ui": "2.1.4", "happy-dom": "*", "jsdom": "*" }, @@ -8731,9 +8746,9 @@ "peer": true }, "node_modules/xmlhttprequest-ssl": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.1.tgz", - "integrity": "sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", "engines": { "node": ">=0.4.0" } @@ -8794,6 +8809,12 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zimmerframe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", + "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "license": "MIT" } } } diff --git a/web/package.json b/web/package.json index bcc52b8d45d50..c0c600f5bc809 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "immich-web", - "version": "1.118.2", + "version": "1.120.2", "license": "GNU Affero General Public License version 3", "scripts": { "dev": "vite dev --host 0.0.0.0 --port 3000", @@ -8,7 +8,7 @@ "build:stats": "BUILD_STATS=true vite build", "package": "svelte-kit package", "preview": "vite preview", - "check:svelte": "svelte-check --no-tsconfig --fail-on-warnings", + "check:svelte": "svelte-check --no-tsconfig --fail-on-warnings --compiler-warnings 'reactive_declaration_non_reactive_property:ignore'", "check:typescript": "tsc --noEmit", "check:watch": "npm run check:svelte -- --watch", "check:code": "npm run format && npm run lint && npm run check:svelte && npm run check:typescript", @@ -27,12 +27,12 @@ "@eslint/js": "^9.8.0", "@faker-js/faker": "^9.0.0", "@socket.io/component-emitter": "^3.1.0", - "@sveltejs/adapter-static": "^3.0.1", - "@sveltejs/enhanced-img": "^0.3.0", - "@sveltejs/kit": "^2.5.18", - "@sveltejs/vite-plugin-svelte": "^3.1.2", + "@sveltejs/adapter-static": "^3.0.5", + "@sveltejs/enhanced-img": "^0.3.9", + "@sveltejs/kit": "^2.7.2", + "@sveltejs/vite-plugin-svelte": "^4.0.0", "@testing-library/jest-dom": "^6.4.2", - "@testing-library/svelte": "^5.2.0", + "@testing-library/svelte": "^5.2.4", "@testing-library/user-event": "^14.5.2", "@types/dom-to-image": "^2.6.7", "@types/justified-layout": "^4.1.4", @@ -45,7 +45,7 @@ "dotenv": "^16.4.5", "eslint": "^9.0.0", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-svelte": "^2.43.0", + "eslint-plugin-svelte": "^2.45.1", "eslint-plugin-unicorn": "^55.0.0", "factory.ts": "^1.4.1", "globals": "^15.9.0", @@ -55,12 +55,12 @@ "prettier-plugin-sort-json": "^4.0.0", "prettier-plugin-svelte": "^3.2.6", "rollup-plugin-visualizer": "^5.12.0", - "svelte": "^4.2.19", + "svelte": "^5.1.5", "svelte-check": "^4.0.0", "tailwindcss": "^3.4.1", "tslib": "^2.6.2", "typescript": "^5.5.0", - "vite": "^5.1.4", + "vite": "^5.4.4", "vitest": "^2.0.5" }, "type": "module", @@ -72,21 +72,21 @@ "@photo-sphere-viewer/core": "^5.7.1", "@photo-sphere-viewer/equirectangular-video-adapter": "^5.7.2", "@photo-sphere-viewer/video-plugin": "^5.7.2", - "@zoom-image/svelte": "^0.2.6", + "@zoom-image/svelte": "^0.3.0", "dom-to-image": "^2.6.0", "handlebars": "^4.7.8", "intl-messageformat": "^10.5.14", "justified-layout": "^4.1.0", "lodash-es": "^4.17.21", "luxon": "^3.4.4", - "socket.io-client": "^4.7.4", + "socket.io-client": "~4.7.5", "svelte-gestures": "^5.0.4", - "svelte-i18n": "^4.0.0", + "svelte-i18n": "^4.0.1", "svelte-local-storage-store": "^0.6.4", "svelte-maplibre": "^0.9.13", "thumbhash": "^0.1.1" }, "volta": { - "node": "20.18.0" + "node": "22.11.0" } } diff --git a/web/src/lib/__mocks__/animate.mock.ts b/web/src/lib/__mocks__/animate.mock.ts new file mode 100644 index 0000000000000..5f0d367d86446 --- /dev/null +++ b/web/src/lib/__mocks__/animate.mock.ts @@ -0,0 +1,17 @@ +import { tick } from 'svelte'; +import { vi } from 'vitest'; + +export const getAnimateMock = () => + vi.fn().mockImplementation(() => { + let onfinish: (() => void) | null = null; + void tick().then(() => onfinish?.()); + + return { + set onfinish(fn: () => void) { + onfinish = fn; + }, + cancel() { + onfinish = null; + }, + }; + }); diff --git a/web/src/lib/actions/__test__/focus-trap-test.svelte b/web/src/lib/actions/__test__/focus-trap-test.svelte index 207c880cd9d8f..e1cb6fa4fba79 100644 --- a/web/src/lib/actions/__test__/focus-trap-test.svelte +++ b/web/src/lib/actions/__test__/focus-trap-test.svelte @@ -1,16 +1,20 @@ - + {#if show}
text - +
diff --git a/web/src/lib/actions/autogrow.ts b/web/src/lib/actions/autogrow.ts index ff80454ef3e84..0e6dec8e8180c 100644 --- a/web/src/lib/actions/autogrow.ts +++ b/web/src/lib/actions/autogrow.ts @@ -1,7 +1,19 @@ -export const autoGrowHeight = (textarea: HTMLTextAreaElement, height = 'auto') => { - if (!textarea) { - return; - } - textarea.style.height = height; - textarea.style.height = `${textarea.scrollHeight}px`; +import { tick } from 'svelte'; +import type { Action } from 'svelte/action'; + +type Parameters = { + height?: string; + value: string; // added to enable reactivity +}; + +export const autoGrowHeight: Action = (textarea, { height = 'auto' }) => { + const update = () => { + void tick().then(() => { + textarea.style.height = height; + textarea.style.height = `${textarea.scrollHeight}px`; + }); + }; + + update(); + return { update }; }; diff --git a/web/src/lib/actions/context-menu-navigation.ts b/web/src/lib/actions/context-menu-navigation.ts index 3b45e7fe527f0..89b7b76d24f4f 100644 --- a/web/src/lib/actions/context-menu-navigation.ts +++ b/web/src/lib/actions/context-menu-navigation.ts @@ -10,7 +10,7 @@ interface Options { /** * The container element that with direct children that should be navigated. */ - container: HTMLElement; + container?: HTMLElement; /** * Indicates if the dropdown is open. */ @@ -52,7 +52,11 @@ export const contextMenuNavigation: Action = (node, option await tick(); } - const children = Array.from(container?.children).filter((child) => child.tagName !== 'HR') as HTMLElement[]; + if (!container) { + return; + } + + const children = Array.from(container.children).filter((child) => child.tagName !== 'HR') as HTMLElement[]; if (children.length === 0) { return; } diff --git a/web/src/lib/actions/list-navigation.ts b/web/src/lib/actions/list-navigation.ts index 8f8ed62ed009e..cd4214f700570 100644 --- a/web/src/lib/actions/list-navigation.ts +++ b/web/src/lib/actions/list-navigation.ts @@ -6,8 +6,15 @@ import type { Action } from 'svelte/action'; * @param node Element which listens for keyboard events * @param container Element containing the list of elements */ -export const listNavigation: Action = (node, container: HTMLElement) => { +export const listNavigation: Action = ( + node: HTMLElement, + container?: HTMLElement, +) => { const moveFocus = (direction: 'up' | 'down') => { + if (!container) { + return; + } + const children = Array.from(container?.children); if (children.length === 0) { return; diff --git a/web/src/lib/components/admin-page/delete-confirm-dialogue.svelte b/web/src/lib/components/admin-page/delete-confirm-dialogue.svelte index a2fbbe787a1e9..6eb603263ecec 100644 --- a/web/src/lib/components/admin-page/delete-confirm-dialogue.svelte +++ b/web/src/lib/components/admin-page/delete-confirm-dialogue.svelte @@ -7,13 +7,17 @@ import { deleteUserAdmin, type UserResponseDto } from '@immich/sdk'; import { t } from 'svelte-i18n'; - export let user: UserResponseDto; - export let onSuccess: () => void; - export let onFail: () => void; - export let onCancel: () => void; + interface Props { + user: UserResponseDto; + onSuccess: () => void; + onFail: () => void; + onCancel: () => void; + } - let forceDelete = false; - let deleteButtonDisabled = false; + let { user, onSuccess, onFail, onCancel }: Props = $props(); + + let forceDelete = $state(false); + let deleteButtonDisabled = $state(false); let userIdInput: string = ''; const handleDeleteUser = async () => { @@ -47,12 +51,14 @@ {onCancel} disabled={deleteButtonDisabled} > - + {#snippet promptSnippet()}
{#if forceDelete}

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

{:else} @@ -60,9 +66,10 @@ - {message} + {#snippet children({ message })} + {message} + {/snippet}

{/if} @@ -73,7 +80,7 @@ label={$t('admin.user_delete_immediately_checkbox')} labelClass="text-sm dark:text-immich-dark-fg" bind:checked={forceDelete} - on:change={() => { + onchange={() => { deleteButtonDisabled = forceDelete; }} /> @@ -92,9 +99,9 @@ aria-describedby="confirm-user-desc" name="confirm-user-id" type="text" - on:input={handleConfirm} + oninput={handleConfirm} /> {/if}
-
+ {/snippet} diff --git a/web/src/lib/components/admin-page/jobs/job-tile-button.svelte b/web/src/lib/components/admin-page/jobs/job-tile-button.svelte index 69d3706230de9..f71d8a3e447af 100644 --- a/web/src/lib/components/admin-page/jobs/job-tile-button.svelte +++ b/web/src/lib/components/admin-page/jobs/job-tile-button.svelte @@ -1,10 +1,18 @@ -
- + {@render children?.()}
diff --git a/web/src/lib/components/admin-page/jobs/job-tile.svelte b/web/src/lib/components/admin-page/jobs/job-tile.svelte index b0af3a710f36f..0e39647c75974 100644 --- a/web/src/lib/components/admin-page/jobs/job-tile.svelte +++ b/web/src/lib/components/admin-page/jobs/job-tile.svelte @@ -14,27 +14,42 @@ mdiPlay, mdiSelectionSearch, } from '@mdi/js'; - import { type ComponentType } from 'svelte'; + import { type Component } from 'svelte'; import { t } from 'svelte-i18n'; import JobTileButton from './job-tile-button.svelte'; import JobTileStatus from './job-tile-status.svelte'; - export let title: string; - export let subtitle: string | undefined; - export let description: ComponentType | undefined; - export let jobCounts: JobCountsDto; - export let queueStatus: QueueStatusDto; - export let icon: string; - export let disabled = false; + interface Props { + title: string; + subtitle: string | undefined; + description: Component | undefined; + jobCounts: JobCountsDto; + queueStatus: QueueStatusDto; + icon: string; + disabled?: boolean; + allText: string | undefined; + refreshText: string | undefined; + missingText: string; + onCommand: (command: JobCommandDto) => void; + } - export let allText: string | undefined; - export let refreshText: string | undefined; - export let missingText: string; - export let onCommand: (command: JobCommandDto) => void; + let { + title, + subtitle, + description, + jobCounts, + queueStatus, + icon, + disabled = false, + allText, + refreshText, + missingText, + onCommand, + }: Props = $props(); - $: waitingCount = jobCounts.waiting + jobCounts.paused + jobCounts.delayed; - $: isIdle = !queueStatus.isActive && !queueStatus.isPaused; - $: multipleButtons = allText || refreshText; + let waitingCount = $derived(jobCounts.waiting + jobCounts.paused + jobCounts.delayed); + let isIdle = $derived(!queueStatus.isActive && !queueStatus.isPaused); + let multipleButtons = $derived(allText || refreshText); const commonClasses = 'flex place-items-center justify-between w-full py-2 sm:py-4 pr-4 pl-6'; @@ -67,7 +82,7 @@ title={$t('clear_message')} size="12" padding="1" - on:click={() => onCommand({ command: JobCommand.ClearFailed, force: false })} + onclick={() => onCommand({ command: JobCommand.ClearFailed, force: false })} />
@@ -87,8 +102,9 @@ {/if} {#if description} + {@const SvelteComponent = description}
- +
{/if} @@ -118,7 +134,7 @@ onCommand({ command: JobCommand.Start, force: false })} + onClick={() => onCommand({ command: JobCommand.Start, force: false })} > {$t('disabled').toUpperCase()} @@ -127,20 +143,20 @@ {#if !disabled && !isIdle} {#if waitingCount > 0} - onCommand({ command: JobCommand.Empty, force: false })}> + onCommand({ command: JobCommand.Empty, force: false })}> {$t('clear').toUpperCase()} {/if} {#if queueStatus.isPaused} {@const size = waitingCount > 0 ? '24' : '48'} - onCommand({ command: JobCommand.Resume, force: false })}> + onCommand({ command: JobCommand.Resume, force: false })}> {$t('resume').toUpperCase()} {:else} - onCommand({ command: JobCommand.Pause, force: false })}> + onCommand({ command: JobCommand.Pause, force: false })}> {$t('pause').toUpperCase()} @@ -149,25 +165,25 @@ {#if !disabled && multipleButtons && isIdle} {#if allText} - onCommand({ command: JobCommand.Start, force: true })}> + onCommand({ command: JobCommand.Start, force: true })}> {allText} {/if} {#if refreshText} - onCommand({ command: JobCommand.Start, force: undefined })}> + onCommand({ command: JobCommand.Start, force: undefined })}> {refreshText} {/if} - onCommand({ command: JobCommand.Start, force: false })}> + onCommand({ command: JobCommand.Start, force: false })}> {missingText} {/if} {#if !disabled && !multipleButtons && isIdle} - onCommand({ command: JobCommand.Start, force: false })}> + onCommand({ command: JobCommand.Start, force: false })}> {$t('start').toUpperCase()} diff --git a/web/src/lib/components/admin-page/jobs/jobs-panel.svelte b/web/src/lib/components/admin-page/jobs/jobs-panel.svelte index 8702a1e933fa4..9b4f3ffdd646a 100644 --- a/web/src/lib/components/admin-page/jobs/jobs-panel.svelte +++ b/web/src/lib/components/admin-page/jobs/jobs-panel.svelte @@ -19,18 +19,22 @@ mdiTagFaces, mdiVideo, } from '@mdi/js'; - import type { ComponentType } from 'svelte'; + import type { Component } from 'svelte'; import JobTile from './job-tile.svelte'; import StorageMigrationDescription from './storage-migration-description.svelte'; import { dialogController } from '$lib/components/shared-components/dialog/dialog'; import { t } from 'svelte-i18n'; - export let jobs: AllJobStatusResponseDto; + interface Props { + jobs: AllJobStatusResponseDto; + } + + let { jobs = $bindable() }: Props = $props(); interface JobDetails { title: string; subtitle?: string; - description?: ComponentType; + description?: Component; allText?: string; refreshText?: string; missingText: string; @@ -56,7 +60,7 @@ await handleCommand(jobId, dto); }; - $: jobDetails = >>{ + let jobDetails: Partial> = { [JobName.ThumbnailGeneration]: { icon: mdiFileJpgBox, title: $getJobName(JobName.ThumbnailGeneration), @@ -141,7 +145,8 @@ missingText: $t('missing'), }, }; - $: jobList = Object.entries(jobDetails) as [JobName, JobDetails][]; + + let jobList = Object.entries(jobDetails) as [JobName, JobDetails][]; async function handleCommand(jobId: JobName, jobCommand: JobCommandDto) { const title = jobDetails[jobId]?.title; diff --git a/web/src/lib/components/admin-page/jobs/storage-migration-description.svelte b/web/src/lib/components/admin-page/jobs/storage-migration-description.svelte index 8a74d2c5ad0be..b47df1daaec86 100644 --- a/web/src/lib/components/admin-page/jobs/storage-migration-description.svelte +++ b/web/src/lib/components/admin-page/jobs/storage-migration-description.svelte @@ -7,12 +7,13 @@ - - {message} - + {#snippet children({ message })} + + {message} + + {/snippet} diff --git a/web/src/lib/components/admin-page/restore-dialogue.svelte b/web/src/lib/components/admin-page/restore-dialogue.svelte index 25afbc6d4b9b6..a72ada2ca5d88 100644 --- a/web/src/lib/components/admin-page/restore-dialogue.svelte +++ b/web/src/lib/components/admin-page/restore-dialogue.svelte @@ -5,10 +5,14 @@ import { restoreUserAdmin, type UserResponseDto } from '@immich/sdk'; import { t } from 'svelte-i18n'; - export let user: UserResponseDto; - export let onSuccess: () => void; - export let onFail: () => void; - export let onCancel: () => void; + interface Props { + user: UserResponseDto; + onSuccess: () => void; + onFail: () => void; + onCancel: () => void; + } + + let { user, onSuccess, onFail, onCancel }: Props = $props(); const handleRestoreUser = async () => { try { @@ -32,11 +36,13 @@ onConfirm={handleRestoreUser} {onCancel} > - + {#snippet promptSnippet()}

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

-
+ {/snippet} diff --git a/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte b/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte index 35afc0962d414..bb288511accf7 100644 --- a/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte +++ b/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte @@ -7,14 +7,22 @@ import StatsCard from './stats-card.svelte'; import { t } from 'svelte-i18n'; - export let stats: ServerStatsResponseDto = { - photos: 0, - videos: 0, - usage: 0, - usageByUser: [], - }; + interface Props { + stats?: ServerStatsResponseDto; + } + + let { + stats = { + photos: 0, + videos: 0, + usage: 0, + usagePhotos: 0, + usageVideos: 0, + usageByUser: [], + }, + }: Props = $props(); - $: zeros = (value: number) => { + const zeros = (value: number) => { const maxLength = 13; const valueLength = value.toString().length; const zeroLength = maxLength - valueLength; @@ -23,7 +31,7 @@ }; const TiB = 1024 ** 4; - $: [statsUsage, statsUsageUnit] = getBytesWithUnit(stats.usage, stats.usage > TiB ? 2 : 0); + let [statsUsage, statsUsageUnit] = $derived(getBytesWithUnit(stats.usage, stats.usage > TiB ? 2 : 0));
@@ -99,8 +107,12 @@ class="flex h-[50px] w-full place-items-center text-center odd:bg-immich-gray even:bg-immich-bg odd:dark:bg-immich-dark-gray/75 even:dark:bg-immich-dark-gray/50" > {user.userName} - {user.photos.toLocaleString($locale)} - {user.videos.toLocaleString($locale)} + {user.photos.toLocaleString($locale)} ({getByteUnitString(user.usagePhotos, $locale, 0)}) + {user.videos.toLocaleString($locale)} ({getByteUnitString(user.usageVideos, $locale, 0)}) {getByteUnitString(user.usage, $locale, 0)} {#if user.quotaSizeInBytes} diff --git a/web/src/lib/components/admin-page/server-stats/stats-card.svelte b/web/src/lib/components/admin-page/server-stats/stats-card.svelte index 31baa0afdd780..14d32c055f23a 100644 --- a/web/src/lib/components/admin-page/server-stats/stats-card.svelte +++ b/web/src/lib/components/admin-page/server-stats/stats-card.svelte @@ -2,18 +2,22 @@ import Icon from '$lib/components/elements/icon.svelte'; import { ByteUnit } from '$lib/utils/byte-units'; - export let icon: string; - export let title: string; - export let value: number; - export let unit: ByteUnit | undefined = undefined; + interface Props { + icon: string; + title: string; + value: number; + unit?: ByteUnit | undefined; + } - $: zeros = () => { + let { icon, title, value, unit = undefined }: Props = $props(); + + const zeros = $derived(() => { const maxLength = 13; const valueLength = value.toString().length; const zeroLength = maxLength - valueLength; return '0'.repeat(zeroLength); - }; + });
diff --git a/web/src/lib/components/admin-page/settings/admin-settings.svelte b/web/src/lib/components/admin-page/settings/admin-settings.svelte index 19a8580d6bb90..199db0b57112a 100644 --- a/web/src/lib/components/admin-page/settings/admin-settings.svelte +++ b/web/src/lib/components/admin-page/settings/admin-settings.svelte @@ -1,5 +1,3 @@ - - {#if savedConfig && defaultConfig} - + {@render children({ savedConfig, defaultConfig })} {/if} diff --git a/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte b/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte index 9b0e4b32706b5..7f94dfa253a4d 100644 --- a/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte +++ b/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte @@ -2,9 +2,7 @@ import ConfirmDialog from '$lib/components/shared-components/dialog/confirm-dialog.svelte'; import SettingAccordion from '$lib/components/shared-components/settings/setting-accordion.svelte'; import SettingButtonsRow from '$lib/components/shared-components/settings/setting-buttons-row.svelte'; - import SettingInputField, { - SettingInputFieldType, - } from '$lib/components/shared-components/settings/setting-input-field.svelte'; + import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte'; import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte'; import { type SystemConfigDto } from '@immich/sdk'; import { isEqual } from 'lodash-es'; @@ -12,15 +10,20 @@ import type { SettingsResetEvent, SettingsSaveEvent } from '../admin-settings'; import { t } from 'svelte-i18n'; import FormatMessage from '$lib/components/i18n/format-message.svelte'; + import { SettingInputFieldType } from '$lib/constants'; - export let savedConfig: SystemConfigDto; - export let defaultConfig: SystemConfigDto; - export let config: SystemConfigDto; // this is the config that is being edited - export let disabled = false; - export let onReset: SettingsResetEvent; - export let onSave: SettingsSaveEvent; + interface Props { + savedConfig: SystemConfigDto; + defaultConfig: SystemConfigDto; + config: SystemConfigDto; + disabled?: boolean; + onReset: SettingsResetEvent; + onSave: SettingsSaveEvent; + } - let isConfirmOpen = false; + let { savedConfig, defaultConfig, config = $bindable(), disabled = false, onReset, onSave }: Props = $props(); + + let isConfirmOpen = $state(false); const handleToggleOverride = () => { // click runs before bind @@ -48,29 +51,31 @@ onCancel={() => (isConfirmOpen = false)} onConfirm={() => handleSave(true)} > - + {#snippet promptSnippet()}

{$t('admin.authentication_settings_disable_all')}

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

-
+ {/snippet} {/if}
-
+ e.preventDefault()}>

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

@@ -147,7 +154,7 @@ handleToggleOverride()} + onToggle={() => handleToggleOverride()} bind:checked={config.oauth.mobileOverrideEnabled} /> diff --git a/web/src/lib/components/admin-page/settings/backup-settings/backup-settings.svelte b/web/src/lib/components/admin-page/settings/backup-settings/backup-settings.svelte new file mode 100644 index 0000000000000..3ec477e29c645 --- /dev/null +++ b/web/src/lib/components/admin-page/settings/backup-settings/backup-settings.svelte @@ -0,0 +1,100 @@ + + +
+
+ +
+ + + + + + {#snippet descriptionSnippet()} +

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

+ {/snippet} +
+ + + + onReset({ ...options, configKeys: ['backup'] })} + onSave={() => onSave({ backup: config.backup })} + showResetToDefault={!isEqual(savedConfig.backup, defaultConfig.backup)} + {disabled} + /> +
+ +
+
diff --git a/web/src/lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte b/web/src/lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte index 42cc004c52d21..702ec1c1710b9 100644 --- a/web/src/lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte +++ b/web/src/lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte @@ -15,44 +15,53 @@ import { fade } from 'svelte/transition'; import type { SettingsResetEvent, SettingsSaveEvent } from '../admin-settings'; import SettingAccordion from '$lib/components/shared-components/settings/setting-accordion.svelte'; - import SettingInputField, { - SettingInputFieldType, - } from '$lib/components/shared-components/settings/setting-input-field.svelte'; + import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte'; import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte'; import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte'; import SettingCheckboxes from '$lib/components/shared-components/settings/setting-checkboxes.svelte'; import SettingButtonsRow from '$lib/components/shared-components/settings/setting-buttons-row.svelte'; import { t } from 'svelte-i18n'; import FormatMessage from '$lib/components/i18n/format-message.svelte'; + import { SettingInputFieldType } from '$lib/constants'; - export let savedConfig: SystemConfigDto; - export let defaultConfig: SystemConfigDto; - export let config: SystemConfigDto; // this is the config that is being edited - export let disabled = false; - export let onReset: SettingsResetEvent; - export let onSave: SettingsSaveEvent; + interface Props { + savedConfig: SystemConfigDto; + defaultConfig: SystemConfigDto; + config: SystemConfigDto; + disabled?: boolean; + onReset: SettingsResetEvent; + onSave: SettingsSaveEvent; + } + + let { savedConfig, defaultConfig, config = $bindable(), disabled = false, onReset, onSave }: Props = $props(); + + const onsubmit = (event: Event) => { + event.preventDefault(); + };
-
+

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

@@ -60,7 +69,7 @@ inputType={SettingInputFieldType.NUMBER} {disabled} label={$t('admin.transcoding_constant_rate_factor')} - desc={$t('admin.transcoding_constant_rate_factor_description')} + description={$t('admin.transcoding_constant_rate_factor_description')} bind:value={config.ffmpeg.crf} required={true} isEdited={config.ffmpeg.crf !== savedConfig.ffmpeg.crf} @@ -186,7 +195,7 @@ inputType={SettingInputFieldType.TEXT} {disabled} label={$t('admin.transcoding_max_bitrate')} - desc={$t('admin.transcoding_max_bitrate_description')} + description={$t('admin.transcoding_max_bitrate_description')} bind:value={config.ffmpeg.maxBitrate} isEdited={config.ffmpeg.maxBitrate !== savedConfig.ffmpeg.maxBitrate} /> @@ -195,7 +204,7 @@ inputType={SettingInputFieldType.NUMBER} {disabled} label={$t('admin.transcoding_threads')} - desc={$t('admin.transcoding_threads_description')} + description={$t('admin.transcoding_threads_description')} bind:value={config.ffmpeg.threads} isEdited={config.ffmpeg.threads !== savedConfig.ffmpeg.threads} /> @@ -329,7 +338,7 @@
- - { + event.preventDefault(); + };
- +
{ + event.preventDefault(); + };
- + {#each jobNames as jobName}
{#if isSystemConfigJobDto(jobName)} @@ -46,7 +53,7 @@ inputType={SettingInputFieldType.NUMBER} {disabled} label={$t('admin.job_concurrency', { values: { job: $getJobName(jobName) } })} - desc="" + description="" bind:value={config.job[jobName].concurrency} required={true} isEdited={!(config.job[jobName].concurrency == savedConfig.job[jobName].concurrency)} @@ -55,7 +62,7 @@ { + event.preventDefault(); + };
- +
-
- - -
+ - + {#snippet descriptionSnippet()}

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

-
+ {/snippet}
diff --git a/web/src/lib/components/admin-page/settings/logging-settings/logging-settings.svelte b/web/src/lib/components/admin-page/settings/logging-settings/logging-settings.svelte index 6e71ba926c79f..29a1c65162359 100644 --- a/web/src/lib/components/admin-page/settings/logging-settings/logging-settings.svelte +++ b/web/src/lib/components/admin-page/settings/logging-settings/logging-settings.svelte @@ -8,17 +8,25 @@ import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte'; import { t } from 'svelte-i18n'; - export let savedConfig: SystemConfigDto; - export let defaultConfig: SystemConfigDto; - export let config: SystemConfigDto; // this is the config that is being edited - export let disabled = false; - export let onReset: SettingsResetEvent; - export let onSave: SettingsSaveEvent; + interface Props { + savedConfig: SystemConfigDto; + defaultConfig: SystemConfigDto; + config: SystemConfigDto; + disabled?: boolean; + onReset: SettingsResetEvent; + onSave: SettingsSaveEvent; + } + + let { savedConfig, defaultConfig, config = $bindable(), disabled = false, onReset, onSave }: Props = $props(); + + const onsubmit = (event: Event) => { + event.preventDefault(); + };
- +
{ + event.preventDefault(); + };
- +
-

- - {message} - -

+ {#snippet descriptionSnippet()} +

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

+ {/snippet}
@@ -100,7 +111,7 @@ step="0.0005" min={0.001} max={0.1} - desc={$t('admin.machine_learning_max_detection_distance_description')} + description={$t('admin.machine_learning_max_detection_distance_description')} disabled={disabled || !$featureFlags.duplicateDetection} isEdited={config.machineLearning.duplicateDetection.maxDistance !== savedConfig.machineLearning.duplicateDetection.maxDistance} @@ -142,7 +153,7 @@ { + event.preventDefault(); + };
- +
@@ -38,7 +45,7 @@ - + {#snippet subtitleSnippet()}

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

-
+ {/snippet}
{ + event.preventDefault(); + };
- +
{ + event.preventDefault(); + };
- +
{ if (isSending) { @@ -65,11 +68,15 @@ isSending = false; } }; + + const onsubmit = (event: Event) => { + event.preventDefault(); + };
- +
@@ -85,7 +92,7 @@ inputType={SettingInputFieldType.TEXT} required label={$t('host')} - desc={$t('admin.notification_email_host_description')} + description={$t('admin.notification_email_host_description')} disabled={disabled || !config.notifications.smtp.enabled} bind:value={config.notifications.smtp.transport.host} isEdited={config.notifications.smtp.transport.host !== savedConfig.notifications.smtp.transport.host} @@ -95,7 +102,7 @@ inputType={SettingInputFieldType.NUMBER} required label={$t('port')} - desc={$t('admin.notification_email_port_description')} + description={$t('admin.notification_email_port_description')} disabled={disabled || !config.notifications.smtp.enabled} bind:value={config.notifications.smtp.transport.port} isEdited={config.notifications.smtp.transport.port !== savedConfig.notifications.smtp.transport.port} @@ -104,7 +111,7 @@
-
{/if} diff --git a/web/src/lib/components/album-page/album-cover.svelte b/web/src/lib/components/album-page/album-cover.svelte index d0444f35990e2..3f71bbe632c9f 100644 --- a/web/src/lib/components/album-page/album-cover.svelte +++ b/web/src/lib/components/album-page/album-cover.svelte @@ -5,13 +5,18 @@ import AssetCover from '$lib/components/sharedlinks-page/covers/asset-cover.svelte'; import { t } from 'svelte-i18n'; - export let album: AlbumResponseDto; - export let preload = false; - let className = ''; - export { className as class }; + interface Props { + album: AlbumResponseDto; + preload?: boolean; + class?: string; + } - $: alt = album.albumName || $t('unnamed_album'); - $: thumbnailUrl = album.albumThumbnailAssetId ? getAssetThumbnailUrl({ id: album.albumThumbnailAssetId }) : null; + let { album, preload = false, class: className = '' }: Props = $props(); + + let alt = $derived(album.albumName || $t('unnamed_album')); + let thumbnailUrl = $derived( + album.albumThumbnailAssetId ? getAssetThumbnailUrl({ id: album.albumThumbnailAssetId }) : null, + ); {#if thumbnailUrl} diff --git a/web/src/lib/components/album-page/album-description.svelte b/web/src/lib/components/album-page/album-description.svelte index b3ad688a30512..46b424f93ae0d 100644 --- a/web/src/lib/components/album-page/album-description.svelte +++ b/web/src/lib/components/album-page/album-description.svelte @@ -4,9 +4,13 @@ import AutogrowTextarea from '$lib/components/shared-components/autogrow-textarea.svelte'; import { t } from 'svelte-i18n'; - export let id: string; - export let description: string; - export let isOwned: boolean; + interface Props { + id: string; + description: string; + isOwned: boolean; + } + + let { id, description = $bindable(), isOwned }: Props = $props(); const handleUpdateDescription = async (newDescription: string) => { try { diff --git a/web/src/lib/components/album-page/album-options.svelte b/web/src/lib/components/album-page/album-options.svelte index 3ec1842757201..884de8c2a2b83 100644 --- a/web/src/lib/components/album-page/album-options.svelte +++ b/web/src/lib/components/album-page/album-options.svelte @@ -23,24 +23,38 @@ import { notificationController, NotificationType } from '../shared-components/notification/notification'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; - export let album: AlbumResponseDto; - export let order: AssetOrder | undefined; - export let user: UserResponseDto; // Declare user as a prop - export let onChangeOrder: (order: AssetOrder) => void; - export let onClose: () => void; - export let onToggleEnabledActivity: () => void; - export let onShowSelectSharedUser: () => void; - export let onRemove: (userId: string) => void; - export let onRefreshAlbum: () => void; + interface Props { + album: AlbumResponseDto; + order: AssetOrder | undefined; + user: UserResponseDto; + onChangeOrder: (order: AssetOrder) => void; + onClose: () => void; + onToggleEnabledActivity: () => void; + onShowSelectSharedUser: () => void; + onRemove: (userId: string) => void; + onRefreshAlbum: () => void; + } - let selectedRemoveUser: UserResponseDto | null = null; + let { + album, + order, + user, + onChangeOrder, + onClose, + onToggleEnabledActivity, + onShowSelectSharedUser, + onRemove, + onRefreshAlbum, + }: Props = $props(); + + let selectedRemoveUser: UserResponseDto | null = $state(null); const options: Record = { [AssetOrder.Asc]: { icon: mdiArrowUpThin, title: $t('oldest_first') }, [AssetOrder.Desc]: { icon: mdiArrowDownThin, title: $t('newest_first') }, }; - $: selectedOption = order ? options[order] : options[AssetOrder.Desc]; + let selectedOption = $derived(order ? options[order] : options[AssetOrder.Desc]); const handleToggle = async (returnedOption: RenderedOption): Promise => { if (selectedOption === returnedOption) { @@ -125,7 +139,7 @@
{$t('people').toUpperCase()}
-
- createAlbumAndRedirect()}> + createAlbumAndRedirect()}>
@@ -179,7 +164,7 @@