mirror of
https://github.com/immich-app/immich.git
synced 2025-12-10 01:10:21 +03:00
Compare commits
1 Commits
chore/tran
...
feat/versi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d209904db |
@@ -29,12 +29,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
||||
// https://github.com/devcontainers/features/issues/1466
|
||||
"moby": false
|
||||
}
|
||||
},
|
||||
"forwardPorts": [3000, 9231, 9230, 2283],
|
||||
"portsAttributes": {
|
||||
"3000": {
|
||||
|
||||
@@ -21,7 +21,6 @@ services:
|
||||
- app-node_modules:/usr/src/app/node_modules
|
||||
- sveltekit:/usr/src/app/web/.svelte-kit
|
||||
- coverage:/usr/src/app/web/coverage
|
||||
- ../plugins:/build/corePlugin
|
||||
immich-web:
|
||||
env_file: !reset []
|
||||
immich-machine-learning:
|
||||
|
||||
2
.github/.nvmrc
vendored
2
.github/.nvmrc
vendored
@@ -1 +1 @@
|
||||
24.11.1
|
||||
24.11.0
|
||||
|
||||
2
.github/package.json
vendored
2
.github/package.json
vendored
@@ -4,6 +4,6 @@
|
||||
"format:fix": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.7.4"
|
||||
"prettier": "^3.5.3"
|
||||
}
|
||||
}
|
||||
|
||||
34
.github/workflows/build-mobile.yml
vendored
34
.github/workflows/build-mobile.yml
vendored
@@ -96,7 +96,7 @@ jobs:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.sha }}
|
||||
persist-credentials: false
|
||||
@@ -165,7 +165,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Publish Android Artifact
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: release-apk-signed
|
||||
path: mobile/build/app/outputs/flutter-apk/*.apk
|
||||
@@ -188,13 +188,13 @@ jobs:
|
||||
needs: pre-job
|
||||
permissions:
|
||||
contents: read
|
||||
# Run on main branch or workflow_dispatch, or on PRs/other branches (build only, no upload)
|
||||
if: ${{ !github.event.pull_request.head.repo.fork && fromJSON(needs.pre-job.outputs.should_run).mobile == true }}
|
||||
# Run on main branch or workflow_dispatch
|
||||
if: ${{ !github.event.pull_request.head.repo.fork && fromJSON(needs.pre-job.outputs.should_run).mobile == true && github.ref == 'refs/heads/main' }}
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.sha }}
|
||||
persist-credentials: false
|
||||
@@ -222,7 +222,6 @@ jobs:
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.3'
|
||||
bundler-cache: true
|
||||
working-directory: ./mobile/ios
|
||||
|
||||
- name: Install CocoaPods dependencies
|
||||
@@ -230,6 +229,13 @@ jobs:
|
||||
run: |
|
||||
pod install
|
||||
|
||||
- name: Install Fastlane
|
||||
working-directory: ./mobile/ios
|
||||
run: |
|
||||
gem install bundler
|
||||
bundle config set --local path 'vendor/bundle'
|
||||
bundle install
|
||||
|
||||
- name: Create API Key
|
||||
env:
|
||||
API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
||||
@@ -297,20 +303,12 @@ jobs:
|
||||
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
||||
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
|
||||
ENVIRONMENT: ${{ inputs.environment || 'development' }}
|
||||
BUNDLE_ID_SUFFIX: ${{ inputs.environment == 'production' && '' || 'development' }}
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
working-directory: ./mobile/ios
|
||||
run: |
|
||||
# Only upload to TestFlight on main branch
|
||||
if [[ "$GITHUB_REF" == "refs/heads/main" ]]; then
|
||||
if [[ "$ENVIRONMENT" == "development" ]]; then
|
||||
bundle exec fastlane gha_testflight_dev
|
||||
else
|
||||
bundle exec fastlane gha_release_prod
|
||||
fi
|
||||
if [[ "$ENVIRONMENT" == "development" ]]; then
|
||||
bundle exec fastlane gha_testflight_dev
|
||||
else
|
||||
# Build only, no TestFlight upload for non-main branches
|
||||
bundle exec fastlane gha_build_only
|
||||
bundle exec fastlane gha_release_prod
|
||||
fi
|
||||
|
||||
- name: Clean up keychain
|
||||
@@ -319,7 +317,7 @@ jobs:
|
||||
security delete-keychain build.keychain || true
|
||||
|
||||
- name: Upload IPA artifact
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: ios-release-ipa
|
||||
path: mobile/ios/Runner.ipa
|
||||
|
||||
2
.github/workflows/cache-cleanup.yml
vendored
2
.github/workflows/cache-cleanup.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
8
.github/workflows/cli.yml
vendored
8
.github/workflows/cli.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -78,13 +78,13 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
|
||||
- name: Generate docker image tags
|
||||
id: metadata
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
with:
|
||||
flavor: |
|
||||
latest=false
|
||||
|
||||
2
.github/workflows/close-duplicates.yml
vendored
2
.github/workflows/close-duplicates.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
needs: [get_body, should_run]
|
||||
if: ${{ needs.should_run.outputs.should_run == 'true' }}
|
||||
container:
|
||||
image: ghcr.io/immich-app/mdq:main@sha256:237cdae7783609c96f18037a513d38088713cf4a2e493a3aa136d0c45490749a
|
||||
image: ghcr.io/immich-app/mdq:main@sha256:6b8450bfc06770af1af66bce9bf2ced7d1d9b90df1a59fc4c83a17777a9f6723
|
||||
outputs:
|
||||
checked: ${{ steps.get_checkbox.outputs.checked }}
|
||||
steps:
|
||||
|
||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -50,14 +50,14 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||
uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||
uses: github/codeql-action/autobuild@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@@ -83,6 +83,6 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||
uses: github/codeql-action/analyze@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
||||
with:
|
||||
category: '/language:${{matrix.language}}'
|
||||
|
||||
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
@@ -132,7 +132,7 @@ jobs:
|
||||
suffixes: '-rocm'
|
||||
platforms: linux/amd64
|
||||
runner-mapping: '{"linux/amd64": "mich"}'
|
||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@0477486d82313fba68f7c82c034120a4b8981297 # multi-runner-build-workflow-v2.1.0
|
||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@47a2ee86898ccff51592d6572391fb1abcd7f782 # multi-runner-build-workflow-v2.0.1
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
name: Build and Push Server
|
||||
needs: pre-job
|
||||
if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }}
|
||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@0477486d82313fba68f7c82c034120a4b8981297 # multi-runner-build-workflow-v2.1.0
|
||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@47a2ee86898ccff51592d6572391fb1abcd7f782 # multi-runner-build-workflow-v2.0.1
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
4
.github/workflows/docs-build.yml
vendored
4
.github/workflows/docs-build.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
run: pnpm build
|
||||
|
||||
- name: Upload build output
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: docs-build-output
|
||||
path: docs/build/
|
||||
|
||||
2
.github/workflows/docs-deploy.yml
vendored
2
.github/workflows/docs-deploy.yml
vendored
@@ -125,7 +125,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
2
.github/workflows/docs-destroy.yml
vendored
2
.github/workflows/docs-destroy.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
4
.github/workflows/fix-format.yml
vendored
4
.github/workflows/fix-format.yml
vendored
@@ -16,13 +16,13 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
|
||||
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
|
||||
with:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
2
.github/workflows/merge-translations.yml
vendored
2
.github/workflows/merge-translations.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Generate a token
|
||||
id: generate_token
|
||||
if: ${{ inputs.skip != true }}
|
||||
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
|
||||
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
|
||||
with:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
14
.github/workflows/prepare-release.yml
vendored
14
.github/workflows/prepare-release.yml
vendored
@@ -49,20 +49,20 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
|
||||
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
|
||||
with:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
uses: astral-sh/setup-uv@2ddd2b9cb38ad8efd50337e8ab201519a34c9f24 # v7.1.1
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
@@ -126,25 +126,25 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
|
||||
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
|
||||
with:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download APK
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
with:
|
||||
name: release-apk-signed
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
- name: Create draft release
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||
with:
|
||||
draft: true
|
||||
tag_name: ${{ env.IMMICH_VERSION }}
|
||||
|
||||
170
.github/workflows/release-pr.yml
vendored
170
.github/workflows/release-pr.yml
vendored
@@ -1,170 +0,0 @@
|
||||
name: Manage release PR
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
|
||||
with:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
|
||||
- name: Determine release type
|
||||
id: bump-type
|
||||
uses: ietf-tools/semver-action@c90370b2958652d71c06a3484129a4d423a6d8a8 # v1.11.0
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
- name: Bump versions
|
||||
env:
|
||||
TYPE: ${{ steps.bump-type.outputs.bump }}
|
||||
run: |
|
||||
if [ "$TYPE" == "none" ]; then
|
||||
exit 1 # TODO: Is there a cleaner way to abort the workflow?
|
||||
fi
|
||||
misc/release/pump-version.sh -s $TYPE -m true
|
||||
|
||||
- name: Manage Outline release document
|
||||
id: outline
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
OUTLINE_API_KEY: ${{ secrets.OUTLINE_API_KEY }}
|
||||
NEXT_VERSION: ${{ steps.bump-type.outputs.next }}
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
const outlineKey = process.env.OUTLINE_API_KEY;
|
||||
const parentDocumentId = 'da856355-0844-43df-bd71-f8edce5382d9'
|
||||
const collectionId = 'e2910656-714c-4871-8721-447d9353bd73';
|
||||
const baseUrl = 'https://outline.immich.cloud';
|
||||
|
||||
const listResponse = await fetch(`${baseUrl}/api/documents.list`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${outlineKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ parentDocumentId })
|
||||
});
|
||||
|
||||
if (!listResponse.ok) {
|
||||
throw new Error(`Outline list failed: ${listResponse.statusText}`);
|
||||
}
|
||||
|
||||
const listData = await listResponse.json();
|
||||
const allDocuments = listData.data || [];
|
||||
|
||||
const document = allDocuments.find(doc => doc.title === 'next');
|
||||
|
||||
let documentId;
|
||||
let documentUrl;
|
||||
let documentText;
|
||||
|
||||
if (!document) {
|
||||
// Create new document
|
||||
console.log('No existing document found. Creating new one...');
|
||||
const notesTmpl = fs.readFileSync('misc/release/notes.tmpl', 'utf8');
|
||||
const createResponse = await fetch(`${baseUrl}/api/documents.create`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${outlineKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: 'next',
|
||||
text: notesTmpl,
|
||||
collectionId: collectionId,
|
||||
parentDocumentId: parentDocumentId,
|
||||
publish: true
|
||||
})
|
||||
});
|
||||
|
||||
if (!createResponse.ok) {
|
||||
throw new Error(`Failed to create document: ${createResponse.statusText}`);
|
||||
}
|
||||
|
||||
const createData = await createResponse.json();
|
||||
documentId = createData.data.id;
|
||||
const urlId = createData.data.urlId;
|
||||
documentUrl = `${baseUrl}/doc/next-${urlId}`;
|
||||
documentText = createData.data.text || '';
|
||||
console.log(`Created new document: ${documentUrl}`);
|
||||
} else {
|
||||
documentId = document.id;
|
||||
const docPath = document.url;
|
||||
documentUrl = `${baseUrl}${docPath}`;
|
||||
documentText = document.text || '';
|
||||
console.log(`Found existing document: ${documentUrl}`);
|
||||
}
|
||||
|
||||
// Generate GitHub release notes
|
||||
console.log('Generating GitHub release notes...');
|
||||
const releaseNotesResponse = await github.rest.repos.generateReleaseNotes({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
tag_name: `${process.env.NEXT_VERSION}`,
|
||||
});
|
||||
|
||||
// Combine the content
|
||||
const changelog = `
|
||||
# ${process.env.NEXT_VERSION}
|
||||
|
||||
${documentText}
|
||||
|
||||
${releaseNotesResponse.data.body}
|
||||
|
||||
---
|
||||
|
||||
`
|
||||
|
||||
const existingChangelog = fs.existsSync('CHANGELOG.md') ? fs.readFileSync('CHANGELOG.md', 'utf8') : '';
|
||||
fs.writeFileSync('CHANGELOG.md', changelog + existingChangelog, 'utf8');
|
||||
|
||||
core.setOutput('document_url', documentUrl);
|
||||
|
||||
- name: Create PR
|
||||
id: create-pr
|
||||
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
commit-message: 'chore: release ${{ steps.bump-type.outputs.next }}'
|
||||
title: 'chore: release ${{ steps.bump-type.outputs.next }}'
|
||||
body: 'Release notes: ${{ steps.outline.outputs.document_url }}'
|
||||
labels: 'changelog:skip'
|
||||
branch: 'release/next'
|
||||
draft: true
|
||||
148
.github/workflows/release.yml
vendored
148
.github/workflows/release.yml
vendored
@@ -1,148 +0,0 @@
|
||||
name: release.yml
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
paths:
|
||||
- CHANGELOG.md
|
||||
|
||||
jobs:
|
||||
# Maybe double check PR source branch?
|
||||
|
||||
merge_translations:
|
||||
uses: ./.github/workflows/merge-translations.yml
|
||||
permissions:
|
||||
pull-requests: write
|
||||
secrets:
|
||||
PUSH_O_MATIC_APP_ID: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
PUSH_O_MATIC_APP_KEY: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }}
|
||||
|
||||
build_mobile:
|
||||
uses: ./.github/workflows/build-mobile.yml
|
||||
needs: merge_translations
|
||||
permissions:
|
||||
contents: read
|
||||
secrets:
|
||||
KEY_JKS: ${{ secrets.KEY_JKS }}
|
||||
ALIAS: ${{ secrets.ALIAS }}
|
||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
|
||||
# iOS secrets
|
||||
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
||||
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
|
||||
APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
|
||||
IOS_CERTIFICATE_P12: ${{ secrets.IOS_CERTIFICATE_P12 }}
|
||||
IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
|
||||
IOS_PROVISIONING_PROFILE: ${{ secrets.IOS_PROVISIONING_PROFILE }}
|
||||
IOS_PROVISIONING_PROFILE_SHARE_EXTENSION: ${{ secrets.IOS_PROVISIONING_PROFILE_SHARE_EXTENSION }}misc/release/notes.tmpl
|
||||
IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION: ${{ secrets.IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION }}
|
||||
IOS_DEVELOPMENT_PROVISIONING_PROFILE: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE }}
|
||||
IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION }}
|
||||
IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION }}
|
||||
FASTLANE_TEAM_ID: ${{ secrets.FASTLANE_TEAM_ID }}
|
||||
with:
|
||||
ref: main
|
||||
environment: production
|
||||
|
||||
prepare_release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build_mobile
|
||||
permissions:
|
||||
actions: read # To download the app artifact
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
|
||||
with:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
persist-credentials: false
|
||||
ref: main
|
||||
|
||||
- name: Extract changelog
|
||||
id: changelog
|
||||
run: |
|
||||
CHANGELOG_PATH=$RUNNER_TEMP/changelog.md
|
||||
sed -n '1,/^---$/p' CHANGELOG.md | head -n -1 > $CHANGELOG_PATH
|
||||
echo "path=$CHANGELOG_PATH" >> $GITHUB_OUTPUT
|
||||
VERSION=$(sed -n 's/^# //p' $CHANGELOG_PATH)
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Download APK
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
name: release-apk-signed
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
- name: Create draft release
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
with:
|
||||
tag_name: ${{ steps.version.outputs.result }}
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
body_path: ${{ steps.changelog.outputs.path }}
|
||||
draft: true
|
||||
files: |
|
||||
docker/docker-compose.yml
|
||||
docker/example.env
|
||||
docker/hwaccel.ml.yml
|
||||
docker/hwaccel.transcoding.yml
|
||||
docker/prometheus.yml
|
||||
*.apk
|
||||
|
||||
- name: Rename Outline document
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
continue-on-error: true
|
||||
env:
|
||||
OUTLINE_API_KEY: ${{ secrets.OUTLINE_API_KEY }}
|
||||
VERSION: ${{ steps.changelog.outputs.version }}
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
const outlineKey = process.env.OUTLINE_API_KEY;
|
||||
const version = process.env.VERSION;
|
||||
const parentDocumentId = 'da856355-0844-43df-bd71-f8edce5382d9';
|
||||
const baseUrl = 'https://outline.immich.cloud';
|
||||
|
||||
const listResponse = await fetch(`${baseUrl}/api/documents.list`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${outlineKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ parentDocumentId })
|
||||
});
|
||||
|
||||
if (!listResponse.ok) {
|
||||
throw new Error(`Outline list failed: ${listResponse.statusText}`);
|
||||
}
|
||||
|
||||
const listData = await listResponse.json();
|
||||
const allDocuments = listData.data || [];
|
||||
const document = allDocuments.find(doc => doc.title === 'next');
|
||||
|
||||
if (document) {
|
||||
console.log(`Found document 'next', renaming to '${version}'...`);
|
||||
|
||||
const updateResponse = await fetch(`${baseUrl}/api/documents.update`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${outlineKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: document.id,
|
||||
title: version
|
||||
})
|
||||
});
|
||||
|
||||
if (!updateResponse.ok) {
|
||||
throw new Error(`Failed to rename document: ${updateResponse.statusText}`);
|
||||
}
|
||||
} else {
|
||||
console.log('No document titled "next" found to rename');
|
||||
}
|
||||
2
.github/workflows/sdk.yml
vendored
2
.github/workflows/sdk.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
2
.github/workflows/static_analysis.yml
vendored
2
.github/workflows/static_analysis.yml
vendored
@@ -55,7 +55,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
44
.github/workflows/test.yml
vendored
44
.github/workflows/test.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -114,7 +114,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -161,7 +161,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -203,7 +203,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -247,7 +247,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -285,7 +285,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -333,7 +333,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -379,7 +379,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: 'recursive'
|
||||
@@ -418,7 +418,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: 'recursive'
|
||||
@@ -473,7 +473,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: 'recursive'
|
||||
@@ -500,16 +500,8 @@ jobs:
|
||||
run: docker compose build
|
||||
if: ${{ !cancelled() }}
|
||||
- name: Run e2e tests (web)
|
||||
env:
|
||||
CI: true
|
||||
run: npx playwright test
|
||||
if: ${{ !cancelled() }}
|
||||
- name: Archive test results
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
if: success() || failure()
|
||||
with:
|
||||
name: e2e-web-test-results-${{ matrix.runner }}
|
||||
path: e2e/playwright-report/
|
||||
success-check-e2e:
|
||||
name: End-to-End Tests Success
|
||||
needs: [e2e-tests-server-cli, e2e-tests-web]
|
||||
@@ -534,7 +526,7 @@ jobs:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -566,13 +558,13 @@ jobs:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
uses: astral-sh/setup-uv@2ddd2b9cb38ad8efd50337e8ab201519a34c9f24 # v7.1.1
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
# TODO: add caching when supported (https://github.com/actions/setup-python/pull/818)
|
||||
# with:
|
||||
# python-version: 3.11
|
||||
@@ -610,7 +602,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -639,7 +631,7 @@ jobs:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -661,7 +653,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -723,7 +715,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
3
.github/workflows/weblate-lock.yml
vendored
3
.github/workflows/weblate-lock.yml
vendored
@@ -36,7 +36,8 @@ jobs:
|
||||
github-token: ${{ steps.token.outputs.token }}
|
||||
filters: |
|
||||
i18n:
|
||||
- modified: 'i18n/!(en)**\.json'
|
||||
- 'i18n/!(en)**\.json'
|
||||
exclude-branches: 'chore/translations'
|
||||
skip-force-logic: 'true'
|
||||
|
||||
enforce-lock:
|
||||
|
||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -52,7 +52,7 @@
|
||||
},
|
||||
"cSpell.words": ["immich"],
|
||||
"editor.formatOnSave": true,
|
||||
"eslint.validate": ["javascript", "typescript", "svelte"],
|
||||
"eslint.validate": ["javascript", "svelte"],
|
||||
"explorer.fileNesting.enabled": true,
|
||||
"explorer.fileNesting.patterns": {
|
||||
"*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart",
|
||||
|
||||
3
Makefile
3
Makefile
@@ -17,9 +17,6 @@ dev-docs:
|
||||
e2e:
|
||||
@trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.yml up --remove-orphans
|
||||
|
||||
e2e-dev:
|
||||
@trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.dev.yml up --remove-orphans
|
||||
|
||||
e2e-update:
|
||||
@trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.yml up --build -V --remove-orphans
|
||||
|
||||
|
||||
10
README.md
10
README.md
@@ -118,16 +118,16 @@ Read more about translations [here](https://docs.immich.app/developer/translatio
|
||||
|
||||
## Star history
|
||||
|
||||
<a href="https://star-history.com/#immich-app/immich&type=date&legend=top-left">
|
||||
<a href="https://star-history.com/#immich-app/immich&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=immich-app/immich&type=date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=immich-app/immich&type=date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=immich-app/immich&type=date" width="100%" />
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=immich-app/immich&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=immich-app/immich&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=immich-app/immich&type=Date" width="100%" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/immich-app/immich/graphs/contributors">
|
||||
<a href="https://github.com/alextran1502/immich/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=immich-app/immich" width="100%"/>
|
||||
</a>
|
||||
|
||||
@@ -1 +1 @@
|
||||
24.11.1
|
||||
24.11.0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:24.1.0-alpine3.20@sha256:8fe019e0d57dbdce5f5c27c0b63d2775cf34b00e3755a7dea969802d7e0c2b25 AS core
|
||||
FROM node:22.16.0-alpine3.20@sha256:2289fb1fba0f4633b08ec47b94a89c7e20b829fc5679f9b7b298eaa2f1ed8b7e AS core
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY package* pnpm* .pnpmfile.cjs ./
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.103",
|
||||
"version": "2.2.101",
|
||||
"description": "Command Line Interface (CLI) for Immich",
|
||||
"type": "module",
|
||||
"exports": "./dist/index.js",
|
||||
@@ -20,7 +20,7 @@
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/micromatch": "^4.0.9",
|
||||
"@types/mock-fs": "^4.13.1",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/node": "^22.19.0",
|
||||
"@vitest/coverage-v8": "^3.0.0",
|
||||
"byte-size": "^9.0.0",
|
||||
"cli-progress": "^3.12.0",
|
||||
@@ -28,10 +28,10 @@
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-unicorn": "^62.0.0",
|
||||
"eslint-plugin-unicorn": "^60.0.0",
|
||||
"globals": "^16.0.0",
|
||||
"mock-fs": "^5.2.0",
|
||||
"prettier": "^3.7.4",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-organize-imports": "^4.0.0",
|
||||
"typescript": "^5.3.3",
|
||||
"typescript-eslint": "^8.28.0",
|
||||
@@ -69,6 +69,6 @@
|
||||
"micromatch": "^4.0.8"
|
||||
},
|
||||
"volta": {
|
||||
"node": "24.11.1"
|
||||
"node": "24.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ describe('crawl', () => {
|
||||
.map(([file]) => file);
|
||||
|
||||
// Compare file's content instead of path since a file can be represent in multiple ways.
|
||||
expect(actual.map((path) => readContent(path)).toSorted()).toEqual(expected.toSorted());
|
||||
expect(actual.map((path) => readContent(path)).sort()).toEqual(expected.sort());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -160,7 +160,7 @@ export const crawl = async (options: CrawlOptions): Promise<string[]> => {
|
||||
ignore: [`**/${exclusionPattern}`],
|
||||
});
|
||||
globbedFiles.push(...crawledFiles);
|
||||
return globbedFiles.toSorted();
|
||||
return globbedFiles.sort();
|
||||
};
|
||||
|
||||
export const sha1 = (filepath: string) => {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"target": "es2023",
|
||||
"target": "es2022",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"incremental": true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tools]
|
||||
terragrunt = "0.93.10"
|
||||
opentofu = "1.10.7"
|
||||
terragrunt = "0.91.2"
|
||||
opentofu = "1.10.6"
|
||||
|
||||
[tasks."tg:fmt"]
|
||||
run = "terragrunt hclfmt"
|
||||
|
||||
@@ -41,7 +41,6 @@ services:
|
||||
- app-node_modules:/usr/src/app/node_modules
|
||||
- sveltekit:/usr/src/app/web/.svelte-kit
|
||||
- coverage:/usr/src/app/web/coverage
|
||||
- ../plugins:/build/corePlugin
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
@@ -58,6 +57,10 @@ services:
|
||||
IMMICH_THIRD_PARTY_BUG_FEATURE_URL: https://github.com/immich-app/immich/issues
|
||||
IMMICH_THIRD_PARTY_DOCUMENTATION_URL: https://docs.immich.app
|
||||
IMMICH_THIRD_PARTY_SUPPORT_URL: https://docs.immich.app/community-guides
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 1048576
|
||||
hard: 1048576
|
||||
ports:
|
||||
- 9230:9230
|
||||
- 9231:9231
|
||||
@@ -96,6 +99,10 @@ services:
|
||||
- app-node_modules:/usr/src/app/node_modules
|
||||
- sveltekit:/usr/src/app/web/.svelte-kit
|
||||
- coverage:/usr/src/app/web/coverage
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 1048576
|
||||
hard: 1048576
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
immich-server:
|
||||
@@ -127,7 +134,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: docker.io/valkey/valkey:9@sha256:4503e204c900a00ad393bec83c8c7c4c76b0529cd629e23b34b52011aefd1d27
|
||||
image: docker.io/valkey/valkey:8@sha256:81db6d39e1bba3b3ff32bd3a1b19a6d69690f94a3954ec131277b9a26b95b3aa
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: docker.io/valkey/valkey:9@sha256:4503e204c900a00ad393bec83c8c7c4c76b0529cd629e23b34b52011aefd1d27
|
||||
image: docker.io/valkey/valkey:8@sha256:81db6d39e1bba3b3ff32bd3a1b19a6d69690f94a3954ec131277b9a26b95b3aa
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
restart: always
|
||||
@@ -95,7 +95,7 @@ services:
|
||||
command: ['./run.sh', '-disable-reporting']
|
||||
ports:
|
||||
- 3000:3000
|
||||
image: grafana/grafana:12.3.0-ubuntu@sha256:cee936306135e1925ab21dffa16f8a411535d16ab086bef2309339a8e74d62df
|
||||
image: grafana/grafana:12.2.1-ubuntu@sha256:797530c642f7b41ba7848c44cfda5e361ef1f3391a98bed1e5d448c472b6826a
|
||||
volumes:
|
||||
- grafana-data:/var/lib/grafana
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: docker.io/valkey/valkey:9@sha256:4503e204c900a00ad393bec83c8c7c4c76b0529cd629e23b34b52011aefd1d27
|
||||
image: docker.io/valkey/valkey:8@sha256:81db6d39e1bba3b3ff32bd3a1b19a6d69690f94a3954ec131277b9a26b95b3aa
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
restart: always
|
||||
|
||||
@@ -1 +1 @@
|
||||
24.11.1
|
||||
24.11.0
|
||||
|
||||
@@ -133,9 +133,9 @@ There are a few different scenarios that can lead to this situation. The solutio
|
||||
The job is only automatically run once per asset after upload. If metadata extraction originally failed, the jobs were cleared/canceled, etc.,
|
||||
the job may not have run automatically the first time.
|
||||
|
||||
### How can I hide a photo or video from the timeline?
|
||||
### How can I hide photos from the timeline?
|
||||
|
||||
You can _archive_ them. This will hide the asset from the main timeline and folder view, but it will still show up in searches. All archived assets can be found in the _Archive_ view
|
||||
You can _archive_ them.
|
||||
|
||||
### How can I backup data from Immich?
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ By default, Immich will keep the last 14 database dumps and create a new dump ev
|
||||
|
||||
#### Trigger Dump
|
||||
|
||||
You are able to trigger a database dump in the [admin job status page](http://my.immich.app/admin/queues).
|
||||
You are able to trigger a database dump in the [admin job status page](http://my.immich.app/admin/jobs-status).
|
||||
Visit the page, open the "Create job" modal from the top right, select "Create Database Dump" and click "Confirm".
|
||||
A job will run and trigger a dump, you can verify this worked correctly by checking the logs or the `backups/` folder.
|
||||
This dumps will count towards the last `X` dumps that will be kept based on your settings.
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# Maintenance Mode
|
||||
|
||||
Maintenance mode is used to perform administrative tasks such as restoring backups to Immich.
|
||||
|
||||
You can enter maintenance mode by either:
|
||||
|
||||
- Selecting "enable maintenance mode" in system settings in administration.
|
||||
- Running the enable maintenance mode [administration command](./server-commands.md).
|
||||
|
||||
## Logging in during maintenance
|
||||
|
||||
Maintenance mode uses a separate login system which is handled automatically behind the scenes in most cases. Enabling maintenance mode in settings will automatically log you into maintenance mode when the server comes back up.
|
||||
|
||||
If you find that you've been logged out, you can:
|
||||
|
||||
- Open the logs for the Immich server and look for _"🚧 Immich is in maintenance mode, you can log in using the following URL:"_
|
||||
- Run the enable maintenance mode [administration command](./server-commands.md) again, this will give you a new URL to login with.
|
||||
- Run the disable maintenance mode [administration command](./server-commands.md) then re-enter through system settings.
|
||||
@@ -21,9 +21,6 @@ server {
|
||||
# allow large file uploads
|
||||
client_max_body_size 50000M;
|
||||
|
||||
# disable buffering uploads to prevent OOM on reverse proxy server and make uploads twice as fast (no pause)
|
||||
proxy_request_buffering off;
|
||||
|
||||
# Set headers
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
@@ -32,6 +29,8 @@ server {
|
||||
|
||||
# enable websockets: http://nginx.org/en/docs/http/websocket.html
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_redirect off;
|
||||
|
||||
# set timeout
|
||||
@@ -41,8 +40,6 @@ server {
|
||||
|
||||
location / {
|
||||
proxy_pass http://<backend_url>:2283;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
# useful when using Let's Encrypt http-01 challenge
|
||||
|
||||
@@ -2,19 +2,17 @@
|
||||
|
||||
The `immich-server` docker image comes preinstalled with an administrative CLI (`immich-admin`) that supports the following commands:
|
||||
|
||||
| Command | Description |
|
||||
| -------------------------- | ------------------------------------------------------------- |
|
||||
| `help` | Display help |
|
||||
| `reset-admin-password` | Reset the password for the admin user |
|
||||
| `disable-password-login` | Disable password login |
|
||||
| `enable-password-login` | Enable password login |
|
||||
| `disable-maintenance-mode` | Disable maintenance mode |
|
||||
| `enable-maintenance-mode` | Enable maintenance mode |
|
||||
| `enable-oauth-login` | Enable OAuth login |
|
||||
| `disable-oauth-login` | Disable OAuth login |
|
||||
| `list-users` | List Immich users |
|
||||
| `version` | Print Immich version |
|
||||
| `change-media-location` | Change database file paths to align with a new media location |
|
||||
| Command | Description |
|
||||
| ------------------------ | ------------------------------------------------------------- |
|
||||
| `help` | Display help |
|
||||
| `reset-admin-password` | Reset the password for the admin user |
|
||||
| `disable-password-login` | Disable password login |
|
||||
| `enable-password-login` | Enable password login |
|
||||
| `enable-oauth-login` | Enable OAuth login |
|
||||
| `disable-oauth-login` | Disable OAuth login |
|
||||
| `list-users` | List Immich users |
|
||||
| `version` | Print Immich version |
|
||||
| `change-media-location` | Change database file paths to align with a new media location |
|
||||
|
||||
## How to run a command
|
||||
|
||||
@@ -49,23 +47,6 @@ immich-admin enable-password-login
|
||||
Password login has been enabled.
|
||||
```
|
||||
|
||||
Disable Maintenance Mode
|
||||
|
||||
```
|
||||
immich-admin disable-maintenace-mode
|
||||
Maintenance mode has been disabled.
|
||||
```
|
||||
|
||||
Enable Maintenance Mode
|
||||
|
||||
```
|
||||
immich-admin enable-maintenance-mode
|
||||
Maintenance mode has been enabled.
|
||||
|
||||
Log in using the following URL:
|
||||
https://my.immich.app/maintenance?token=<token>
|
||||
```
|
||||
|
||||
Enable OAuth login
|
||||
|
||||
```
|
||||
|
||||
@@ -12,13 +12,3 @@ pnpm run migrations:generate <migration-name>
|
||||
3. Move the migration file to folder `./server/src/schema/migrations` in your code editor.
|
||||
|
||||
The server will automatically detect `*.ts` file changes and restart. Part of the server start-up process includes running any new migrations, so it will be applied immediately.
|
||||
|
||||
## Reverting a Migration
|
||||
|
||||
If you need to undo the most recently applied migration—for example, when developing or testing on schema changes—run:
|
||||
|
||||
```bash
|
||||
pnpm run migrations:revert
|
||||
```
|
||||
|
||||
This command rolls back the latest migration and brings the database schema back to its previous state.
|
||||
|
||||
@@ -256,7 +256,7 @@ The Dev Container supports multiple ways to run tests:
|
||||
|
||||
```bash
|
||||
# Run tests for specific components
|
||||
make test-server # Server unit tests
|
||||
make test-server # Server unit tests
|
||||
make test-web # Web unit tests
|
||||
make test-e2e # End-to-end tests
|
||||
make test-cli # CLI tests
|
||||
@@ -268,13 +268,12 @@ make test-all # Runs tests for all components
|
||||
make test-medium-dev # End-to-end tests
|
||||
```
|
||||
|
||||
#### Using PNPM Directly
|
||||
#### Using NPM Directly
|
||||
|
||||
```bash
|
||||
# Server tests
|
||||
cd /workspaces/immich/server
|
||||
pnpm test # Run all tests
|
||||
pnpm run test:medium # Medium tests (integration tests)
|
||||
pnpm test # Run all tests
|
||||
pnpm run test:watch # Watch mode
|
||||
pnpm run test:cov # Coverage report
|
||||
|
||||
@@ -294,21 +293,21 @@ pnpm run test:web # Run web UI tests
|
||||
```bash
|
||||
# Linting
|
||||
make lint-server # Lint server code
|
||||
make lint-web # Lint web code
|
||||
make lint-all # Lint all components
|
||||
make lint-web # Lint web code
|
||||
make lint-all # Lint all components
|
||||
|
||||
# Formatting
|
||||
make format-server # Format server code
|
||||
make format-web # Format web code
|
||||
make format-all # Format all code
|
||||
make format-web # Format web code
|
||||
make format-all # Format all code
|
||||
|
||||
# Type checking
|
||||
make check-server # Type check server
|
||||
make check-web # Type check web
|
||||
make check-all # Check all components
|
||||
make check-web # Type check web
|
||||
make check-all # Check all components
|
||||
|
||||
# Complete hygiene check
|
||||
make hygiene-all # Run lint, format, check, SQL sync, and audit
|
||||
make hygiene-all # Runs lint, format, check, SQL sync, and audit
|
||||
```
|
||||
|
||||
### Additional Make Commands
|
||||
@@ -316,21 +315,21 @@ make hygiene-all # Run lint, format, check, SQL sync, and audit
|
||||
```bash
|
||||
# Build commands
|
||||
make build-server # Build server
|
||||
make build-web # Build web app
|
||||
make build-all # Build everything
|
||||
make build-web # Build web app
|
||||
make build-all # Build everything
|
||||
|
||||
# API generation
|
||||
make open-api # Generate OpenAPI specs
|
||||
make open-api # Generate OpenAPI specs
|
||||
make open-api-typescript # Generate TypeScript SDK
|
||||
make open-api-dart # Generate Dart SDK
|
||||
make open-api-dart # Generate Dart SDK
|
||||
|
||||
# Database
|
||||
make sql # Sync database schema
|
||||
make sql # Sync database schema
|
||||
|
||||
# Dependencies
|
||||
make install-server # Install server dependencies
|
||||
make install-web # Install web dependencies
|
||||
make install-all # Install all dependencies
|
||||
make install-server # Install server dependencies
|
||||
make install-web # Install web dependencies
|
||||
make install-all # Install all dependencies
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
@@ -14,15 +14,15 @@ When contributing code through a pull request, please check the following:
|
||||
- [ ] `pnpm run check:typescript` (check typescript)
|
||||
- [ ] `pnpm test` (unit tests)
|
||||
|
||||
:::tip AIO
|
||||
Run all web checks with `pnpm run check:all`
|
||||
:::
|
||||
|
||||
## Documentation
|
||||
|
||||
- [ ] `pnpm run format` (formatting via Prettier)
|
||||
- [ ] Update the `_redirects` file if you have renamed a page or removed it from the documentation.
|
||||
|
||||
:::tip AIO
|
||||
Run all web checks with `pnpm run check:all`
|
||||
:::
|
||||
|
||||
## Server Checks
|
||||
|
||||
- [ ] `pnpm run lint` (linting via ESLint)
|
||||
|
||||
@@ -5,7 +5,7 @@ sidebar_position: 2
|
||||
# Setup
|
||||
|
||||
:::note
|
||||
If there's a feature you're planning to work on, just give us a heads up in [#contributing](https://discord.com/channels/979116623879368755/1071165397228855327) on [our Discord](https://discord.immich.app) so we can:
|
||||
If there's a feature you're planning to work on, just give us a heads up in [Discord](https://discord.com/channels/979116623879368755/1071165397228855327) so we can:
|
||||
|
||||
1. Let you know if it's something we would accept into Immich
|
||||
2. Provide any guidance on how something like that would ideally be implemented
|
||||
@@ -48,6 +48,7 @@ You can access the web from `http://your-machine-ip:3000` or `http://localhost:3
|
||||
**Notes:**
|
||||
|
||||
- The "web" development container runs with uid 1000. If that uid does not have read/write permissions on the mounted volumes, you may encounter errors
|
||||
- In case of rootless docker setup, you need to use root within the container, otherwise you will encounter read/write permission related errors, see comments in `docker/docker-compose.dev.yml`.
|
||||
|
||||
#### Connect web to a remote backend
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ make e2e
|
||||
Before you can run the tests, you need to run the following commands _once_:
|
||||
|
||||
- `pnpm install` (in `e2e/`)
|
||||
- `pnpm run build` (in `cli/`)
|
||||
- `make open-api` (in the project root `/`)
|
||||
|
||||
Once the test environment is running, the e2e tests can be run via:
|
||||
|
||||
@@ -1222,4 +1222,4 @@ Feel free to make a feature request if there's a model you want to use that we d
|
||||
[huggingface-clip]: https://huggingface.co/collections/immich-app/clip-654eaefb077425890874cd07
|
||||
[huggingface-multilingual-clip]: https://huggingface.co/collections/immich-app/multilingual-clip-654eb08c2382f591eeb8c2a7
|
||||
[smart-search-settings]: https://my.immich.app/admin/system-settings?isOpen=machine-learning+smart-search
|
||||
[job-status-page]: https://my.immich.app/admin/queues
|
||||
[job-status-page]: https://my.immich.app/admin/jobs-status
|
||||
|
||||
@@ -53,7 +53,7 @@ Version mismatches between both hosts may cause bugs and instability, so remembe
|
||||
|
||||
Adding a new URL to the settings is recommended over replacing the existing URL. This is because it will allow machine learning tasks to be processed successfully when the remote server is down by falling back to the local machine learning container. If you do not want machine learning tasks to be processed locally when the remote server is not available, you can instead replace the existing URL and only provide the remote container's URL. If doing this, you can remove the `immich-machine-learning` section of the local `docker-compose.yml` file to save resources, as this service will never be used.
|
||||
|
||||
Do note that this will mean that Smart Search and Face Detection jobs will fail to be processed when the remote instance is not available. This in turn means that tasks dependent on these features—Duplicate Detection and Facial Recognition—will not run for affected assets. If this occurs, you must manually click the _Missing_ button next to Smart Search and Face Detection in the [Job Status](http://my.immich.app/admin/queues) page for the jobs to be retried.
|
||||
Do note that this will mean that Smart Search and Face Detection jobs will fail to be processed when the remote instance is not available. This in turn means that tasks dependent on these features—Duplicate Detection and Facial Recognition—will not run for affected assets. If this occurs, you must manually click the _Missing_ button next to Smart Search and Face Detection in the [Job Status](http://my.immich.app/admin/jobs-status) page for the jobs to be retried.
|
||||
|
||||
## Load balancing
|
||||
|
||||
|
||||
@@ -62,10 +62,10 @@ Information on the current workers can be found [here](/administration/jobs-work
|
||||
|
||||
## Ports
|
||||
|
||||
| Variable | Description | Default | Containers |
|
||||
| :------------ | :------------- | :----------------------------------------: | :----------------------- |
|
||||
| `IMMICH_HOST` | Listening host | `0.0.0.0` | server, machine learning |
|
||||
| `IMMICH_PORT` | Listening port | `2283` (server), `3003` (machine learning) | server, machine learning |
|
||||
| Variable | Description | Default |
|
||||
| :------------ | :------------- | :----------------------------------------: |
|
||||
| `IMMICH_HOST` | Listening host | `0.0.0.0` |
|
||||
| `IMMICH_PORT` | Listening port | `2283` (server), `3003` (machine learning) |
|
||||
|
||||
## Database
|
||||
|
||||
@@ -80,7 +80,7 @@ Information on the current workers can be found [here](/administration/jobs-work
|
||||
| `DB_SSL_MODE` | Database SSL mode | | server |
|
||||
| `DB_VECTOR_EXTENSION`<sup>\*2</sup> | Database vector extension (one of [`vectorchord`, `pgvector`, `pgvecto.rs`]) | | server |
|
||||
| `DB_SKIP_MIGRATIONS` | Whether to skip running migrations on startup (one of [`true`, `false`]) | `false` | server |
|
||||
| `DB_STORAGE_TYPE` | Optimize concurrent IO on SSDs or sequential IO on HDDs ([`SSD`, `HDD`])<sup>\*3</sup> | `SSD` | database |
|
||||
| `DB_STORAGE_TYPE` | Optimize concurrent IO on SSDs or sequential IO on HDDs ([`SSD`, `HDD`])<sup>\*3</sup> | `SSD` | server |
|
||||
|
||||
\*1: The values of `DB_USERNAME`, `DB_PASSWORD`, and `DB_DATABASE_NAME` are passed to the Postgres container as the variables `POSTGRES_USER`, `POSTGRES_PASSWORD`, and `POSTGRES_DB` in `docker-compose.yml`.
|
||||
|
||||
@@ -93,7 +93,7 @@ Information on the current workers can be found [here](/administration/jobs-work
|
||||
All `DB_` variables must be provided to all Immich workers, including `api` and `microservices`.
|
||||
|
||||
`DB_URL` must be in the format `postgresql://immichdbusername:immichdbpassword@postgreshost:postgresport/immichdatabasename`.
|
||||
You can require SSL by adding `?sslmode=require` to the end of the `DB_URL` string, or require SSL and skip certificate verification by adding `?sslmode=require&uselibpqcompat=true`. This allows both immich and `pg_dumpall` (the utility used for database backups) to [properly connect](https://github.com/brianc/node-postgres/tree/master/packages/pg-connection-string#tcp-connections) to your database.
|
||||
You can require SSL by adding `?sslmode=require` to the end of the `DB_URL` string, or require SSL and skip certificate verification by adding `?sslmode=require&sslmode=no-verify`.
|
||||
|
||||
When `DB_URL` is defined, the `DB_HOSTNAME`, `DB_PORT`, `DB_USERNAME`, `DB_PASSWORD` and `DB_DATABASE_NAME` database variables are ignored.
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"@docusaurus/module-type-aliases": "~3.9.0",
|
||||
"@docusaurus/tsconfig": "^3.7.0",
|
||||
"@docusaurus/types": "^3.7.0",
|
||||
"prettier": "^3.7.4",
|
||||
"prettier": "^3.2.4",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"browserslist": {
|
||||
@@ -57,6 +57,6 @@
|
||||
"node": ">=20"
|
||||
},
|
||||
"volta": {
|
||||
"node": "24.11.1"
|
||||
"node": "24.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
8
docs/static/archived-versions.json
vendored
8
docs/static/archived-versions.json
vendored
@@ -1,12 +1,4 @@
|
||||
[
|
||||
{
|
||||
"label": "v2.3.1",
|
||||
"url": "https://docs.v2.3.1.archive.immich.app"
|
||||
},
|
||||
{
|
||||
"label": "v2.3.0",
|
||||
"url": "https://docs.v2.3.0.archive.immich.app"
|
||||
},
|
||||
{
|
||||
"label": "v2.2.3",
|
||||
"url": "https://docs.v2.2.3.archive.immich.app"
|
||||
|
||||
1
e2e/.gitignore
vendored
1
e2e/.gitignore
vendored
@@ -4,4 +4,3 @@ node_modules/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
/dist
|
||||
.env
|
||||
|
||||
@@ -1 +1 @@
|
||||
24.11.1
|
||||
24.11.0
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
name: immich-e2e
|
||||
|
||||
services:
|
||||
immich-server:
|
||||
container_name: immich-e2e-server
|
||||
command: ['immich-dev']
|
||||
image: immich-server-dev:latest
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: server/Dockerfile.dev
|
||||
target: dev
|
||||
environment:
|
||||
- DB_HOSTNAME=database
|
||||
- DB_USERNAME=postgres
|
||||
- DB_PASSWORD=postgres
|
||||
- DB_DATABASE_NAME=immich
|
||||
- IMMICH_MACHINE_LEARNING_ENABLED=false
|
||||
- IMMICH_TELEMETRY_INCLUDE=all
|
||||
- IMMICH_ENV=testing
|
||||
- IMMICH_PORT=2285
|
||||
- IMMICH_IGNORE_MOUNT_CHECK_ERRORS=true
|
||||
volumes:
|
||||
- ./test-assets:/test-assets
|
||||
- ..:/usr/src/app
|
||||
- ${UPLOAD_LOCATION}/photos:/data
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- pnpm-store:/usr/src/app/.pnpm-store
|
||||
- server-node_modules:/usr/src/app/server/node_modules
|
||||
- web-node_modules:/usr/src/app/web/node_modules
|
||||
- github-node_modules:/usr/src/app/.github/node_modules
|
||||
- cli-node_modules:/usr/src/app/cli/node_modules
|
||||
- docs-node_modules:/usr/src/app/docs/node_modules
|
||||
- e2e-node_modules:/usr/src/app/e2e/node_modules
|
||||
- sdk-node_modules:/usr/src/app/open-api/typescript-sdk/node_modules
|
||||
- app-node_modules:/usr/src/app/node_modules
|
||||
- sveltekit:/usr/src/app/web/.svelte-kit
|
||||
- coverage:/usr/src/app/web/coverage
|
||||
- ../plugins:/build/corePlugin
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_started
|
||||
database:
|
||||
condition: service_healthy
|
||||
|
||||
immich-web:
|
||||
container_name: immich-e2e-web
|
||||
image: immich-web-dev:latest
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: server/Dockerfile.dev
|
||||
target: dev
|
||||
command: ['immich-web']
|
||||
ports:
|
||||
- 2285:3000
|
||||
environment:
|
||||
- IMMICH_SERVER_URL=http://immich-server:2285/
|
||||
volumes:
|
||||
- ..:/usr/src/app
|
||||
- pnpm-store:/usr/src/app/.pnpm-store
|
||||
- server-node_modules:/usr/src/app/server/node_modules
|
||||
- web-node_modules:/usr/src/app/web/node_modules
|
||||
- github-node_modules:/usr/src/app/.github/node_modules
|
||||
- cli-node_modules:/usr/src/app/cli/node_modules
|
||||
- docs-node_modules:/usr/src/app/docs/node_modules
|
||||
- e2e-node_modules:/usr/src/app/e2e/node_modules
|
||||
- sdk-node_modules:/usr/src/app/open-api/typescript-sdk/node_modules
|
||||
- app-node_modules:/usr/src/app/node_modules
|
||||
- sveltekit:/usr/src/app/web/.svelte-kit
|
||||
- coverage:/usr/src/app/web/coverage
|
||||
restart: unless-stopped
|
||||
|
||||
redis:
|
||||
image: redis:6.2-alpine@sha256:37e002448575b32a599109664107e374c8709546905c372a34d64919043b9ceb
|
||||
|
||||
database:
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:6f3e9d2c2177af16c2988ff71425d79d89ca630ec2f9c8db03209ab716542338
|
||||
command: -c fsync=off -c shared_preload_libraries=vchord.so -c config_file=/var/lib/postgresql/data/postgresql.conf
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: immich
|
||||
ports:
|
||||
- 5435:5432
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U postgres -d immich']
|
||||
interval: 1s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
start_period: 10s
|
||||
|
||||
volumes:
|
||||
model-cache:
|
||||
prometheus-data:
|
||||
grafana-data:
|
||||
pnpm-store:
|
||||
server-node_modules:
|
||||
web-node_modules:
|
||||
github-node_modules:
|
||||
cli-node_modules:
|
||||
docs-node_modules:
|
||||
e2e-node_modules:
|
||||
sdk-node_modules:
|
||||
app-node_modules:
|
||||
sveltekit:
|
||||
coverage:
|
||||
@@ -7,9 +7,6 @@ services:
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: server/Dockerfile
|
||||
cache_from:
|
||||
- type=registry,ref=ghcr.io/immich-app/immich-server-build-cache:linux-amd64-cc099f297acd18c924b35ece3245215b53d106eb2518e3af6415931d055746cd-main
|
||||
- type=registry,ref=ghcr.io/immich-app/immich-server-build-cache:linux-arm64-cc099f297acd18c924b35ece3245215b53d106eb2518e3af6415931d055746cd-main
|
||||
args:
|
||||
- BUILD_ID=1234567890
|
||||
- BUILD_IMAGE=e2e
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "2.3.1",
|
||||
"version": "2.2.3",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
@@ -20,32 +20,30 @@
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.8.0",
|
||||
"@faker-js/faker": "^10.1.0",
|
||||
"@immich/cli": "file:../cli",
|
||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||
"@playwright/test": "^1.44.1",
|
||||
"@socket.io/component-emitter": "^3.1.2",
|
||||
"@types/luxon": "^3.4.2",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/node": "^22.19.0",
|
||||
"@types/oidc-provider": "^9.0.0",
|
||||
"@types/pg": "^8.15.1",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"dotenv": "^17.2.3",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-unicorn": "^62.0.0",
|
||||
"exiftool-vendored": "^34.0.0",
|
||||
"eslint-plugin-unicorn": "^60.0.0",
|
||||
"exiftool-vendored": "^31.1.0",
|
||||
"globals": "^16.0.0",
|
||||
"jose": "^5.6.3",
|
||||
"luxon": "^3.4.4",
|
||||
"oidc-provider": "^9.0.0",
|
||||
"pg": "^8.11.3",
|
||||
"pngjs": "^7.0.0",
|
||||
"prettier": "^3.7.4",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-organize-imports": "^4.0.0",
|
||||
"sharp": "^0.34.5",
|
||||
"sharp": "^0.34.4",
|
||||
"socket.io-client": "^4.7.4",
|
||||
"supertest": "^7.0.0",
|
||||
"typescript": "^5.3.3",
|
||||
@@ -54,6 +52,6 @@
|
||||
"vitest": "^3.0.0"
|
||||
},
|
||||
"volta": {
|
||||
"node": "24.11.1"
|
||||
"node": "24.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,23 @@
|
||||
import { defineConfig, devices, PlaywrightTestConfig } from '@playwright/test';
|
||||
import dotenv from 'dotenv';
|
||||
import { cpus } from 'node:os';
|
||||
import { resolve } from 'node:path';
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
dotenv.config({ path: resolve(import.meta.dirname, '.env') });
|
||||
|
||||
export const playwrightHost = process.env.PLAYWRIGHT_HOST ?? '127.0.0.1';
|
||||
export const playwrightDbHost = process.env.PLAYWRIGHT_DB_HOST ?? '127.0.0.1';
|
||||
export const playwriteBaseUrl = process.env.PLAYWRIGHT_BASE_URL ?? `http://${playwrightHost}:2285`;
|
||||
export const playwriteSlowMo = parseInt(process.env.PLAYWRIGHT_SLOW_MO ?? '0');
|
||||
export const playwrightDisableWebserver = process.env.PLAYWRIGHT_DISABLE_WEBSERVER;
|
||||
|
||||
process.env.PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS = '1';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
export default defineConfig({
|
||||
testDir: './src/web/specs',
|
||||
fullyParallel: false,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 4 : 0,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: 1,
|
||||
reporter: 'html',
|
||||
use: {
|
||||
baseURL: playwriteBaseUrl,
|
||||
baseURL: 'http://127.0.0.1:2285',
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
launchOptions: {
|
||||
slowMo: playwriteSlowMo,
|
||||
},
|
||||
},
|
||||
|
||||
testMatch: /.*\.e2e-spec\.ts/,
|
||||
|
||||
workers: process.env.CI ? 4 : Math.round(cpus().length * 0.75),
|
||||
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
testMatch: /.*\.e2e-spec\.ts/,
|
||||
workers: 1,
|
||||
},
|
||||
{
|
||||
name: 'parallel tests',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
testMatch: /.*\.parallel-e2e-spec\.ts/,
|
||||
fullyParallel: true,
|
||||
workers: process.env.CI ? 3 : Math.max(1, Math.round(cpus().length * 0.75) - 1),
|
||||
},
|
||||
|
||||
// {
|
||||
@@ -86,8 +59,4 @@ const config: PlaywrightTestConfig = {
|
||||
stderr: 'pipe',
|
||||
reuseExistingServer: true,
|
||||
},
|
||||
};
|
||||
if (playwrightDisableWebserver) {
|
||||
delete config.webServer;
|
||||
}
|
||||
export default defineConfig(config);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LoginResponseDto, QueueCommand, QueueName, updateConfig } from '@immich/sdk';
|
||||
import { JobCommand, JobName, LoginResponseDto, updateConfig } from '@immich/sdk';
|
||||
import { cpSync, rmSync } from 'node:fs';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { basename } from 'node:path';
|
||||
@@ -17,28 +17,28 @@ describe('/jobs', () => {
|
||||
|
||||
describe('PUT /jobs', () => {
|
||||
afterEach(async () => {
|
||||
await utils.queueCommand(admin.accessToken, QueueName.MetadataExtraction, {
|
||||
command: QueueCommand.Resume,
|
||||
await utils.jobCommand(admin.accessToken, JobName.MetadataExtraction, {
|
||||
command: JobCommand.Resume,
|
||||
force: false,
|
||||
});
|
||||
|
||||
await utils.queueCommand(admin.accessToken, QueueName.ThumbnailGeneration, {
|
||||
command: QueueCommand.Resume,
|
||||
await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, {
|
||||
command: JobCommand.Resume,
|
||||
force: false,
|
||||
});
|
||||
|
||||
await utils.queueCommand(admin.accessToken, QueueName.FaceDetection, {
|
||||
command: QueueCommand.Resume,
|
||||
await utils.jobCommand(admin.accessToken, JobName.FaceDetection, {
|
||||
command: JobCommand.Resume,
|
||||
force: false,
|
||||
});
|
||||
|
||||
await utils.queueCommand(admin.accessToken, QueueName.SmartSearch, {
|
||||
command: QueueCommand.Resume,
|
||||
await utils.jobCommand(admin.accessToken, JobName.SmartSearch, {
|
||||
command: JobCommand.Resume,
|
||||
force: false,
|
||||
});
|
||||
|
||||
await utils.queueCommand(admin.accessToken, QueueName.DuplicateDetection, {
|
||||
command: QueueCommand.Resume,
|
||||
await utils.jobCommand(admin.accessToken, JobName.DuplicateDetection, {
|
||||
command: JobCommand.Resume,
|
||||
force: false,
|
||||
});
|
||||
|
||||
@@ -59,8 +59,8 @@ describe('/jobs', () => {
|
||||
it('should queue metadata extraction for missing assets', async () => {
|
||||
const path = `${testAssetDir}/formats/raw/Nikon/D700/philadelphia.nef`;
|
||||
|
||||
await utils.queueCommand(admin.accessToken, QueueName.MetadataExtraction, {
|
||||
command: QueueCommand.Pause,
|
||||
await utils.jobCommand(admin.accessToken, JobName.MetadataExtraction, {
|
||||
command: JobCommand.Pause,
|
||||
force: false,
|
||||
});
|
||||
|
||||
@@ -77,20 +77,20 @@ describe('/jobs', () => {
|
||||
expect(asset.exifInfo?.make).toBeNull();
|
||||
}
|
||||
|
||||
await utils.queueCommand(admin.accessToken, QueueName.MetadataExtraction, {
|
||||
command: QueueCommand.Empty,
|
||||
await utils.jobCommand(admin.accessToken, JobName.MetadataExtraction, {
|
||||
command: JobCommand.Empty,
|
||||
force: false,
|
||||
});
|
||||
|
||||
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
|
||||
|
||||
await utils.queueCommand(admin.accessToken, QueueName.MetadataExtraction, {
|
||||
command: QueueCommand.Resume,
|
||||
await utils.jobCommand(admin.accessToken, JobName.MetadataExtraction, {
|
||||
command: JobCommand.Resume,
|
||||
force: false,
|
||||
});
|
||||
|
||||
await utils.queueCommand(admin.accessToken, QueueName.MetadataExtraction, {
|
||||
command: QueueCommand.Start,
|
||||
await utils.jobCommand(admin.accessToken, JobName.MetadataExtraction, {
|
||||
command: JobCommand.Start,
|
||||
force: false,
|
||||
});
|
||||
|
||||
@@ -124,8 +124,8 @@ describe('/jobs', () => {
|
||||
|
||||
cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, path);
|
||||
|
||||
await utils.queueCommand(admin.accessToken, QueueName.MetadataExtraction, {
|
||||
command: QueueCommand.Start,
|
||||
await utils.jobCommand(admin.accessToken, JobName.MetadataExtraction, {
|
||||
command: JobCommand.Start,
|
||||
force: false,
|
||||
});
|
||||
|
||||
@@ -144,8 +144,8 @@ describe('/jobs', () => {
|
||||
it('should queue thumbnail extraction for assets missing thumbs', async () => {
|
||||
const path = `${testAssetDir}/albums/nature/tanners_ridge.jpg`;
|
||||
|
||||
await utils.queueCommand(admin.accessToken, QueueName.ThumbnailGeneration, {
|
||||
command: QueueCommand.Pause,
|
||||
await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, {
|
||||
command: JobCommand.Pause,
|
||||
force: false,
|
||||
});
|
||||
|
||||
@@ -153,32 +153,32 @@ describe('/jobs', () => {
|
||||
assetData: { bytes: await readFile(path), filename: basename(path) },
|
||||
});
|
||||
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.MetadataExtraction);
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.ThumbnailGeneration);
|
||||
await utils.waitForQueueFinish(admin.accessToken, JobName.MetadataExtraction);
|
||||
await utils.waitForQueueFinish(admin.accessToken, JobName.ThumbnailGeneration);
|
||||
|
||||
const assetBefore = await utils.getAssetInfo(admin.accessToken, id);
|
||||
expect(assetBefore.thumbhash).toBeNull();
|
||||
|
||||
await utils.queueCommand(admin.accessToken, QueueName.ThumbnailGeneration, {
|
||||
command: QueueCommand.Empty,
|
||||
await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, {
|
||||
command: JobCommand.Empty,
|
||||
force: false,
|
||||
});
|
||||
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.MetadataExtraction);
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.ThumbnailGeneration);
|
||||
await utils.waitForQueueFinish(admin.accessToken, JobName.MetadataExtraction);
|
||||
await utils.waitForQueueFinish(admin.accessToken, JobName.ThumbnailGeneration);
|
||||
|
||||
await utils.queueCommand(admin.accessToken, QueueName.ThumbnailGeneration, {
|
||||
command: QueueCommand.Resume,
|
||||
await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, {
|
||||
command: JobCommand.Resume,
|
||||
force: false,
|
||||
});
|
||||
|
||||
await utils.queueCommand(admin.accessToken, QueueName.ThumbnailGeneration, {
|
||||
command: QueueCommand.Start,
|
||||
await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, {
|
||||
command: JobCommand.Start,
|
||||
force: false,
|
||||
});
|
||||
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.MetadataExtraction);
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.ThumbnailGeneration);
|
||||
await utils.waitForQueueFinish(admin.accessToken, JobName.MetadataExtraction);
|
||||
await utils.waitForQueueFinish(admin.accessToken, JobName.ThumbnailGeneration);
|
||||
|
||||
const assetAfter = await utils.getAssetInfo(admin.accessToken, id);
|
||||
expect(assetAfter.thumbhash).not.toBeNull();
|
||||
@@ -193,26 +193,26 @@ describe('/jobs', () => {
|
||||
assetData: { bytes: await readFile(path), filename: basename(path) },
|
||||
});
|
||||
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.MetadataExtraction);
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.ThumbnailGeneration);
|
||||
await utils.waitForQueueFinish(admin.accessToken, JobName.MetadataExtraction);
|
||||
await utils.waitForQueueFinish(admin.accessToken, JobName.ThumbnailGeneration);
|
||||
|
||||
const assetBefore = await utils.getAssetInfo(admin.accessToken, id);
|
||||
|
||||
cpSync(`${testAssetDir}/albums/nature/notocactus_minimus.jpg`, path);
|
||||
|
||||
await utils.queueCommand(admin.accessToken, QueueName.ThumbnailGeneration, {
|
||||
command: QueueCommand.Resume,
|
||||
await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, {
|
||||
command: JobCommand.Resume,
|
||||
force: false,
|
||||
});
|
||||
|
||||
// This runs the missing thumbnail job
|
||||
await utils.queueCommand(admin.accessToken, QueueName.ThumbnailGeneration, {
|
||||
command: QueueCommand.Start,
|
||||
await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, {
|
||||
command: JobCommand.Start,
|
||||
force: false,
|
||||
});
|
||||
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.MetadataExtraction);
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.ThumbnailGeneration);
|
||||
await utils.waitForQueueFinish(admin.accessToken, JobName.MetadataExtraction);
|
||||
await utils.waitForQueueFinish(admin.accessToken, JobName.ThumbnailGeneration);
|
||||
|
||||
const assetAfter = await utils.getAssetInfo(admin.accessToken, id);
|
||||
|
||||
|
||||
@@ -1006,7 +1006,7 @@ describe('/libraries', () => {
|
||||
rmSync(`${testAssetDir}/temp/xmp`, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('should switch from using file metadata to file.ext.xmp metadata when asset refreshes', async () => {
|
||||
it('should switch from using file metadata to file.xmp metadata when asset refreshes', async () => {
|
||||
const library = await utils.createLibrary(admin.accessToken, {
|
||||
ownerId: admin.userId,
|
||||
importPaths: [`${testAssetDirInternal}/temp/xmp`],
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
import { LoginResponseDto } from '@immich/sdk';
|
||||
import { createUserDto } from 'src/fixtures';
|
||||
import { errorDto } from 'src/responses';
|
||||
import { app, utils } from 'src/utils';
|
||||
import request from 'supertest';
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
describe('/admin/maintenance', () => {
|
||||
let cookie: string | undefined;
|
||||
let admin: LoginResponseDto;
|
||||
let nonAdmin: LoginResponseDto;
|
||||
|
||||
beforeAll(async () => {
|
||||
await utils.resetDatabase();
|
||||
admin = await utils.adminSetup();
|
||||
nonAdmin = await utils.userSetup(admin.accessToken, createUserDto.user1);
|
||||
});
|
||||
|
||||
// => outside of maintenance mode
|
||||
|
||||
describe('GET ~/server/config', async () => {
|
||||
it('should indicate we are out of maintenance mode', async () => {
|
||||
const { status, body } = await request(app).get('/server/config');
|
||||
expect(status).toBe(200);
|
||||
expect(body.maintenanceMode).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /login', async () => {
|
||||
it('should not work out of maintenance mode', async () => {
|
||||
const { status, body } = await request(app).post('/admin/maintenance/login').send({ token: 'token' });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest('Not in maintenance mode'));
|
||||
});
|
||||
});
|
||||
|
||||
// => enter maintenance mode
|
||||
|
||||
describe.sequential('POST /', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(app).post('/admin/maintenance').send({
|
||||
action: 'end',
|
||||
});
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorDto.unauthorized);
|
||||
});
|
||||
|
||||
it('should only work for admins', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.post('/admin/maintenance')
|
||||
.set('Authorization', `Bearer ${nonAdmin.accessToken}`)
|
||||
.send({ action: 'end' });
|
||||
expect(status).toBe(403);
|
||||
expect(body).toEqual(errorDto.forbidden);
|
||||
});
|
||||
|
||||
it('should be a no-op if try to exit maintenance mode', async () => {
|
||||
const { status } = await request(app)
|
||||
.post('/admin/maintenance')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||
.send({ action: 'end' });
|
||||
expect(status).toBe(201);
|
||||
});
|
||||
|
||||
it('should enter maintenance mode', async () => {
|
||||
const { status, headers } = await request(app)
|
||||
.post('/admin/maintenance')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||
.send({
|
||||
action: 'start',
|
||||
});
|
||||
expect(status).toBe(201);
|
||||
|
||||
cookie = headers['set-cookie'][0].split(';')[0];
|
||||
expect(cookie).toEqual(
|
||||
expect.stringMatching(/^immich_maintenance_token=[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*$/),
|
||||
);
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const { body } = await request(app).get('/server/config');
|
||||
return body.maintenanceMode;
|
||||
},
|
||||
{
|
||||
interval: 5e2,
|
||||
timeout: 1e4,
|
||||
},
|
||||
)
|
||||
.toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
// => in maintenance mode
|
||||
|
||||
describe.sequential('in maintenance mode', () => {
|
||||
describe('GET ~/server/config', async () => {
|
||||
it('should indicate we are in maintenance mode', async () => {
|
||||
const { status, body } = await request(app).get('/server/config');
|
||||
expect(status).toBe(200);
|
||||
expect(body.maintenanceMode).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /login', async () => {
|
||||
it('should fail without cookie or token in body', async () => {
|
||||
const { status, body } = await request(app).post('/admin/maintenance/login').send({});
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorDto.unauthorizedWithMessage('Missing JWT Token'));
|
||||
});
|
||||
|
||||
it('should succeed with cookie', async () => {
|
||||
const { status, body } = await request(app).post('/admin/maintenance/login').set('cookie', cookie!).send({});
|
||||
expect(status).toBe(201);
|
||||
expect(body).toEqual(
|
||||
expect.objectContaining({
|
||||
username: 'Immich Admin',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should succeed with token', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.post('/admin/maintenance/login')
|
||||
.send({
|
||||
token: cookie!.split('=')[1].trim(),
|
||||
});
|
||||
expect(status).toBe(201);
|
||||
expect(body).toEqual(
|
||||
expect.objectContaining({
|
||||
username: 'Immich Admin',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /', async () => {
|
||||
it('should be a no-op if try to enter maintenance mode', async () => {
|
||||
const { status } = await request(app)
|
||||
.post('/admin/maintenance')
|
||||
.set('cookie', cookie!)
|
||||
.send({ action: 'start' });
|
||||
expect(status).toBe(201);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// => exit maintenance mode
|
||||
|
||||
describe.sequential('POST /', () => {
|
||||
it('should exit maintenance mode', async () => {
|
||||
const { status } = await request(app).post('/admin/maintenance').set('cookie', cookie!).send({
|
||||
action: 'end',
|
||||
});
|
||||
|
||||
expect(status).toBe(201);
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const { body } = await request(app).get('/server/config');
|
||||
return body.maintenanceMode;
|
||||
},
|
||||
{
|
||||
interval: 5e2,
|
||||
timeout: 1e4,
|
||||
},
|
||||
)
|
||||
.toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -136,7 +136,6 @@ describe('/server', () => {
|
||||
externalDomain: '',
|
||||
publicUsers: true,
|
||||
isOnboarded: false,
|
||||
maintenanceMode: false,
|
||||
mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json',
|
||||
mapLightStyleUrl: 'https://tiles.immich.cloud/v1/style/light.json',
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
JobName,
|
||||
LoginResponseDto,
|
||||
QueueName,
|
||||
createStack,
|
||||
deleteUserAdmin,
|
||||
getMyUser,
|
||||
@@ -328,7 +328,7 @@ describe('/admin/users', () => {
|
||||
{ headers: asBearerAuth(user.accessToken) },
|
||||
);
|
||||
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.BackgroundTask);
|
||||
await utils.waitForQueueFinish(admin.accessToken, JobName.BackgroundTask);
|
||||
|
||||
const { status, body } = await request(app)
|
||||
.delete(`/admin/users/${user.userId}`)
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
export { generateTimelineData } from './timeline/model-objects';
|
||||
|
||||
export { createDefaultTimelineConfig, validateTimelineConfig } from './timeline/timeline-config';
|
||||
|
||||
export type {
|
||||
MockAlbum,
|
||||
MonthSpec,
|
||||
SerializedTimelineData,
|
||||
MockTimelineAsset as TimelineAssetConfig,
|
||||
TimelineConfig,
|
||||
MockTimelineData as TimelineData,
|
||||
} from './timeline/timeline-config';
|
||||
|
||||
export {
|
||||
getAlbum,
|
||||
getAsset,
|
||||
getTimeBucket,
|
||||
getTimeBuckets,
|
||||
toAssetResponseDto,
|
||||
toColumnarFormat,
|
||||
} from './timeline/rest-response';
|
||||
|
||||
export type { Changes } from './timeline/rest-response';
|
||||
|
||||
export { randomImage, randomImageFromString, randomPreview, randomThumbnail } from './timeline/images';
|
||||
|
||||
export {
|
||||
SeededRandom,
|
||||
getMockAsset,
|
||||
parseTimeBucketKey,
|
||||
selectRandom,
|
||||
selectRandomDays,
|
||||
selectRandomMultiple,
|
||||
} from './timeline/utils';
|
||||
|
||||
export { ASSET_DISTRIBUTION, DAY_DISTRIBUTION } from './timeline/distribution-patterns';
|
||||
export type { DayPattern, MonthDistribution } from './timeline/distribution-patterns';
|
||||
@@ -1,183 +0,0 @@
|
||||
import { generateConsecutiveDays, generateDayAssets } from 'src/generators/timeline/model-objects';
|
||||
import { SeededRandom, selectRandomDays } from 'src/generators/timeline/utils';
|
||||
import type { MockTimelineAsset } from './timeline-config';
|
||||
import { GENERATION_CONSTANTS } from './timeline-config';
|
||||
|
||||
type AssetDistributionStrategy = (rng: SeededRandom) => number;
|
||||
|
||||
type DayDistributionStrategy = (
|
||||
year: number,
|
||||
month: number,
|
||||
daysInMonth: number,
|
||||
totalAssets: number,
|
||||
ownerId: string,
|
||||
rng: SeededRandom,
|
||||
) => MockTimelineAsset[];
|
||||
|
||||
/**
|
||||
* Strategies for determining total asset count per month
|
||||
*/
|
||||
export const ASSET_DISTRIBUTION: Record<MonthDistribution, AssetDistributionStrategy | null> = {
|
||||
empty: null, // Special case - handled separately
|
||||
sparse: (rng) => rng.nextInt(3, 9), // 3-8 assets
|
||||
medium: (rng) => rng.nextInt(15, 31), // 15-30 assets
|
||||
dense: (rng) => rng.nextInt(50, 81), // 50-80 assets
|
||||
'very-dense': (rng) => rng.nextInt(80, 151), // 80-150 assets
|
||||
};
|
||||
|
||||
/**
|
||||
* Strategies for distributing assets across days within a month
|
||||
*/
|
||||
export const DAY_DISTRIBUTION: Record<DayPattern, DayDistributionStrategy> = {
|
||||
'single-day': (year, month, daysInMonth, totalAssets, ownerId, rng) => {
|
||||
// All assets on one day in the middle of the month
|
||||
const day = Math.floor(daysInMonth / 2);
|
||||
return generateDayAssets(year, month, day, totalAssets, ownerId, rng);
|
||||
},
|
||||
|
||||
'consecutive-large': (year, month, daysInMonth, totalAssets, ownerId, rng) => {
|
||||
// 3-5 consecutive days with evenly distributed assets
|
||||
const numDays = Math.min(5, Math.floor(totalAssets / 15));
|
||||
const startDay = rng.nextInt(1, daysInMonth - numDays + 2);
|
||||
return generateConsecutiveDays(year, month, startDay, numDays, totalAssets, ownerId, rng);
|
||||
},
|
||||
|
||||
'consecutive-small': (year, month, daysInMonth, totalAssets, ownerId, rng) => {
|
||||
// Multiple consecutive days with 1-3 assets each (side-by-side layout)
|
||||
const assets: MockTimelineAsset[] = [];
|
||||
const numDays = Math.min(totalAssets, Math.floor(daysInMonth / 2));
|
||||
const startDay = rng.nextInt(1, daysInMonth - numDays + 2);
|
||||
let assetIndex = 0;
|
||||
|
||||
for (let i = 0; i < numDays && assetIndex < totalAssets; i++) {
|
||||
const dayAssets = Math.min(3, rng.nextInt(1, 4));
|
||||
const actualAssets = Math.min(dayAssets, totalAssets - assetIndex);
|
||||
// Create a new RNG for this day
|
||||
const dayRng = new SeededRandom(rng.nextInt(0, 1_000_000));
|
||||
assets.push(...generateDayAssets(year, month, startDay + i, actualAssets, ownerId, dayRng));
|
||||
assetIndex += actualAssets;
|
||||
}
|
||||
return assets;
|
||||
},
|
||||
|
||||
alternating: (year, month, daysInMonth, totalAssets, ownerId, rng) => {
|
||||
// Alternate between large (15-25) and small (1-3) days
|
||||
const assets: MockTimelineAsset[] = [];
|
||||
let day = 1;
|
||||
let isLarge = true;
|
||||
let assetIndex = 0;
|
||||
|
||||
while (assetIndex < totalAssets && day <= daysInMonth) {
|
||||
const dayAssets = isLarge ? Math.min(25, rng.nextInt(15, 26)) : rng.nextInt(1, 4);
|
||||
|
||||
const actualAssets = Math.min(dayAssets, totalAssets - assetIndex);
|
||||
// Create a new RNG for this day
|
||||
const dayRng = new SeededRandom(rng.nextInt(0, 1_000_000));
|
||||
assets.push(...generateDayAssets(year, month, day, actualAssets, ownerId, dayRng));
|
||||
assetIndex += actualAssets;
|
||||
|
||||
day += isLarge ? 1 : 1; // Could add gaps here
|
||||
isLarge = !isLarge;
|
||||
}
|
||||
return assets;
|
||||
},
|
||||
|
||||
'sparse-scattered': (year, month, daysInMonth, totalAssets, ownerId, rng) => {
|
||||
// Spread assets across random days with gaps
|
||||
const assets: MockTimelineAsset[] = [];
|
||||
const numDays = Math.min(totalAssets, Math.floor(daysInMonth * GENERATION_CONSTANTS.SPARSE_DAY_COVERAGE));
|
||||
const daysWithPhotos = selectRandomDays(daysInMonth, numDays, rng);
|
||||
let assetIndex = 0;
|
||||
|
||||
for (let i = 0; i < daysWithPhotos.length && assetIndex < totalAssets; i++) {
|
||||
const dayAssets =
|
||||
Math.floor(totalAssets / numDays) + (i === daysWithPhotos.length - 1 ? totalAssets % numDays : 0);
|
||||
// Create a new RNG for this day
|
||||
const dayRng = new SeededRandom(rng.nextInt(0, 1_000_000));
|
||||
assets.push(...generateDayAssets(year, month, daysWithPhotos[i], dayAssets, ownerId, dayRng));
|
||||
assetIndex += dayAssets;
|
||||
}
|
||||
return assets;
|
||||
},
|
||||
|
||||
'start-heavy': (year, month, daysInMonth, totalAssets, ownerId, rng) => {
|
||||
// Most assets in first week
|
||||
const assets: MockTimelineAsset[] = [];
|
||||
const firstWeekAssets = Math.floor(totalAssets * 0.7);
|
||||
const remainingAssets = totalAssets - firstWeekAssets;
|
||||
|
||||
// First 7 days
|
||||
assets.push(...generateConsecutiveDays(year, month, 1, 7, firstWeekAssets, ownerId, rng));
|
||||
|
||||
// Remaining scattered
|
||||
if (remainingAssets > 0) {
|
||||
const midDay = Math.floor(daysInMonth / 2);
|
||||
// Create a new RNG for the remaining assets
|
||||
const remainingRng = new SeededRandom(rng.nextInt(0, 1_000_000));
|
||||
assets.push(...generateDayAssets(year, month, midDay, remainingAssets, ownerId, remainingRng));
|
||||
}
|
||||
return assets;
|
||||
},
|
||||
|
||||
'end-heavy': (year, month, daysInMonth, totalAssets, ownerId, rng) => {
|
||||
// Most assets in last week
|
||||
const assets: MockTimelineAsset[] = [];
|
||||
const lastWeekAssets = Math.floor(totalAssets * 0.7);
|
||||
const remainingAssets = totalAssets - lastWeekAssets;
|
||||
|
||||
// Remaining at start
|
||||
if (remainingAssets > 0) {
|
||||
// Create a new RNG for the start assets
|
||||
const startRng = new SeededRandom(rng.nextInt(0, 1_000_000));
|
||||
assets.push(...generateDayAssets(year, month, 2, remainingAssets, ownerId, startRng));
|
||||
}
|
||||
|
||||
// Last 7 days
|
||||
const startDay = daysInMonth - 6;
|
||||
assets.push(...generateConsecutiveDays(year, month, startDay, 7, lastWeekAssets, ownerId, rng));
|
||||
return assets;
|
||||
},
|
||||
|
||||
'mid-heavy': (year, month, daysInMonth, totalAssets, ownerId, rng) => {
|
||||
// Most assets in middle of month
|
||||
const assets: MockTimelineAsset[] = [];
|
||||
const midAssets = Math.floor(totalAssets * 0.7);
|
||||
const sideAssets = Math.floor((totalAssets - midAssets) / 2);
|
||||
|
||||
// Start
|
||||
if (sideAssets > 0) {
|
||||
// Create a new RNG for the start assets
|
||||
const startRng = new SeededRandom(rng.nextInt(0, 1_000_000));
|
||||
assets.push(...generateDayAssets(year, month, 2, sideAssets, ownerId, startRng));
|
||||
}
|
||||
|
||||
// Middle
|
||||
const midStart = Math.floor(daysInMonth / 2) - 3;
|
||||
assets.push(...generateConsecutiveDays(year, month, midStart, 7, midAssets, ownerId, rng));
|
||||
|
||||
// End
|
||||
const endAssets = totalAssets - midAssets - sideAssets;
|
||||
if (endAssets > 0) {
|
||||
// Create a new RNG for the end assets
|
||||
const endRng = new SeededRandom(rng.nextInt(0, 1_000_000));
|
||||
assets.push(...generateDayAssets(year, month, daysInMonth - 1, endAssets, ownerId, endRng));
|
||||
}
|
||||
return assets;
|
||||
},
|
||||
};
|
||||
export type MonthDistribution =
|
||||
| 'empty' // 0 assets
|
||||
| 'sparse' // 3-8 assets
|
||||
| 'medium' // 15-30 assets
|
||||
| 'dense' // 50-80 assets
|
||||
| 'very-dense'; // 80-150 assets
|
||||
|
||||
export type DayPattern =
|
||||
| 'single-day' // All images in one day
|
||||
| 'consecutive-large' // Multiple days with 15-25 images each
|
||||
| 'consecutive-small' // Multiple days with 1-3 images each (side-by-side)
|
||||
| 'alternating' // Alternating large/small days
|
||||
| 'sparse-scattered' // Few images scattered across month
|
||||
| 'start-heavy' // Most images at start of month
|
||||
| 'end-heavy' // Most images at end of month
|
||||
| 'mid-heavy'; // Most images in middle of month
|
||||
@@ -1,111 +0,0 @@
|
||||
import sharp from 'sharp';
|
||||
import { SeededRandom } from 'src/generators/timeline/utils';
|
||||
|
||||
export const randomThumbnail = async (seed: string, ratio: number) => {
|
||||
const height = 235;
|
||||
const width = Math.round(height * ratio);
|
||||
return randomImageFromString(seed, { width, height });
|
||||
};
|
||||
|
||||
export const randomPreview = async (seed: string, ratio: number) => {
|
||||
const height = 500;
|
||||
const width = Math.round(height * ratio);
|
||||
return randomImageFromString(seed, { width, height });
|
||||
};
|
||||
|
||||
export const randomImageFromString = async (
|
||||
seed: string = '',
|
||||
{ width = 100, height = 100 }: { width: number; height: number },
|
||||
) => {
|
||||
// Convert string to number for seeding
|
||||
let seedNumber = 0;
|
||||
for (let i = 0; i < seed.length; i++) {
|
||||
seedNumber = (seedNumber << 5) - seedNumber + (seed.codePointAt(i) ?? 0);
|
||||
seedNumber = seedNumber & seedNumber; // Convert to 32bit integer
|
||||
}
|
||||
return randomImage(new SeededRandom(Math.abs(seedNumber)), { width, height });
|
||||
};
|
||||
|
||||
export const randomImage = async (rng: SeededRandom, { width, height }: { width: number; height: number }) => {
|
||||
const r1 = rng.nextInt(0, 256);
|
||||
const g1 = rng.nextInt(0, 256);
|
||||
const b1 = rng.nextInt(0, 256);
|
||||
const r2 = rng.nextInt(0, 256);
|
||||
const g2 = rng.nextInt(0, 256);
|
||||
const b2 = rng.nextInt(0, 256);
|
||||
const patternType = rng.nextInt(0, 5);
|
||||
|
||||
let svgPattern = '';
|
||||
|
||||
switch (patternType) {
|
||||
case 0: {
|
||||
// Solid color
|
||||
svgPattern = `<svg width="${width}" height="${height}">
|
||||
<rect x="0" y="0" width="${width}" height="${height}" fill="rgb(${r1},${g1},${b1})"/>
|
||||
</svg>`;
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: {
|
||||
// Horizontal stripes
|
||||
const stripeHeight = 10;
|
||||
svgPattern = `<svg width="${width}" height="${height}">
|
||||
${Array.from(
|
||||
{ length: height / stripeHeight },
|
||||
(_, i) =>
|
||||
`<rect x="0" y="${i * stripeHeight}" width="${width}" height="${stripeHeight}"
|
||||
fill="rgb(${i % 2 ? r1 : r2},${i % 2 ? g1 : g2},${i % 2 ? b1 : b2})"/>`,
|
||||
).join('')}
|
||||
</svg>`;
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: {
|
||||
// Vertical stripes
|
||||
const stripeWidth = 10;
|
||||
svgPattern = `<svg width="${width}" height="${height}">
|
||||
${Array.from(
|
||||
{ length: width / stripeWidth },
|
||||
(_, i) =>
|
||||
`<rect x="${i * stripeWidth}" y="0" width="${stripeWidth}" height="${height}"
|
||||
fill="rgb(${i % 2 ? r1 : r2},${i % 2 ? g1 : g2},${i % 2 ? b1 : b2})"/>`,
|
||||
).join('')}
|
||||
</svg>`;
|
||||
break;
|
||||
}
|
||||
|
||||
case 3: {
|
||||
// Checkerboard
|
||||
const squareSize = 10;
|
||||
svgPattern = `<svg width="${width}" height="${height}">
|
||||
${Array.from({ length: height / squareSize }, (_, row) =>
|
||||
Array.from({ length: width / squareSize }, (_, col) => {
|
||||
const isEven = (row + col) % 2 === 0;
|
||||
return `<rect x="${col * squareSize}" y="${row * squareSize}"
|
||||
width="${squareSize}" height="${squareSize}"
|
||||
fill="rgb(${isEven ? r1 : r2},${isEven ? g1 : g2},${isEven ? b1 : b2})"/>`;
|
||||
}).join(''),
|
||||
).join('')}
|
||||
</svg>`;
|
||||
break;
|
||||
}
|
||||
|
||||
case 4: {
|
||||
// Diagonal stripes
|
||||
svgPattern = `<svg width="${width}" height="${height}">
|
||||
<defs>
|
||||
<pattern id="diagonal" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
|
||||
<rect x="0" y="0" width="10" height="20" fill="rgb(${r1},${g1},${b1})"/>
|
||||
<rect x="10" y="0" width="10" height="20" fill="rgb(${r2},${g2},${b2})"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect x="0" y="0" width="${width}" height="${height}" fill="url(#diagonal)" transform="rotate(45 50 50)"/>
|
||||
</svg>`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const svgBuffer = Buffer.from(svgPattern);
|
||||
const jpegData = await sharp(svgBuffer).jpeg({ quality: 50 }).toBuffer();
|
||||
return jpegData;
|
||||
};
|
||||
@@ -1,265 +0,0 @@
|
||||
/**
|
||||
* Generator functions for timeline model objects
|
||||
*/
|
||||
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { AssetVisibility } from '@immich/sdk';
|
||||
import { DateTime } from 'luxon';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { SeededRandom } from 'src/generators/timeline/utils';
|
||||
import type { DayPattern, MonthDistribution } from './distribution-patterns';
|
||||
import { ASSET_DISTRIBUTION, DAY_DISTRIBUTION } from './distribution-patterns';
|
||||
import type { MockTimelineAsset, MockTimelineData, SerializedTimelineData, TimelineConfig } from './timeline-config';
|
||||
import { ASPECT_RATIO_WEIGHTS, GENERATION_CONSTANTS, validateTimelineConfig } from './timeline-config';
|
||||
|
||||
/**
|
||||
* Generate a random aspect ratio based on weighted probabilities
|
||||
*/
|
||||
export function generateAspectRatio(rng: SeededRandom): string {
|
||||
const random = rng.next();
|
||||
let cumulative = 0;
|
||||
|
||||
for (const [ratio, weight] of Object.entries(ASPECT_RATIO_WEIGHTS)) {
|
||||
cumulative += weight;
|
||||
if (random < cumulative) {
|
||||
return ratio;
|
||||
}
|
||||
}
|
||||
return '16:9'; // Default fallback
|
||||
}
|
||||
|
||||
export function generateThumbhash(rng: SeededRandom): string {
|
||||
return Array.from({ length: 10 }, () => rng.nextInt(0, 256).toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
export function generateDuration(rng: SeededRandom): string {
|
||||
return `${rng.nextInt(GENERATION_CONSTANTS.MIN_VIDEO_DURATION_SECONDS, GENERATION_CONSTANTS.MAX_VIDEO_DURATION_SECONDS)}.${rng.nextInt(0, 1000).toString().padStart(3, '0')}`;
|
||||
}
|
||||
|
||||
export function generateUUID(): string {
|
||||
return faker.string.uuid();
|
||||
}
|
||||
|
||||
export function generateAsset(
|
||||
year: number,
|
||||
month: number,
|
||||
day: number,
|
||||
ownerId: string,
|
||||
rng: SeededRandom,
|
||||
): MockTimelineAsset {
|
||||
const from = DateTime.fromObject({ year, month, day }).setZone('UTC');
|
||||
const to = from.endOf('day');
|
||||
const date = faker.date.between({ from: from.toJSDate(), to: to.toJSDate() });
|
||||
const isVideo = rng.next() < GENERATION_CONSTANTS.VIDEO_PROBABILITY;
|
||||
|
||||
const assetId = generateUUID();
|
||||
const hasGPS = rng.next() < GENERATION_CONSTANTS.GPS_PERCENTAGE;
|
||||
|
||||
const ratio = generateAspectRatio(rng);
|
||||
|
||||
const asset: MockTimelineAsset = {
|
||||
id: assetId,
|
||||
ownerId,
|
||||
ratio: Number.parseFloat(ratio.split(':')[0]) / Number.parseFloat(ratio.split(':')[1]),
|
||||
thumbhash: generateThumbhash(rng),
|
||||
localDateTime: date.toISOString(),
|
||||
fileCreatedAt: date.toISOString(),
|
||||
isFavorite: rng.next() < GENERATION_CONSTANTS.FAVORITE_PROBABILITY,
|
||||
isTrashed: false,
|
||||
isVideo,
|
||||
isImage: !isVideo,
|
||||
duration: isVideo ? generateDuration(rng) : null,
|
||||
projectionType: null,
|
||||
livePhotoVideoId: null,
|
||||
city: hasGPS ? faker.location.city() : null,
|
||||
country: hasGPS ? faker.location.country() : null,
|
||||
people: null,
|
||||
latitude: hasGPS ? faker.location.latitude() : null,
|
||||
longitude: hasGPS ? faker.location.longitude() : null,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
stack: null,
|
||||
fileSizeInByte: faker.number.int({ min: 510, max: 5_000_000 }),
|
||||
checksum: faker.string.alphanumeric({ length: 5 }),
|
||||
};
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate assets for a specific day
|
||||
*/
|
||||
export function generateDayAssets(
|
||||
year: number,
|
||||
month: number,
|
||||
day: number,
|
||||
assetCount: number,
|
||||
ownerId: string,
|
||||
rng: SeededRandom,
|
||||
): MockTimelineAsset[] {
|
||||
return Array.from({ length: assetCount }, () => generateAsset(year, month, day, ownerId, rng));
|
||||
}
|
||||
|
||||
/**
|
||||
* Distribute assets evenly across consecutive days
|
||||
*
|
||||
* @returns Array of generated timeline assets
|
||||
*/
|
||||
export function generateConsecutiveDays(
|
||||
year: number,
|
||||
month: number,
|
||||
startDay: number,
|
||||
numDays: number,
|
||||
totalAssets: number,
|
||||
ownerId: string,
|
||||
rng: SeededRandom,
|
||||
): MockTimelineAsset[] {
|
||||
const assets: MockTimelineAsset[] = [];
|
||||
const assetsPerDay = Math.floor(totalAssets / numDays);
|
||||
|
||||
for (let i = 0; i < numDays; i++) {
|
||||
const dayAssets =
|
||||
i === numDays - 1
|
||||
? totalAssets - assetsPerDay * (numDays - 1) // Remainder on last day
|
||||
: assetsPerDay;
|
||||
// Create a new RNG with a different seed for each day
|
||||
const dayRng = new SeededRandom(rng.nextInt(0, 1_000_000) + i * 100);
|
||||
assets.push(...generateDayAssets(year, month, startDay + i, dayAssets, ownerId, dayRng));
|
||||
}
|
||||
|
||||
return assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate assets for a month with specified distribution pattern
|
||||
*/
|
||||
export function generateMonthAssets(
|
||||
year: number,
|
||||
month: number,
|
||||
ownerId: string,
|
||||
distribution: MonthDistribution = 'medium',
|
||||
pattern: DayPattern = 'consecutive-large',
|
||||
rng: SeededRandom,
|
||||
): MockTimelineAsset[] {
|
||||
const daysInMonth = new Date(year, month, 0).getDate();
|
||||
|
||||
if (distribution === 'empty') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const distributionStrategy = ASSET_DISTRIBUTION[distribution];
|
||||
if (!distributionStrategy) {
|
||||
console.warn(`Unknown distribution: ${distribution}, defaulting to medium`);
|
||||
return [];
|
||||
}
|
||||
const totalAssets = distributionStrategy(rng);
|
||||
|
||||
const dayStrategy = DAY_DISTRIBUTION[pattern];
|
||||
if (!dayStrategy) {
|
||||
console.warn(`Unknown pattern: ${pattern}, defaulting to consecutive-large`);
|
||||
// Fallback to consecutive-large pattern
|
||||
const numDays = Math.min(5, Math.floor(totalAssets / 15));
|
||||
const startDay = rng.nextInt(1, daysInMonth - numDays + 2);
|
||||
const assets = generateConsecutiveDays(year, month, startDay, numDays, totalAssets, ownerId, rng);
|
||||
assets.sort((a, b) => DateTime.fromISO(b.localDateTime).diff(DateTime.fromISO(a.localDateTime)).milliseconds);
|
||||
return assets;
|
||||
}
|
||||
|
||||
const assets = dayStrategy(year, month, daysInMonth, totalAssets, ownerId, rng);
|
||||
assets.sort((a, b) => DateTime.fromISO(b.localDateTime).diff(DateTime.fromISO(a.localDateTime)).milliseconds);
|
||||
return assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main generator function for timeline data
|
||||
*/
|
||||
export function generateTimelineData(config: TimelineConfig): MockTimelineData {
|
||||
validateTimelineConfig(config);
|
||||
|
||||
const buckets = new Map<string, MockTimelineAsset[]>();
|
||||
const monthStats: Record<string, { count: number; distribution: MonthDistribution; pattern: DayPattern }> = {};
|
||||
|
||||
const globalRng = new SeededRandom(config.seed || GENERATION_CONSTANTS.DEFAULT_SEED);
|
||||
faker.seed(globalRng.nextInt(0, 1_000_000));
|
||||
for (const monthConfig of config.months) {
|
||||
const { year, month, distribution, pattern } = monthConfig;
|
||||
|
||||
const monthSeed = globalRng.nextInt(0, 1_000_000);
|
||||
const monthRng = new SeededRandom(monthSeed);
|
||||
|
||||
const monthAssets = generateMonthAssets(
|
||||
year,
|
||||
month,
|
||||
config.ownerId || generateUUID(),
|
||||
distribution,
|
||||
pattern,
|
||||
monthRng,
|
||||
);
|
||||
|
||||
if (monthAssets.length > 0) {
|
||||
const monthKey = `${year}-${month.toString().padStart(2, '0')}`;
|
||||
monthStats[monthKey] = {
|
||||
count: monthAssets.length,
|
||||
distribution,
|
||||
pattern,
|
||||
};
|
||||
|
||||
// Create bucket key (YYYY-MM-01)
|
||||
const bucketKey = `${year}-${month.toString().padStart(2, '0')}-01`;
|
||||
buckets.set(bucketKey, monthAssets);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a mock album from random assets
|
||||
const allAssets = [...buckets.values()].flat();
|
||||
|
||||
// Select 10-30 random assets for the album (or all assets if less than 10)
|
||||
const albumSize = Math.min(allAssets.length, globalRng.nextInt(10, 31));
|
||||
const selectedAssetConfigs: MockTimelineAsset[] = [];
|
||||
const usedIndices = new Set<number>();
|
||||
|
||||
while (selectedAssetConfigs.length < albumSize && usedIndices.size < allAssets.length) {
|
||||
const randomIndex = globalRng.nextInt(0, allAssets.length);
|
||||
if (!usedIndices.has(randomIndex)) {
|
||||
usedIndices.add(randomIndex);
|
||||
selectedAssetConfigs.push(allAssets[randomIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort selected assets by date (newest first)
|
||||
selectedAssetConfigs.sort(
|
||||
(a, b) => DateTime.fromISO(b.localDateTime).diff(DateTime.fromISO(a.localDateTime)).milliseconds,
|
||||
);
|
||||
|
||||
const selectedAssets = selectedAssetConfigs.map((asset) => asset.id);
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const album = {
|
||||
id: generateUUID(),
|
||||
albumName: 'Test Album',
|
||||
description: 'A mock album for testing',
|
||||
assetIds: selectedAssets,
|
||||
thumbnailAssetId: selectedAssets.length > 0 ? selectedAssets[0] : null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
// Write to file if configured
|
||||
if (config.writeToFile) {
|
||||
const outputPath = config.outputPath || '/tmp/timeline-data.json';
|
||||
|
||||
// Convert Map to object for serialization
|
||||
const serializedData: SerializedTimelineData = {
|
||||
buckets: Object.fromEntries(buckets),
|
||||
album,
|
||||
};
|
||||
|
||||
try {
|
||||
writeFileSync(outputPath, JSON.stringify(serializedData, null, 2));
|
||||
console.log(`Timeline data written to ${outputPath}`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to write timeline data to ${outputPath}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return { buckets, album };
|
||||
}
|
||||
@@ -1,436 +0,0 @@
|
||||
/**
|
||||
* REST API output functions for converting timeline data to API response formats
|
||||
*/
|
||||
|
||||
import {
|
||||
AssetTypeEnum,
|
||||
AssetVisibility,
|
||||
UserAvatarColor,
|
||||
type AlbumResponseDto,
|
||||
type AssetResponseDto,
|
||||
type ExifResponseDto,
|
||||
type TimeBucketAssetResponseDto,
|
||||
type TimeBucketsResponseDto,
|
||||
type UserResponseDto,
|
||||
} from '@immich/sdk';
|
||||
import { DateTime } from 'luxon';
|
||||
import { signupDto } from 'src/fixtures';
|
||||
import { parseTimeBucketKey } from 'src/generators/timeline/utils';
|
||||
import type { MockTimelineAsset, MockTimelineData } from './timeline-config';
|
||||
|
||||
/**
|
||||
* Convert timeline/asset models to columnar format (parallel arrays)
|
||||
*/
|
||||
export function toColumnarFormat(assets: MockTimelineAsset[]): TimeBucketAssetResponseDto {
|
||||
const result: TimeBucketAssetResponseDto = {
|
||||
id: [],
|
||||
ownerId: [],
|
||||
ratio: [],
|
||||
thumbhash: [],
|
||||
fileCreatedAt: [],
|
||||
localOffsetHours: [],
|
||||
isFavorite: [],
|
||||
isTrashed: [],
|
||||
isImage: [],
|
||||
duration: [],
|
||||
projectionType: [],
|
||||
livePhotoVideoId: [],
|
||||
city: [],
|
||||
country: [],
|
||||
visibility: [],
|
||||
};
|
||||
|
||||
for (const asset of assets) {
|
||||
result.id.push(asset.id);
|
||||
result.ownerId.push(asset.ownerId);
|
||||
result.ratio.push(asset.ratio);
|
||||
result.thumbhash.push(asset.thumbhash);
|
||||
result.fileCreatedAt.push(asset.fileCreatedAt);
|
||||
result.localOffsetHours.push(0); // Assuming UTC for mocks
|
||||
result.isFavorite.push(asset.isFavorite);
|
||||
result.isTrashed.push(asset.isTrashed);
|
||||
result.isImage.push(asset.isImage);
|
||||
result.duration.push(asset.duration);
|
||||
result.projectionType.push(asset.projectionType);
|
||||
result.livePhotoVideoId.push(asset.livePhotoVideoId);
|
||||
result.city.push(asset.city);
|
||||
result.country.push(asset.country);
|
||||
result.visibility.push(asset.visibility);
|
||||
}
|
||||
|
||||
if (assets.some((a) => a.latitude !== null || a.longitude !== null)) {
|
||||
result.latitude = assets.map((a) => a.latitude);
|
||||
result.longitude = assets.map((a) => a.longitude);
|
||||
}
|
||||
|
||||
result.stack = assets.map(() => null);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a single bucket from timeline data (mimics getTimeBucket API)
|
||||
* Automatically handles both ISO timestamp and simple month formats
|
||||
* Returns data in columnar format matching the actual API
|
||||
* When albumId is provided, only returns assets from that album
|
||||
*/
|
||||
export function getTimeBucket(
|
||||
timelineData: MockTimelineData,
|
||||
timeBucket: string,
|
||||
isTrashed: boolean | undefined,
|
||||
isArchived: boolean | undefined,
|
||||
isFavorite: boolean | undefined,
|
||||
albumId: string | undefined,
|
||||
changes: Changes,
|
||||
): TimeBucketAssetResponseDto {
|
||||
const bucketKey = parseTimeBucketKey(timeBucket);
|
||||
let assets = timelineData.buckets.get(bucketKey);
|
||||
|
||||
if (!assets) {
|
||||
return toColumnarFormat([]);
|
||||
}
|
||||
|
||||
// Create sets for quick lookups
|
||||
const deletedAssetIds = new Set(changes.assetDeletions);
|
||||
const archivedAssetIds = new Set(changes.assetArchivals);
|
||||
const favoritedAssetIds = new Set(changes.assetFavorites);
|
||||
|
||||
// Filter assets based on trashed/archived status
|
||||
assets = assets.filter((asset) =>
|
||||
shouldIncludeAsset(asset, isTrashed, isArchived, isFavorite, deletedAssetIds, archivedAssetIds, favoritedAssetIds),
|
||||
);
|
||||
|
||||
// Filter to only include assets from the specified album
|
||||
if (albumId) {
|
||||
const album = timelineData.album;
|
||||
if (!album || album.id !== albumId) {
|
||||
return toColumnarFormat([]);
|
||||
}
|
||||
|
||||
// Create a Set for faster lookup
|
||||
const albumAssetIds = new Set([...album.assetIds, ...changes.albumAdditions]);
|
||||
assets = assets.filter((asset) => albumAssetIds.has(asset.id));
|
||||
}
|
||||
|
||||
// Override properties for assets in changes arrays
|
||||
const assetsWithOverrides = assets.map((asset) => {
|
||||
if (deletedAssetIds.has(asset.id) || archivedAssetIds.has(asset.id) || favoritedAssetIds.has(asset.id)) {
|
||||
return {
|
||||
...asset,
|
||||
isFavorite: favoritedAssetIds.has(asset.id) ? true : asset.isFavorite,
|
||||
isTrashed: deletedAssetIds.has(asset.id) ? true : asset.isTrashed,
|
||||
visibility: archivedAssetIds.has(asset.id) ? AssetVisibility.Archive : asset.visibility,
|
||||
};
|
||||
}
|
||||
return asset;
|
||||
});
|
||||
|
||||
return toColumnarFormat(assetsWithOverrides);
|
||||
}
|
||||
|
||||
export type Changes = {
|
||||
// ids of assets that are newly added to the album
|
||||
albumAdditions: string[];
|
||||
// ids of assets that are newly deleted
|
||||
assetDeletions: string[];
|
||||
// ids of assets that are newly archived
|
||||
assetArchivals: string[];
|
||||
// ids of assets that are newly favorited
|
||||
assetFavorites: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to determine if an asset should be included based on filter criteria
|
||||
* @param asset - The asset to check
|
||||
* @param isTrashed - Filter for trashed status (undefined means no filter)
|
||||
* @param isArchived - Filter for archived status (undefined means no filter)
|
||||
* @param isFavorite - Filter for favorite status (undefined means no filter)
|
||||
* @param deletedAssetIds - Set of IDs for assets that have been deleted
|
||||
* @param archivedAssetIds - Set of IDs for assets that have been archived
|
||||
* @param favoritedAssetIds - Set of IDs for assets that have been favorited
|
||||
* @returns true if the asset matches all filter criteria
|
||||
*/
|
||||
function shouldIncludeAsset(
|
||||
asset: MockTimelineAsset,
|
||||
isTrashed: boolean | undefined,
|
||||
isArchived: boolean | undefined,
|
||||
isFavorite: boolean | undefined,
|
||||
deletedAssetIds: Set<string>,
|
||||
archivedAssetIds: Set<string>,
|
||||
favoritedAssetIds: Set<string>,
|
||||
): boolean {
|
||||
// Determine actual status (property or in changes)
|
||||
const actuallyTrashed = asset.isTrashed || deletedAssetIds.has(asset.id);
|
||||
const actuallyArchived = asset.visibility === 'archive' || archivedAssetIds.has(asset.id);
|
||||
const actuallyFavorited = asset.isFavorite || favoritedAssetIds.has(asset.id);
|
||||
|
||||
// Apply filters
|
||||
if (isTrashed !== undefined && actuallyTrashed !== isTrashed) {
|
||||
return false;
|
||||
}
|
||||
if (isArchived !== undefined && actuallyArchived !== isArchived) {
|
||||
return false;
|
||||
}
|
||||
if (isFavorite !== undefined && actuallyFavorited !== isFavorite) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Get summary for all buckets (mimics getTimeBuckets API)
|
||||
* When albumId is provided, only includes buckets that contain assets from that album
|
||||
*/
|
||||
export function getTimeBuckets(
|
||||
timelineData: MockTimelineData,
|
||||
isTrashed: boolean | undefined,
|
||||
isArchived: boolean | undefined,
|
||||
isFavorite: boolean | undefined,
|
||||
albumId: string | undefined,
|
||||
changes: Changes,
|
||||
): TimeBucketsResponseDto[] {
|
||||
const summary: TimeBucketsResponseDto[] = [];
|
||||
|
||||
// Create sets for quick lookups
|
||||
const deletedAssetIds = new Set(changes.assetDeletions);
|
||||
const archivedAssetIds = new Set(changes.assetArchivals);
|
||||
const favoritedAssetIds = new Set(changes.assetFavorites);
|
||||
|
||||
// If no albumId is specified, return summary for all assets
|
||||
if (albumId) {
|
||||
// Filter to only include buckets with assets from the specified album
|
||||
const album = timelineData.album;
|
||||
if (!album || album.id !== albumId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Create a Set for faster lookup
|
||||
const albumAssetIds = new Set([...album.assetIds, ...changes.albumAdditions]);
|
||||
for (const removed of changes.assetDeletions) {
|
||||
albumAssetIds.delete(removed);
|
||||
}
|
||||
for (const [bucketKey, assets] of timelineData.buckets) {
|
||||
// Count how many assets in this bucket are in the album and match trashed/archived filters
|
||||
const albumAssetsInBucket = assets.filter((asset) => {
|
||||
// Must be in the album
|
||||
if (!albumAssetIds.has(asset.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return shouldIncludeAsset(
|
||||
asset,
|
||||
isTrashed,
|
||||
isArchived,
|
||||
isFavorite,
|
||||
deletedAssetIds,
|
||||
archivedAssetIds,
|
||||
favoritedAssetIds,
|
||||
);
|
||||
});
|
||||
|
||||
if (albumAssetsInBucket.length > 0) {
|
||||
summary.push({
|
||||
timeBucket: bucketKey,
|
||||
count: albumAssetsInBucket.length,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const [bucketKey, assets] of timelineData.buckets) {
|
||||
// Filter assets based on trashed/archived status
|
||||
const filteredAssets = assets.filter((asset) =>
|
||||
shouldIncludeAsset(
|
||||
asset,
|
||||
isTrashed,
|
||||
isArchived,
|
||||
isFavorite,
|
||||
deletedAssetIds,
|
||||
archivedAssetIds,
|
||||
favoritedAssetIds,
|
||||
),
|
||||
);
|
||||
|
||||
if (filteredAssets.length > 0) {
|
||||
summary.push({
|
||||
timeBucket: bucketKey,
|
||||
count: filteredAssets.length,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort summary by date (newest first) using luxon
|
||||
summary.sort((a, b) => {
|
||||
const dateA = DateTime.fromISO(a.timeBucket);
|
||||
const dateB = DateTime.fromISO(b.timeBucket);
|
||||
return dateB.diff(dateA).milliseconds;
|
||||
});
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
const createDefaultOwner = (ownerId: string) => {
|
||||
const defaultOwner: UserResponseDto = {
|
||||
id: ownerId,
|
||||
email: signupDto.admin.email,
|
||||
name: signupDto.admin.name,
|
||||
profileImagePath: '',
|
||||
profileChangedAt: new Date().toISOString(),
|
||||
avatarColor: UserAvatarColor.Blue,
|
||||
};
|
||||
return defaultOwner;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a TimelineAssetConfig to a full AssetResponseDto
|
||||
* This matches the response from GET /api/assets/:id
|
||||
*/
|
||||
export function toAssetResponseDto(asset: MockTimelineAsset, owner?: UserResponseDto): AssetResponseDto {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
// Default owner if not provided
|
||||
const defaultOwner = createDefaultOwner(asset.ownerId);
|
||||
|
||||
const exifInfo: ExifResponseDto = {
|
||||
make: null,
|
||||
model: null,
|
||||
exifImageWidth: asset.ratio > 1 ? 4000 : 3000,
|
||||
exifImageHeight: asset.ratio > 1 ? Math.round(4000 / asset.ratio) : Math.round(3000 * asset.ratio),
|
||||
fileSizeInByte: asset.fileSizeInByte,
|
||||
orientation: '1',
|
||||
dateTimeOriginal: asset.fileCreatedAt,
|
||||
modifyDate: asset.fileCreatedAt,
|
||||
timeZone: asset.latitude === null ? null : 'UTC',
|
||||
lensModel: null,
|
||||
fNumber: null,
|
||||
focalLength: null,
|
||||
iso: null,
|
||||
exposureTime: null,
|
||||
latitude: asset.latitude,
|
||||
longitude: asset.longitude,
|
||||
city: asset.city,
|
||||
country: asset.country,
|
||||
state: null,
|
||||
description: null,
|
||||
};
|
||||
|
||||
return {
|
||||
id: asset.id,
|
||||
deviceAssetId: `device-${asset.id}`,
|
||||
ownerId: asset.ownerId,
|
||||
owner: owner || defaultOwner,
|
||||
libraryId: `library-${asset.ownerId}`,
|
||||
deviceId: `device-${asset.ownerId}`,
|
||||
type: asset.isVideo ? AssetTypeEnum.Video : AssetTypeEnum.Image,
|
||||
originalPath: `/original/${asset.id}.${asset.isVideo ? 'mp4' : 'jpg'}`,
|
||||
originalFileName: `${asset.id}.${asset.isVideo ? 'mp4' : 'jpg'}`,
|
||||
originalMimeType: asset.isVideo ? 'video/mp4' : 'image/jpeg',
|
||||
thumbhash: asset.thumbhash,
|
||||
fileCreatedAt: asset.fileCreatedAt,
|
||||
fileModifiedAt: asset.fileCreatedAt,
|
||||
localDateTime: asset.localDateTime,
|
||||
updatedAt: now,
|
||||
createdAt: asset.fileCreatedAt,
|
||||
isFavorite: asset.isFavorite,
|
||||
isArchived: false,
|
||||
isTrashed: asset.isTrashed,
|
||||
visibility: asset.visibility,
|
||||
duration: asset.duration || '0:00:00.00000',
|
||||
exifInfo,
|
||||
livePhotoVideoId: asset.livePhotoVideoId,
|
||||
tags: [],
|
||||
people: [],
|
||||
unassignedFaces: [],
|
||||
stack: asset.stack,
|
||||
isOffline: false,
|
||||
hasMetadata: true,
|
||||
duplicateId: null,
|
||||
resized: true,
|
||||
checksum: asset.checksum,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single asset by ID from timeline data
|
||||
* This matches the response from GET /api/assets/:id
|
||||
*/
|
||||
export function getAsset(
|
||||
timelineData: MockTimelineData,
|
||||
assetId: string,
|
||||
owner?: UserResponseDto,
|
||||
): AssetResponseDto | undefined {
|
||||
// Search through all buckets for the asset
|
||||
const buckets = [...timelineData.buckets.values()];
|
||||
for (const assets of buckets) {
|
||||
const asset = assets.find((a) => a.id === assetId);
|
||||
if (asset) {
|
||||
return toAssetResponseDto(asset, owner);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mock album from timeline data
|
||||
* This matches the response from GET /api/albums/:id
|
||||
*/
|
||||
export function getAlbum(
|
||||
timelineData: MockTimelineData,
|
||||
ownerId: string,
|
||||
albumId: string | undefined,
|
||||
changes: Changes,
|
||||
): AlbumResponseDto | undefined {
|
||||
if (!timelineData.album) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If albumId is provided and doesn't match, return undefined
|
||||
if (albumId && albumId !== timelineData.album.id) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const album = timelineData.album;
|
||||
const albumOwner = createDefaultOwner(ownerId);
|
||||
|
||||
// Get the actual asset objects from the timeline data
|
||||
const albumAssets: AssetResponseDto[] = [];
|
||||
const allAssets = [...timelineData.buckets.values()].flat();
|
||||
|
||||
for (const assetId of album.assetIds) {
|
||||
const assetConfig = allAssets.find((a) => a.id === assetId);
|
||||
if (assetConfig) {
|
||||
albumAssets.push(toAssetResponseDto(assetConfig, albumOwner));
|
||||
}
|
||||
}
|
||||
for (const assetId of changes.albumAdditions ?? []) {
|
||||
const assetConfig = allAssets.find((a) => a.id === assetId);
|
||||
if (assetConfig) {
|
||||
albumAssets.push(toAssetResponseDto(assetConfig, albumOwner));
|
||||
}
|
||||
}
|
||||
|
||||
albumAssets.sort((a, b) => DateTime.fromISO(b.localDateTime).diff(DateTime.fromISO(a.localDateTime)).milliseconds);
|
||||
|
||||
// For a basic mock album, we don't include any albumUsers (shared users)
|
||||
// The owner is represented by the owner field, not in albumUsers
|
||||
const response: AlbumResponseDto = {
|
||||
id: album.id,
|
||||
albumName: album.albumName,
|
||||
description: album.description,
|
||||
albumThumbnailAssetId: album.thumbnailAssetId,
|
||||
createdAt: album.createdAt,
|
||||
updatedAt: album.updatedAt,
|
||||
ownerId: albumOwner.id,
|
||||
owner: albumOwner,
|
||||
albumUsers: [], // Empty array for non-shared album
|
||||
shared: false,
|
||||
hasSharedLink: false,
|
||||
isActivityEnabled: true,
|
||||
assetCount: albumAssets.length,
|
||||
assets: albumAssets,
|
||||
startDate: albumAssets.length > 0 ? albumAssets.at(-1)?.fileCreatedAt : undefined,
|
||||
endDate: albumAssets.length > 0 ? albumAssets[0].fileCreatedAt : undefined,
|
||||
lastModifiedAssetTimestamp: albumAssets.length > 0 ? albumAssets[0].fileCreatedAt : undefined,
|
||||
};
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
import type { AssetVisibility } from '@immich/sdk';
|
||||
import { DayPattern, MonthDistribution } from 'src/generators/timeline/distribution-patterns';
|
||||
|
||||
// Constants for generation parameters
|
||||
export const GENERATION_CONSTANTS = {
|
||||
VIDEO_PROBABILITY: 0.15, // 15% of assets are videos
|
||||
GPS_PERCENTAGE: 0.7, // 70% of assets have GPS data
|
||||
FAVORITE_PROBABILITY: 0.1, // 10% of assets are favorited
|
||||
MIN_VIDEO_DURATION_SECONDS: 5,
|
||||
MAX_VIDEO_DURATION_SECONDS: 300,
|
||||
DEFAULT_SEED: 12_345,
|
||||
DEFAULT_OWNER_ID: 'user-1',
|
||||
MAX_SELECT_ATTEMPTS: 10,
|
||||
SPARSE_DAY_COVERAGE: 0.4, // 40% of days have photos in sparse pattern
|
||||
} as const;
|
||||
|
||||
// Aspect ratio distribution weights (must sum to 1)
|
||||
export const ASPECT_RATIO_WEIGHTS = {
|
||||
'4:3': 0.35, // 35% 4:3 landscape
|
||||
'3:2': 0.25, // 25% 3:2 landscape
|
||||
'16:9': 0.2, // 20% 16:9 landscape
|
||||
'2:3': 0.1, // 10% 2:3 portrait
|
||||
'1:1': 0.09, // 9% 1:1 square
|
||||
'3:1': 0.01, // 1% 3:1 panorama
|
||||
} as const;
|
||||
|
||||
export type AspectRatio = {
|
||||
width: number;
|
||||
height: number;
|
||||
ratio: number;
|
||||
name: string;
|
||||
};
|
||||
|
||||
// Mock configuration for asset generation - will be transformed to API response formats
|
||||
export type MockTimelineAsset = {
|
||||
id: string;
|
||||
ownerId: string;
|
||||
ratio: number;
|
||||
thumbhash: string | null;
|
||||
localDateTime: string;
|
||||
fileCreatedAt: string;
|
||||
isFavorite: boolean;
|
||||
isTrashed: boolean;
|
||||
isVideo: boolean;
|
||||
isImage: boolean;
|
||||
duration: string | null;
|
||||
projectionType: string | null;
|
||||
livePhotoVideoId: string | null;
|
||||
city: string | null;
|
||||
country: string | null;
|
||||
people: string[] | null;
|
||||
latitude: number | null;
|
||||
longitude: number | null;
|
||||
visibility: AssetVisibility;
|
||||
stack: null;
|
||||
checksum: string;
|
||||
fileSizeInByte: number;
|
||||
};
|
||||
|
||||
export type MonthSpec = {
|
||||
year: number;
|
||||
month: number; // 1-12
|
||||
distribution: MonthDistribution;
|
||||
pattern: DayPattern;
|
||||
};
|
||||
|
||||
/**
|
||||
* Configuration for timeline data generation
|
||||
*/
|
||||
export type TimelineConfig = {
|
||||
ownerId?: string;
|
||||
months: MonthSpec[];
|
||||
seed?: number;
|
||||
writeToFile?: boolean;
|
||||
outputPath?: string;
|
||||
};
|
||||
|
||||
export type MockAlbum = {
|
||||
id: string;
|
||||
albumName: string;
|
||||
description: string;
|
||||
assetIds: string[]; // IDs of assets in the album
|
||||
thumbnailAssetId: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type MockTimelineData = {
|
||||
buckets: Map<string, MockTimelineAsset[]>;
|
||||
album: MockAlbum; // Mock album created from random assets
|
||||
};
|
||||
|
||||
export type SerializedTimelineData = {
|
||||
buckets: Record<string, MockTimelineAsset[]>;
|
||||
album: MockAlbum;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates a TimelineConfig object to ensure all values are within expected ranges
|
||||
*/
|
||||
export function validateTimelineConfig(config: TimelineConfig): void {
|
||||
if (!config.months || config.months.length === 0) {
|
||||
throw new Error('TimelineConfig must contain at least one month');
|
||||
}
|
||||
|
||||
const seenMonths = new Set<string>();
|
||||
|
||||
for (const month of config.months) {
|
||||
if (month.month < 1 || month.month > 12) {
|
||||
throw new Error(`Invalid month: ${month.month}. Must be between 1 and 12`);
|
||||
}
|
||||
|
||||
if (month.year < 1900 || month.year > 2100) {
|
||||
throw new Error(`Invalid year: ${month.year}. Must be between 1900 and 2100`);
|
||||
}
|
||||
|
||||
const monthKey = `${month.year}-${month.month}`;
|
||||
if (seenMonths.has(monthKey)) {
|
||||
throw new Error(`Duplicate month found: ${monthKey}`);
|
||||
}
|
||||
seenMonths.add(monthKey);
|
||||
|
||||
// Validate distribution if provided
|
||||
if (month.distribution && !['empty', 'sparse', 'medium', 'dense', 'very-dense'].includes(month.distribution)) {
|
||||
throw new Error(
|
||||
`Invalid distribution: ${month.distribution}. Must be one of: empty, sparse, medium, dense, very-dense`,
|
||||
);
|
||||
}
|
||||
|
||||
const validPatterns = [
|
||||
'single-day',
|
||||
'consecutive-large',
|
||||
'consecutive-small',
|
||||
'alternating',
|
||||
'sparse-scattered',
|
||||
'start-heavy',
|
||||
'end-heavy',
|
||||
'mid-heavy',
|
||||
];
|
||||
if (month.pattern && !validPatterns.includes(month.pattern)) {
|
||||
throw new Error(`Invalid pattern: ${month.pattern}. Must be one of: ${validPatterns.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate seed if provided
|
||||
if (config.seed !== undefined && (config.seed < 0 || !Number.isInteger(config.seed))) {
|
||||
throw new Error('Seed must be a non-negative integer');
|
||||
}
|
||||
|
||||
// Validate ownerId if provided
|
||||
if (config.ownerId !== undefined && config.ownerId.trim() === '') {
|
||||
throw new Error('Owner ID cannot be an empty string');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a default timeline configuration
|
||||
*/
|
||||
export function createDefaultTimelineConfig(): TimelineConfig {
|
||||
const months: MonthSpec[] = [
|
||||
// 2024 - Mix of patterns
|
||||
{ year: 2024, month: 12, distribution: 'very-dense', pattern: 'alternating' },
|
||||
{ year: 2024, month: 11, distribution: 'dense', pattern: 'consecutive-large' },
|
||||
{ year: 2024, month: 10, distribution: 'medium', pattern: 'mid-heavy' },
|
||||
{ year: 2024, month: 9, distribution: 'sparse', pattern: 'consecutive-small' },
|
||||
{ year: 2024, month: 8, distribution: 'empty', pattern: 'single-day' },
|
||||
{ year: 2024, month: 7, distribution: 'dense', pattern: 'start-heavy' },
|
||||
{ year: 2024, month: 6, distribution: 'medium', pattern: 'sparse-scattered' },
|
||||
{ year: 2024, month: 5, distribution: 'sparse', pattern: 'single-day' },
|
||||
{ year: 2024, month: 4, distribution: 'very-dense', pattern: 'consecutive-large' },
|
||||
{ year: 2024, month: 3, distribution: 'empty', pattern: 'single-day' },
|
||||
{ year: 2024, month: 2, distribution: 'medium', pattern: 'end-heavy' },
|
||||
{ year: 2024, month: 1, distribution: 'dense', pattern: 'alternating' },
|
||||
|
||||
// 2023 - Testing year boundaries and more patterns
|
||||
{ year: 2023, month: 12, distribution: 'very-dense', pattern: 'end-heavy' },
|
||||
{ year: 2023, month: 11, distribution: 'sparse', pattern: 'consecutive-small' },
|
||||
{ year: 2023, month: 10, distribution: 'empty', pattern: 'single-day' },
|
||||
{ year: 2023, month: 9, distribution: 'medium', pattern: 'alternating' },
|
||||
{ year: 2023, month: 8, distribution: 'dense', pattern: 'mid-heavy' },
|
||||
{ year: 2023, month: 7, distribution: 'sparse', pattern: 'sparse-scattered' },
|
||||
{ year: 2023, month: 6, distribution: 'medium', pattern: 'consecutive-large' },
|
||||
{ year: 2023, month: 5, distribution: 'empty', pattern: 'single-day' },
|
||||
{ year: 2023, month: 4, distribution: 'sparse', pattern: 'single-day' },
|
||||
{ year: 2023, month: 3, distribution: 'dense', pattern: 'start-heavy' },
|
||||
{ year: 2023, month: 2, distribution: 'medium', pattern: 'alternating' },
|
||||
{ year: 2023, month: 1, distribution: 'very-dense', pattern: 'consecutive-large' },
|
||||
];
|
||||
|
||||
for (let year = 2022; year >= 2000; year--) {
|
||||
for (let month = 12; month >= 1; month--) {
|
||||
months.push({ year, month, distribution: 'medium', pattern: 'sparse-scattered' });
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
months,
|
||||
seed: 42,
|
||||
};
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { GENERATION_CONSTANTS, MockTimelineAsset } from 'src/generators/timeline/timeline-config';
|
||||
|
||||
/**
|
||||
* Linear Congruential Generator for deterministic pseudo-random numbers
|
||||
*/
|
||||
export class SeededRandom {
|
||||
private seed: number;
|
||||
|
||||
constructor(seed: number) {
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate next random number in range [0, 1)
|
||||
*/
|
||||
next(): number {
|
||||
// LCG parameters from Numerical Recipes
|
||||
this.seed = (this.seed * 1_664_525 + 1_013_904_223) % 2_147_483_647;
|
||||
return this.seed / 2_147_483_647;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random integer in range [min, max)
|
||||
*/
|
||||
nextInt(min: number, max: number): number {
|
||||
return Math.floor(this.next() * (max - min)) + min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random boolean with given probability
|
||||
*/
|
||||
nextBoolean(probability = 0.5): boolean {
|
||||
return this.next() < probability;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select random days using seed variation to avoid collisions.
|
||||
*
|
||||
* @param daysInMonth - Total number of days in the month
|
||||
* @param numDays - Number of days to select
|
||||
* @param rng - Random number generator instance
|
||||
* @returns Array of selected day numbers, sorted in descending order
|
||||
*/
|
||||
export function selectRandomDays(daysInMonth: number, numDays: number, rng: SeededRandom): number[] {
|
||||
const selectedDays = new Set<number>();
|
||||
const maxAttempts = numDays * GENERATION_CONSTANTS.MAX_SELECT_ATTEMPTS; // Safety limit
|
||||
let attempts = 0;
|
||||
|
||||
while (selectedDays.size < numDays && attempts < maxAttempts) {
|
||||
const day = rng.nextInt(1, daysInMonth + 1);
|
||||
selectedDays.add(day);
|
||||
attempts++;
|
||||
}
|
||||
|
||||
// Fallback: if we couldn't select enough random days, fill with sequential days
|
||||
if (selectedDays.size < numDays) {
|
||||
for (let day = 1; day <= daysInMonth && selectedDays.size < numDays; day++) {
|
||||
selectedDays.add(day);
|
||||
}
|
||||
}
|
||||
|
||||
return [...selectedDays].toSorted((a, b) => b - a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select item from array using seeded random
|
||||
*/
|
||||
export function selectRandom<T>(arr: T[], rng: SeededRandom): T {
|
||||
if (arr.length === 0) {
|
||||
throw new Error('Cannot select from empty array');
|
||||
}
|
||||
const index = rng.nextInt(0, arr.length);
|
||||
return arr[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Select multiple random items from array using seeded random without duplicates
|
||||
*/
|
||||
export function selectRandomMultiple<T>(arr: T[], count: number, rng: SeededRandom): T[] {
|
||||
if (arr.length === 0) {
|
||||
throw new Error('Cannot select from empty array');
|
||||
}
|
||||
if (count < 0) {
|
||||
throw new Error('Count must be non-negative');
|
||||
}
|
||||
if (count > arr.length) {
|
||||
throw new Error('Count cannot exceed array length');
|
||||
}
|
||||
|
||||
const result: T[] = [];
|
||||
const selectedIndices = new Set<number>();
|
||||
|
||||
while (result.length < count) {
|
||||
const index = rng.nextInt(0, arr.length);
|
||||
if (!selectedIndices.has(index)) {
|
||||
selectedIndices.add(index);
|
||||
result.push(arr[index]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse timeBucket parameter to extract year-month key
|
||||
* Handles both formats:
|
||||
* - ISO timestamp: "2024-12-01T00:00:00.000Z" -> "2024-12-01"
|
||||
* - Simple format: "2024-12-01" -> "2024-12-01"
|
||||
*/
|
||||
export function parseTimeBucketKey(timeBucket: string): string {
|
||||
if (!timeBucket) {
|
||||
throw new Error('timeBucket parameter cannot be empty');
|
||||
}
|
||||
|
||||
const dt = DateTime.fromISO(timeBucket, { zone: 'utc' });
|
||||
|
||||
if (!dt.isValid) {
|
||||
// Fallback to regex if not a valid ISO string
|
||||
const match = timeBucket.match(/^(\d{4}-\d{2}-\d{2})/);
|
||||
return match ? match[1] : timeBucket;
|
||||
}
|
||||
|
||||
// Format as YYYY-MM-01 (first day of month)
|
||||
return `${dt.year}-${String(dt.month).padStart(2, '0')}-01`;
|
||||
}
|
||||
|
||||
export function getMockAsset(
|
||||
asset: MockTimelineAsset,
|
||||
sortedDescendingAssets: MockTimelineAsset[],
|
||||
direction: 'next' | 'previous',
|
||||
unit: 'day' | 'month' | 'year' = 'day',
|
||||
): MockTimelineAsset | null {
|
||||
const currentDateTime = DateTime.fromISO(asset.localDateTime, { zone: 'utc' });
|
||||
|
||||
const currentIndex = sortedDescendingAssets.findIndex((a) => a.id === asset.id);
|
||||
|
||||
if (currentIndex === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const step = direction === 'next' ? 1 : -1;
|
||||
const startIndex = currentIndex + step;
|
||||
|
||||
if (direction === 'next' && currentIndex >= sortedDescendingAssets.length - 1) {
|
||||
return null;
|
||||
}
|
||||
if (direction === 'previous' && currentIndex <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isInDifferentPeriod = (date1: DateTime, date2: DateTime): boolean => {
|
||||
if (unit === 'day') {
|
||||
return !date1.startOf('day').equals(date2.startOf('day'));
|
||||
} else if (unit === 'month') {
|
||||
return date1.year !== date2.year || date1.month !== date2.month;
|
||||
} else {
|
||||
return date1.year !== date2.year;
|
||||
}
|
||||
};
|
||||
|
||||
if (direction === 'next') {
|
||||
// Search forward in array (backwards in time)
|
||||
for (let i = startIndex; i < sortedDescendingAssets.length; i++) {
|
||||
const nextAsset = sortedDescendingAssets[i];
|
||||
const nextDate = DateTime.fromISO(nextAsset.localDateTime, { zone: 'utc' });
|
||||
|
||||
if (isInDifferentPeriod(nextDate, currentDateTime)) {
|
||||
return nextAsset;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Search backward in array (forwards in time)
|
||||
for (let i = startIndex; i >= 0; i--) {
|
||||
const prevAsset = sortedDescendingAssets[i];
|
||||
const prevDate = DateTime.fromISO(prevAsset.localDateTime, { zone: 'utc' });
|
||||
|
||||
if (isInDifferentPeriod(prevDate, currentDateTime)) {
|
||||
return prevAsset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
import { BrowserContext } from '@playwright/test';
|
||||
import { playwrightHost } from 'playwright.config';
|
||||
|
||||
export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserId: string) => {
|
||||
await context.addCookies([
|
||||
{
|
||||
name: 'immich_is_authenticated',
|
||||
value: 'true',
|
||||
domain: playwrightHost,
|
||||
path: '/',
|
||||
},
|
||||
]);
|
||||
await context.route('**/api/users/me', async (route) => {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: {
|
||||
id: adminUserId,
|
||||
email: 'admin@immich.cloud',
|
||||
name: 'Immich Admin',
|
||||
profileImagePath: '',
|
||||
avatarColor: 'orange',
|
||||
profileChangedAt: '2025-01-22T21:31:23.996Z',
|
||||
storageLabel: 'admin',
|
||||
shouldChangePassword: true,
|
||||
isAdmin: true,
|
||||
createdAt: '2025-01-22T21:31:23.996Z',
|
||||
deletedAt: null,
|
||||
updatedAt: '2025-11-14T00:00:00.369Z',
|
||||
oauthId: '',
|
||||
quotaSizeInBytes: null,
|
||||
quotaUsageInBytes: 20_849_000_159,
|
||||
status: 'active',
|
||||
license: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
await context.route('**/users/me/preferences', async (route) => {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: {
|
||||
albums: {
|
||||
defaultAssetOrder: 'desc',
|
||||
},
|
||||
folders: {
|
||||
enabled: false,
|
||||
sidebarWeb: false,
|
||||
},
|
||||
memories: {
|
||||
enabled: true,
|
||||
duration: 5,
|
||||
},
|
||||
people: {
|
||||
enabled: true,
|
||||
sidebarWeb: false,
|
||||
},
|
||||
sharedLinks: {
|
||||
enabled: true,
|
||||
sidebarWeb: false,
|
||||
},
|
||||
ratings: {
|
||||
enabled: false,
|
||||
},
|
||||
tags: {
|
||||
enabled: false,
|
||||
sidebarWeb: false,
|
||||
},
|
||||
emailNotifications: {
|
||||
enabled: true,
|
||||
albumInvite: true,
|
||||
albumUpdate: true,
|
||||
},
|
||||
download: {
|
||||
archiveSize: 4_294_967_296,
|
||||
includeEmbeddedVideos: false,
|
||||
},
|
||||
purchase: {
|
||||
showSupportBadge: true,
|
||||
hideBuyButtonUntil: '2100-02-12T00:00:00.000Z',
|
||||
},
|
||||
cast: {
|
||||
gCastEnabled: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
await context.route('**/server/about', async (route) => {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: {
|
||||
version: 'v2.2.3',
|
||||
versionUrl: 'https://github.com/immich-app/immich/releases/tag/v2.2.3',
|
||||
licensed: false,
|
||||
build: '1234567890',
|
||||
buildUrl: 'https://github.com/immich-app/immich/actions/runs/1234567890',
|
||||
buildImage: 'e2e',
|
||||
buildImageUrl: 'https://github.com/immich-app/immich/pkgs/container/immich-server',
|
||||
repository: 'immich-app/immich',
|
||||
repositoryUrl: 'https://github.com/immich-app/immich',
|
||||
sourceRef: 'e2e',
|
||||
sourceCommit: 'e2eeeeeeeeeeeeeeeeee',
|
||||
sourceUrl: 'https://github.com/immich-app/immich/commit/e2eeeeeeeeeeeeeeeeee',
|
||||
nodejs: 'v22.18.0',
|
||||
exiftool: '13.41',
|
||||
ffmpeg: '7.1.1-6',
|
||||
libvips: '8.17.2',
|
||||
imagemagick: '7.1.2-2',
|
||||
},
|
||||
});
|
||||
});
|
||||
await context.route('**/api/server/features', async (route) => {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: {
|
||||
smartSearch: false,
|
||||
facialRecognition: false,
|
||||
duplicateDetection: false,
|
||||
map: true,
|
||||
reverseGeocoding: true,
|
||||
importFaces: false,
|
||||
sidecar: true,
|
||||
search: true,
|
||||
trash: true,
|
||||
oauth: false,
|
||||
oauthAutoLaunch: false,
|
||||
ocr: false,
|
||||
passwordLogin: true,
|
||||
configFile: false,
|
||||
email: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
await context.route('**/api/server/config', async (route) => {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: {
|
||||
loginPageMessage: '',
|
||||
trashDays: 30,
|
||||
userDeleteDelay: 7,
|
||||
oauthButtonText: 'Login with OAuth',
|
||||
isInitialized: true,
|
||||
isOnboarded: true,
|
||||
externalDomain: '',
|
||||
publicUsers: true,
|
||||
mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json',
|
||||
mapLightStyleUrl: 'https://tiles.immich.cloud/v1/style/light.json',
|
||||
maintenanceMode: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
await context.route('**/api/server/media-types', async (route) => {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: {
|
||||
video: [
|
||||
'.3gp',
|
||||
'.3gpp',
|
||||
'.avi',
|
||||
'.flv',
|
||||
'.insv',
|
||||
'.m2t',
|
||||
'.m2ts',
|
||||
'.m4v',
|
||||
'.mkv',
|
||||
'.mov',
|
||||
'.mp4',
|
||||
'.mpe',
|
||||
'.mpeg',
|
||||
'.mpg',
|
||||
'.mts',
|
||||
'.vob',
|
||||
'.webm',
|
||||
'.wmv',
|
||||
],
|
||||
image: [
|
||||
'.3fr',
|
||||
'.ari',
|
||||
'.arw',
|
||||
'.cap',
|
||||
'.cin',
|
||||
'.cr2',
|
||||
'.cr3',
|
||||
'.crw',
|
||||
'.dcr',
|
||||
'.dng',
|
||||
'.erf',
|
||||
'.fff',
|
||||
'.iiq',
|
||||
'.k25',
|
||||
'.kdc',
|
||||
'.mrw',
|
||||
'.nef',
|
||||
'.nrw',
|
||||
'.orf',
|
||||
'.ori',
|
||||
'.pef',
|
||||
'.psd',
|
||||
'.raf',
|
||||
'.raw',
|
||||
'.rw2',
|
||||
'.rwl',
|
||||
'.sr2',
|
||||
'.srf',
|
||||
'.srw',
|
||||
'.x3f',
|
||||
'.avif',
|
||||
'.gif',
|
||||
'.jpeg',
|
||||
'.jpg',
|
||||
'.png',
|
||||
'.webp',
|
||||
'.bmp',
|
||||
'.heic',
|
||||
'.heif',
|
||||
'.hif',
|
||||
'.insp',
|
||||
'.jp2',
|
||||
'.jpe',
|
||||
'.jxl',
|
||||
'.svg',
|
||||
'.tif',
|
||||
'.tiff',
|
||||
],
|
||||
sidecar: ['.xmp'],
|
||||
},
|
||||
});
|
||||
});
|
||||
await context.route('**/api/notifications*', async (route) => {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: [],
|
||||
});
|
||||
});
|
||||
await context.route('**/api/albums*', async (route, request) => {
|
||||
if (request.url().endsWith('albums?shared=true') || request.url().endsWith('albums')) {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: [],
|
||||
});
|
||||
}
|
||||
await route.fallback();
|
||||
});
|
||||
await context.route('**/api/memories*', async (route) => {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: [],
|
||||
});
|
||||
});
|
||||
await context.route('**/api/server/storage', async (route) => {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: {
|
||||
diskSize: '100.0 GiB',
|
||||
diskUse: '74.4 GiB',
|
||||
diskAvailable: '25.6 GiB',
|
||||
diskSizeRaw: 107_374_182_400,
|
||||
diskUseRaw: 79_891_660_800,
|
||||
diskAvailableRaw: 27_482_521_600,
|
||||
diskUsagePercentage: 74.4,
|
||||
},
|
||||
});
|
||||
});
|
||||
await context.route('**/api/server/version-history', async (route) => {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: [
|
||||
{
|
||||
id: 'd1fbeadc-cb4f-4db3-8d19-8c6a921d5d8e',
|
||||
createdAt: '2025-11-15T20:14:01.935Z',
|
||||
version: '2.2.3',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -1,149 +0,0 @@
|
||||
import { BrowserContext, Page, Request, Route } from '@playwright/test';
|
||||
import { basename } from 'node:path';
|
||||
import {
|
||||
Changes,
|
||||
getAlbum,
|
||||
getAsset,
|
||||
getTimeBucket,
|
||||
getTimeBuckets,
|
||||
randomPreview,
|
||||
randomThumbnail,
|
||||
TimelineData,
|
||||
} from 'src/generators/timeline';
|
||||
import { sleep } from 'src/web/specs/timeline/utils';
|
||||
|
||||
export class TimelineTestContext {
|
||||
slowBucket = false;
|
||||
adminId = '';
|
||||
}
|
||||
|
||||
export const setupTimelineMockApiRoutes = async (
|
||||
context: BrowserContext,
|
||||
timelineRestData: TimelineData,
|
||||
changes: Changes,
|
||||
testContext: TimelineTestContext,
|
||||
) => {
|
||||
await context.route('**/api/timeline**', async (route, request) => {
|
||||
const url = new URL(request.url());
|
||||
const pathname = url.pathname;
|
||||
if (pathname === '/api/timeline/buckets') {
|
||||
const albumId = url.searchParams.get('albumId') || undefined;
|
||||
const isTrashed = url.searchParams.get('isTrashed') ? url.searchParams.get('isTrashed') === 'true' : undefined;
|
||||
const isFavorite = url.searchParams.get('isFavorite') ? url.searchParams.get('isFavorite') === 'true' : undefined;
|
||||
const isArchived = url.searchParams.get('visibility')
|
||||
? url.searchParams.get('visibility') === 'archive'
|
||||
: undefined;
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: getTimeBuckets(timelineRestData, isTrashed, isArchived, isFavorite, albumId, changes),
|
||||
});
|
||||
} else if (pathname === '/api/timeline/bucket') {
|
||||
const timeBucket = url.searchParams.get('timeBucket');
|
||||
if (!timeBucket) {
|
||||
return route.continue();
|
||||
}
|
||||
const isTrashed = url.searchParams.get('isTrashed') ? url.searchParams.get('isTrashed') === 'true' : undefined;
|
||||
const isArchived = url.searchParams.get('visibility')
|
||||
? url.searchParams.get('visibility') === 'archive'
|
||||
: undefined;
|
||||
const isFavorite = url.searchParams.get('isFavorite') ? url.searchParams.get('isFavorite') === 'true' : undefined;
|
||||
const albumId = url.searchParams.get('albumId') || undefined;
|
||||
const assets = getTimeBucket(timelineRestData, timeBucket, isTrashed, isArchived, isFavorite, albumId, changes);
|
||||
if (testContext.slowBucket) {
|
||||
await sleep(5000);
|
||||
}
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: assets,
|
||||
});
|
||||
}
|
||||
return route.continue();
|
||||
});
|
||||
|
||||
await context.route('**/api/assets/*', async (route, request) => {
|
||||
const url = new URL(request.url());
|
||||
const pathname = url.pathname;
|
||||
const assetId = basename(pathname);
|
||||
const asset = getAsset(timelineRestData, assetId);
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: asset,
|
||||
});
|
||||
});
|
||||
|
||||
await context.route('**/api/assets/*/ocr', async (route) => {
|
||||
return route.fulfill({ status: 200, contentType: 'application/json', json: [] });
|
||||
});
|
||||
|
||||
await context.route('**/api/assets/*/thumbnail?size=*', async (route, request) => {
|
||||
const pattern = /\/api\/assets\/(?<assetId>[^/]+)\/thumbnail\?size=(?<size>preview|thumbnail)/;
|
||||
const match = request.url().match(pattern);
|
||||
if (!match?.groups) {
|
||||
throw new Error(`Invalid URL for thumbnail endpoint: ${request.url()}`);
|
||||
}
|
||||
|
||||
if (match.groups.size === 'preview') {
|
||||
if (!route.request().serviceWorker()) {
|
||||
return route.continue();
|
||||
}
|
||||
const asset = getAsset(timelineRestData, match.groups.assetId);
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
headers: { 'content-type': 'image/jpeg', ETag: 'abc123', 'Cache-Control': 'public, max-age=3600' },
|
||||
body: await randomPreview(
|
||||
match.groups.assetId,
|
||||
(asset?.exifInfo?.exifImageWidth ?? 0) / (asset?.exifInfo?.exifImageHeight ?? 1),
|
||||
),
|
||||
});
|
||||
}
|
||||
if (match.groups.size === 'thumbnail') {
|
||||
if (!route.request().serviceWorker()) {
|
||||
return route.continue();
|
||||
}
|
||||
const asset = getAsset(timelineRestData, match.groups.assetId);
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
headers: { 'content-type': 'image/jpeg' },
|
||||
body: await randomThumbnail(
|
||||
match.groups.assetId,
|
||||
(asset?.exifInfo?.exifImageWidth ?? 0) / (asset?.exifInfo?.exifImageHeight ?? 1),
|
||||
),
|
||||
});
|
||||
}
|
||||
return route.continue();
|
||||
});
|
||||
|
||||
await context.route('**/api/albums/**', async (route, request) => {
|
||||
const pattern = /\/api\/albums\/(?<albumId>[^/?]+)/;
|
||||
const match = request.url().match(pattern);
|
||||
if (!match) {
|
||||
return route.continue();
|
||||
}
|
||||
const album = getAlbum(timelineRestData, testContext.adminId, match.groups?.albumId, changes);
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: album,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const pageRoutePromise = async (
|
||||
page: Page,
|
||||
route: string,
|
||||
callback: (route: Route, request: Request) => Promise<void>,
|
||||
) => {
|
||||
let resolveRequest: ((value: unknown | PromiseLike<unknown>) => void) | undefined;
|
||||
const deleteRequest = new Promise((resolve) => {
|
||||
resolveRequest = resolve;
|
||||
});
|
||||
await page.route(route, async (route, request) => {
|
||||
await callback(route, request);
|
||||
const requestJson = request.postDataJSON();
|
||||
resolveRequest?.(requestJson);
|
||||
});
|
||||
return deleteRequest;
|
||||
};
|
||||
@@ -7,12 +7,6 @@ export const errorDto = {
|
||||
message: 'Authentication required',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
unauthorizedWithMessage: (message: string) => ({
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message,
|
||||
correlationId: expect.any(String),
|
||||
}),
|
||||
forbidden: {
|
||||
error: 'Forbidden',
|
||||
statusCode: 403,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
AllJobStatusResponseDto,
|
||||
AssetMediaCreateDto,
|
||||
AssetMediaResponseDto,
|
||||
AssetResponseDto,
|
||||
@@ -6,13 +7,11 @@ import {
|
||||
CheckExistingAssetsDto,
|
||||
CreateAlbumDto,
|
||||
CreateLibraryDto,
|
||||
MaintenanceAction,
|
||||
JobCommandDto,
|
||||
JobName,
|
||||
MetadataSearchDto,
|
||||
Permission,
|
||||
PersonCreateDto,
|
||||
QueueCommandDto,
|
||||
QueueName,
|
||||
QueuesResponseLegacyDto,
|
||||
SharedLinkCreateDto,
|
||||
UpdateLibraryDto,
|
||||
UserAdminCreateDto,
|
||||
@@ -28,16 +27,15 @@ import {
|
||||
createStack,
|
||||
createUserAdmin,
|
||||
deleteAssets,
|
||||
getAllJobsStatus,
|
||||
getAssetInfo,
|
||||
getConfig,
|
||||
getConfigDefaults,
|
||||
getQueuesLegacy,
|
||||
login,
|
||||
runQueueCommandLegacy,
|
||||
scanLibrary,
|
||||
searchAssets,
|
||||
sendJobCommand,
|
||||
setBaseUrl,
|
||||
setMaintenanceMode,
|
||||
signUpAdmin,
|
||||
tagAssets,
|
||||
updateAdminOnboarding,
|
||||
@@ -54,7 +52,7 @@ import { exec, spawn } from 'node:child_process';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import path, { dirname } from 'node:path';
|
||||
import { setTimeout as setAsyncTimeout } from 'node:timers/promises';
|
||||
import { promisify } from 'node:util';
|
||||
import pg from 'pg';
|
||||
@@ -62,8 +60,6 @@ import { io, type Socket } from 'socket.io-client';
|
||||
import { loginDto, signupDto } from 'src/fixtures';
|
||||
import { makeRandomImage } from 'src/generators';
|
||||
import request from 'supertest';
|
||||
import { playwrightDbHost, playwrightHost, playwriteBaseUrl } from '../playwright.config';
|
||||
|
||||
export type { Emitter } from '@socket.io/component-emitter';
|
||||
|
||||
type CommandResponse = { stdout: string; stderr: string; exitCode: number | null };
|
||||
@@ -72,12 +68,12 @@ type WaitOptions = { event: EventType; id?: string; total?: number; timeout?: nu
|
||||
type AdminSetupOptions = { onboarding?: boolean };
|
||||
type FileData = { bytes?: Buffer; filename: string };
|
||||
|
||||
const dbUrl = `postgres://postgres:postgres@${playwrightDbHost}:5435/immich`;
|
||||
export const baseUrl = playwriteBaseUrl;
|
||||
const dbUrl = 'postgres://postgres:postgres@127.0.0.1:5435/immich';
|
||||
export const baseUrl = 'http://127.0.0.1:2285';
|
||||
export const shareUrl = `${baseUrl}/share`;
|
||||
export const app = `${baseUrl}/api`;
|
||||
// TODO move test assets into e2e/assets
|
||||
export const testAssetDir = resolve(import.meta.dirname, '../test-assets');
|
||||
export const testAssetDir = path.resolve('./test-assets');
|
||||
export const testAssetDirInternal = '/test-assets';
|
||||
export const tempDir = tmpdir();
|
||||
export const asBearerAuth = (accessToken: string) => ({ Authorization: `Bearer ${accessToken}` });
|
||||
@@ -481,10 +477,10 @@ export const utils = {
|
||||
tagAssets: (accessToken: string, tagId: string, assetIds: string[]) =>
|
||||
tagAssets({ id: tagId, bulkIdsDto: { ids: assetIds } }, { headers: asBearerAuth(accessToken) }),
|
||||
|
||||
queueCommand: async (accessToken: string, name: QueueName, queueCommandDto: QueueCommandDto) =>
|
||||
runQueueCommandLegacy({ name, queueCommandDto }, { headers: asBearerAuth(accessToken) }),
|
||||
jobCommand: async (accessToken: string, jobName: JobName, jobCommandDto: JobCommandDto) =>
|
||||
sendJobCommand({ id: jobName, jobCommandDto }, { headers: asBearerAuth(accessToken) }),
|
||||
|
||||
setAuthCookies: async (context: BrowserContext, accessToken: string, domain = playwrightHost) =>
|
||||
setAuthCookies: async (context: BrowserContext, accessToken: string, domain = '127.0.0.1') =>
|
||||
await context.addCookies([
|
||||
{
|
||||
name: 'immich_access_token',
|
||||
@@ -518,42 +514,6 @@ export const utils = {
|
||||
},
|
||||
]),
|
||||
|
||||
setMaintenanceAuthCookie: async (context: BrowserContext, token: string, domain = '127.0.0.1') =>
|
||||
await context.addCookies([
|
||||
{
|
||||
name: 'immich_maintenance_token',
|
||||
value: token,
|
||||
domain,
|
||||
path: '/',
|
||||
expires: 2_058_028_213,
|
||||
httpOnly: true,
|
||||
secure: false,
|
||||
sameSite: 'Lax',
|
||||
},
|
||||
]),
|
||||
|
||||
enterMaintenance: async (accessToken: string) => {
|
||||
let setCookie: string[] | undefined;
|
||||
|
||||
await setMaintenanceMode(
|
||||
{
|
||||
setMaintenanceModeDto: {
|
||||
action: MaintenanceAction.Start,
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: asBearerAuth(accessToken),
|
||||
fetch: (...args: Parameters<typeof fetch>) =>
|
||||
fetch(...args).then((response) => {
|
||||
setCookie = response.headers.getSetCookie();
|
||||
return response;
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
return setCookie;
|
||||
},
|
||||
|
||||
resetTempFolder: () => {
|
||||
rmSync(`${testAssetDir}/temp`, { recursive: true, force: true });
|
||||
mkdirSync(`${testAssetDir}/temp`, { recursive: true });
|
||||
@@ -564,13 +524,13 @@ export const utils = {
|
||||
await updateConfig({ systemConfigDto: defaultConfig }, { headers: asBearerAuth(accessToken) });
|
||||
},
|
||||
|
||||
isQueueEmpty: async (accessToken: string, queue: keyof QueuesResponseLegacyDto) => {
|
||||
const queues = await getQueuesLegacy({ headers: asBearerAuth(accessToken) });
|
||||
isQueueEmpty: async (accessToken: string, queue: keyof AllJobStatusResponseDto) => {
|
||||
const queues = await getAllJobsStatus({ headers: asBearerAuth(accessToken) });
|
||||
const jobCounts = queues[queue].jobCounts;
|
||||
return !jobCounts.active && !jobCounts.waiting;
|
||||
},
|
||||
|
||||
waitForQueueFinish: (accessToken: string, queue: keyof QueuesResponseLegacyDto, ms?: number) => {
|
||||
waitForQueueFinish: (accessToken: string, queue: keyof AllJobStatusResponseDto, ms?: number) => {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
const timeout = setTimeout(() => reject(new Error('Timed out waiting for queue to empty')), ms || 10_000);
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { LoginResponseDto } from '@immich/sdk';
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { utils } from 'src/utils';
|
||||
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
test.describe('Maintenance', () => {
|
||||
let admin: LoginResponseDto;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
utils.initSdk();
|
||||
await utils.resetDatabase();
|
||||
admin = await utils.adminSetup();
|
||||
});
|
||||
|
||||
test('enter and exit maintenance mode', async ({ context, page }) => {
|
||||
await utils.setAuthCookies(context, admin.accessToken);
|
||||
|
||||
await page.goto('/admin/system-settings?isOpen=maintenance');
|
||||
await page.getByRole('button', { name: 'Start maintenance mode' }).click();
|
||||
|
||||
await expect(page.getByText('Temporarily Unavailable')).toBeVisible({ timeout: 10_000 });
|
||||
await page.getByRole('button', { name: 'End maintenance mode' }).click();
|
||||
await page.waitForURL('**/admin/system-settings*', { timeout: 10_000 });
|
||||
});
|
||||
|
||||
test('maintenance shows no options to users until they authenticate', async ({ page }) => {
|
||||
const setCookie = await utils.enterMaintenance(admin.accessToken);
|
||||
const cookie = setCookie
|
||||
?.map((cookie) => cookie.split(';')[0].split('='))
|
||||
?.find(([name]) => name === 'immich_maintenance_token');
|
||||
|
||||
expect(cookie).toBeTruthy();
|
||||
|
||||
await expect(async () => {
|
||||
await page.goto('/');
|
||||
await page.waitForURL('**/maintenance?**', {
|
||||
timeout: 1000,
|
||||
});
|
||||
}).toPass({ timeout: 10_000 });
|
||||
|
||||
await expect(page.getByText('Temporarily Unavailable')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'End maintenance mode' })).toHaveCount(0);
|
||||
|
||||
await page.goto(`/maintenance?${new URLSearchParams({ token: cookie![1] })}`);
|
||||
await expect(page.getByText('Temporarily Unavailable')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'End maintenance mode' })).toBeVisible();
|
||||
await page.getByRole('button', { name: 'End maintenance mode' }).click();
|
||||
await page.waitForURL('**/auth/login');
|
||||
});
|
||||
});
|
||||
@@ -1,864 +0,0 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { DateTime } from 'luxon';
|
||||
import {
|
||||
Changes,
|
||||
createDefaultTimelineConfig,
|
||||
generateTimelineData,
|
||||
getAsset,
|
||||
getMockAsset,
|
||||
SeededRandom,
|
||||
selectRandom,
|
||||
selectRandomMultiple,
|
||||
TimelineAssetConfig,
|
||||
TimelineData,
|
||||
} from 'src/generators/timeline';
|
||||
import { setupBaseMockApiRoutes } from 'src/mock-network/base-network';
|
||||
import { pageRoutePromise, setupTimelineMockApiRoutes, TimelineTestContext } from 'src/mock-network/timeline-network';
|
||||
import { utils } from 'src/utils';
|
||||
import {
|
||||
assetViewerUtils,
|
||||
cancelAllPollers,
|
||||
padYearMonth,
|
||||
pageUtils,
|
||||
poll,
|
||||
thumbnailUtils,
|
||||
timelineUtils,
|
||||
} from 'src/web/specs/timeline/utils';
|
||||
|
||||
test.describe.configure({ mode: 'parallel' });
|
||||
test.describe('Timeline', () => {
|
||||
let adminUserId: string;
|
||||
let timelineRestData: TimelineData;
|
||||
const assets: TimelineAssetConfig[] = [];
|
||||
const yearMonths: string[] = [];
|
||||
const testContext = new TimelineTestContext();
|
||||
const changes: Changes = {
|
||||
albumAdditions: [],
|
||||
assetDeletions: [],
|
||||
assetArchivals: [],
|
||||
assetFavorites: [],
|
||||
};
|
||||
|
||||
test.beforeAll(async () => {
|
||||
test.fail(
|
||||
process.env.PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS !== '1',
|
||||
'This test requires env var: PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1',
|
||||
);
|
||||
utils.initSdk();
|
||||
adminUserId = faker.string.uuid();
|
||||
testContext.adminId = adminUserId;
|
||||
timelineRestData = generateTimelineData({ ...createDefaultTimelineConfig(), ownerId: adminUserId });
|
||||
for (const timeBucket of timelineRestData.buckets.values()) {
|
||||
assets.push(...timeBucket);
|
||||
}
|
||||
for (const yearMonth of timelineRestData.buckets.keys()) {
|
||||
const [year, month] = yearMonth.split('-');
|
||||
yearMonths.push(`${year}-${Number(month)}`);
|
||||
}
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ context }) => {
|
||||
await setupBaseMockApiRoutes(context, adminUserId);
|
||||
await setupTimelineMockApiRoutes(context, timelineRestData, changes, testContext);
|
||||
});
|
||||
|
||||
test.afterEach(() => {
|
||||
cancelAllPollers();
|
||||
testContext.slowBucket = false;
|
||||
changes.albumAdditions = [];
|
||||
changes.assetDeletions = [];
|
||||
changes.assetArchivals = [];
|
||||
changes.assetFavorites = [];
|
||||
});
|
||||
|
||||
test.describe('/photos', () => {
|
||||
test('Open /photos', async ({ page }) => {
|
||||
await page.goto(`/photos`);
|
||||
await page.waitForSelector('#asset-grid');
|
||||
await thumbnailUtils.expectTimelineHasOnScreenAssets(page);
|
||||
});
|
||||
test('Deep link to last photo', async ({ page }) => {
|
||||
const lastAsset = assets.at(-1)!;
|
||||
await pageUtils.deepLinkPhotosPage(page, lastAsset.id);
|
||||
await thumbnailUtils.expectTimelineHasOnScreenAssets(page);
|
||||
await thumbnailUtils.expectInViewport(page, lastAsset.id);
|
||||
});
|
||||
const rng = new SeededRandom(529);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
test('Deep link to random asset ' + i, async ({ page }) => {
|
||||
const asset = selectRandom(assets, rng);
|
||||
await pageUtils.deepLinkPhotosPage(page, asset.id);
|
||||
await thumbnailUtils.expectTimelineHasOnScreenAssets(page);
|
||||
await thumbnailUtils.expectInViewport(page, asset.id);
|
||||
});
|
||||
}
|
||||
test('Open /photos, open asset-viewer, browser back', async ({ page }) => {
|
||||
const rng = new SeededRandom(22);
|
||||
const asset = selectRandom(assets, rng);
|
||||
await pageUtils.deepLinkPhotosPage(page, asset.id);
|
||||
const scrollTopBefore = await timelineUtils.getScrollTop(page);
|
||||
await thumbnailUtils.clickAssetId(page, asset.id);
|
||||
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||
await page.goBack();
|
||||
await timelineUtils.locator(page).waitFor();
|
||||
const scrollTopAfter = await timelineUtils.getScrollTop(page);
|
||||
expect(scrollTopAfter).toBe(scrollTopBefore);
|
||||
});
|
||||
test('Open /photos, open asset-viewer, next photo, browser back, back', async ({ page }) => {
|
||||
const rng = new SeededRandom(49);
|
||||
const asset = selectRandom(assets, rng);
|
||||
const assetIndex = assets.indexOf(asset);
|
||||
const nextAsset = assets[assetIndex + 1];
|
||||
await pageUtils.deepLinkPhotosPage(page, asset.id);
|
||||
const scrollTopBefore = await timelineUtils.getScrollTop(page);
|
||||
await thumbnailUtils.clickAssetId(page, asset.id);
|
||||
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||
await expect.poll(() => new URL(page.url()).pathname).toBe(`/photos/${asset.id}`);
|
||||
await page.getByLabel('View next asset').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, nextAsset);
|
||||
await expect.poll(() => new URL(page.url()).pathname).toBe(`/photos/${nextAsset.id}`);
|
||||
await page.goBack();
|
||||
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||
await page.goBack();
|
||||
await page.waitForURL('**/photos?at=*');
|
||||
const scrollTopAfter = await timelineUtils.getScrollTop(page);
|
||||
expect(Math.abs(scrollTopAfter - scrollTopBefore)).toBeLessThan(5);
|
||||
});
|
||||
test('Open /photos, open asset-viewer, next photo 15x, backwardsArrow', async ({ page }) => {
|
||||
await pageUtils.deepLinkPhotosPage(page, assets[0].id);
|
||||
await thumbnailUtils.clickAssetId(page, assets[0].id);
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[0]);
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
await page.getByLabel('View next asset').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[i]);
|
||||
}
|
||||
await page.getByLabel('Go back').click();
|
||||
await page.waitForURL('**/photos?at=*');
|
||||
await thumbnailUtils.expectInViewport(page, assets[15].id);
|
||||
await thumbnailUtils.expectBottomIsTimelineBottom(page, assets[15]!.id);
|
||||
});
|
||||
test('Open /photos, open asset-viewer, previous photo 15x, backwardsArrow', async ({ page }) => {
|
||||
const lastAsset = assets.at(-1)!;
|
||||
await pageUtils.deepLinkPhotosPage(page, lastAsset.id);
|
||||
await thumbnailUtils.clickAssetId(page, lastAsset.id);
|
||||
await assetViewerUtils.waitForViewerLoad(page, lastAsset);
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
await page.getByLabel('View previous asset').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets.at(-1 - i)!);
|
||||
}
|
||||
await page.getByLabel('Go back').click();
|
||||
await page.waitForURL('**/photos?at=*');
|
||||
await thumbnailUtils.expectInViewport(page, assets.at(-1 - 15)!.id);
|
||||
await thumbnailUtils.expectTopIsTimelineTop(page, assets.at(-1 - 15)!.id);
|
||||
});
|
||||
});
|
||||
test.describe('keyboard', () => {
|
||||
/**
|
||||
* This text tests keyboard nativation, and also ensures that the scroll-to-asset behavior
|
||||
* scrolls the minimum amount. That is, if you are navigating using right arrow (auto scrolling
|
||||
* as necessary downwards), then the asset should always be at the lowest row of the grid.
|
||||
*/
|
||||
test('Next/previous asset - ArrowRight/ArrowLeft', async ({ page }) => {
|
||||
await pageUtils.openPhotosPage(page);
|
||||
await thumbnailUtils.withAssetId(page, assets[0].id).focus();
|
||||
const rightKey = 'ArrowRight';
|
||||
const leftKey = 'ArrowLeft';
|
||||
for (let i = 1; i < 15; i++) {
|
||||
await page.keyboard.press(rightKey);
|
||||
await assetViewerUtils.expectActiveAssetToBe(page, assets[i].id);
|
||||
}
|
||||
for (let i = 15; i <= 20; i++) {
|
||||
await page.keyboard.press(rightKey);
|
||||
await assetViewerUtils.expectActiveAssetToBe(page, assets[i].id);
|
||||
expect(await thumbnailUtils.expectBottomIsTimelineBottom(page, assets.at(i)!.id));
|
||||
}
|
||||
// now test previous asset
|
||||
for (let i = 19; i >= 15; i--) {
|
||||
await page.keyboard.press(leftKey);
|
||||
await assetViewerUtils.expectActiveAssetToBe(page, assets[i].id);
|
||||
}
|
||||
for (let i = 14; i > 0; i--) {
|
||||
await page.keyboard.press(leftKey);
|
||||
await assetViewerUtils.expectActiveAssetToBe(page, assets[i].id);
|
||||
expect(await thumbnailUtils.expectTopIsTimelineTop(page, assets.at(i)!.id));
|
||||
}
|
||||
});
|
||||
test('Next/previous asset - Tab/Shift+Tab', async ({ page }) => {
|
||||
await pageUtils.openPhotosPage(page);
|
||||
await thumbnailUtils.withAssetId(page, assets[0].id).focus();
|
||||
const rightKey = 'Tab';
|
||||
const leftKey = 'Shift+Tab';
|
||||
for (let i = 1; i < 15; i++) {
|
||||
await page.keyboard.press(rightKey);
|
||||
await assetViewerUtils.expectActiveAssetToBe(page, assets[i].id);
|
||||
}
|
||||
for (let i = 15; i <= 20; i++) {
|
||||
await page.keyboard.press(rightKey);
|
||||
await assetViewerUtils.expectActiveAssetToBe(page, assets[i].id);
|
||||
}
|
||||
// now test previous asset
|
||||
for (let i = 19; i >= 15; i--) {
|
||||
await page.keyboard.press(leftKey);
|
||||
await assetViewerUtils.expectActiveAssetToBe(page, assets[i].id);
|
||||
}
|
||||
for (let i = 14; i > 0; i--) {
|
||||
await page.keyboard.press(leftKey);
|
||||
await assetViewerUtils.expectActiveAssetToBe(page, assets[i].id);
|
||||
}
|
||||
});
|
||||
test('Next/previous day - d, Shift+D', async ({ page }) => {
|
||||
await pageUtils.openPhotosPage(page);
|
||||
let asset = assets[0];
|
||||
await timelineUtils.locator(page).hover();
|
||||
await page.keyboard.press('d');
|
||||
await assetViewerUtils.expectActiveAssetToBe(page, asset.id);
|
||||
for (let i = 0; i < 15; i++) {
|
||||
await page.keyboard.press('d');
|
||||
const next = getMockAsset(asset, assets, 'next', 'day')!;
|
||||
await assetViewerUtils.expectActiveAssetToBe(page, next.id);
|
||||
asset = next;
|
||||
}
|
||||
for (let i = 0; i < 15; i++) {
|
||||
await page.keyboard.press('Shift+D');
|
||||
const previous = getMockAsset(asset, assets, 'previous', 'day')!;
|
||||
await assetViewerUtils.expectActiveAssetToBe(page, previous.id);
|
||||
asset = previous;
|
||||
}
|
||||
});
|
||||
test('Next/previous month - m, Shift+M', async ({ page }) => {
|
||||
await pageUtils.openPhotosPage(page);
|
||||
let asset = assets[0];
|
||||
await timelineUtils.locator(page).hover();
|
||||
await page.keyboard.press('m');
|
||||
await assetViewerUtils.expectActiveAssetToBe(page, asset.id);
|
||||
for (let i = 0; i < 15; i++) {
|
||||
await page.keyboard.press('m');
|
||||
const next = getMockAsset(asset, assets, 'next', 'month')!;
|
||||
await assetViewerUtils.expectActiveAssetToBe(page, next.id);
|
||||
asset = next;
|
||||
}
|
||||
for (let i = 0; i < 15; i++) {
|
||||
await page.keyboard.press('Shift+M');
|
||||
const previous = getMockAsset(asset, assets, 'previous', 'month')!;
|
||||
await assetViewerUtils.expectActiveAssetToBe(page, previous.id);
|
||||
asset = previous;
|
||||
}
|
||||
});
|
||||
test('Next/previous year - y, Shift+Y', async ({ page }) => {
|
||||
await pageUtils.openPhotosPage(page);
|
||||
let asset = assets[0];
|
||||
await timelineUtils.locator(page).hover();
|
||||
await page.keyboard.press('y');
|
||||
await assetViewerUtils.expectActiveAssetToBe(page, asset.id);
|
||||
for (let i = 0; i < 15; i++) {
|
||||
await page.keyboard.press('y');
|
||||
const next = getMockAsset(asset, assets, 'next', 'year')!;
|
||||
await assetViewerUtils.expectActiveAssetToBe(page, next.id);
|
||||
asset = next;
|
||||
}
|
||||
for (let i = 0; i < 15; i++) {
|
||||
await page.keyboard.press('Shift+Y');
|
||||
const previous = getMockAsset(asset, assets, 'previous', 'year')!;
|
||||
await assetViewerUtils.expectActiveAssetToBe(page, previous.id);
|
||||
asset = previous;
|
||||
}
|
||||
});
|
||||
test('Navigate to time - g', async ({ page }) => {
|
||||
const rng = new SeededRandom(4782);
|
||||
await pageUtils.openPhotosPage(page);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const asset = selectRandom(assets, rng);
|
||||
await pageUtils.goToAsset(page, asset.fileCreatedAt);
|
||||
await thumbnailUtils.expectInViewport(page, asset.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
test.describe('selection', () => {
|
||||
test('Select day, unselect day', async ({ page }) => {
|
||||
await pageUtils.openPhotosPage(page);
|
||||
await pageUtils.selectDay(page, 'Wed, Dec 11, 2024');
|
||||
await expect(thumbnailUtils.selectedAsset(page)).toHaveCount(4);
|
||||
await pageUtils.selectDay(page, 'Wed, Dec 11, 2024');
|
||||
await expect(thumbnailUtils.selectedAsset(page)).toHaveCount(0);
|
||||
});
|
||||
test('Select asset, click asset to select', async ({ page }) => {
|
||||
await pageUtils.openPhotosPage(page);
|
||||
await thumbnailUtils.withAssetId(page, assets[1].id).hover();
|
||||
await thumbnailUtils.selectButton(page, assets[1].id).click();
|
||||
await expect(thumbnailUtils.selectedAsset(page)).toHaveCount(1);
|
||||
// no need to hover, once selection is active
|
||||
await thumbnailUtils.clickAssetId(page, assets[2].id);
|
||||
await expect(thumbnailUtils.selectedAsset(page)).toHaveCount(2);
|
||||
});
|
||||
test('Select asset, click unselect asset', async ({ page }) => {
|
||||
await pageUtils.openPhotosPage(page);
|
||||
await thumbnailUtils.withAssetId(page, assets[1].id).hover();
|
||||
await thumbnailUtils.selectButton(page, assets[1].id).click();
|
||||
await expect(thumbnailUtils.selectedAsset(page)).toHaveCount(1);
|
||||
await thumbnailUtils.clickAssetId(page, assets[1].id);
|
||||
// the hover uses a checked button too, so just move mouse away
|
||||
await page.mouse.move(0, 0);
|
||||
await expect(thumbnailUtils.selectedAsset(page)).toHaveCount(0);
|
||||
});
|
||||
test('Select asset, shift-hover candidates, shift-click end', async ({ page }) => {
|
||||
await pageUtils.openPhotosPage(page);
|
||||
const asset = assets[0];
|
||||
await thumbnailUtils.withAssetId(page, asset.id).hover();
|
||||
await thumbnailUtils.selectButton(page, asset.id).click();
|
||||
await page.keyboard.down('Shift');
|
||||
await thumbnailUtils.withAssetId(page, assets[2].id).hover();
|
||||
await expect(
|
||||
thumbnailUtils.locator(page).locator('.absolute.top-0.h-full.w-full.bg-immich-primary.opacity-40'),
|
||||
).toHaveCount(3);
|
||||
await thumbnailUtils.selectButton(page, assets[2].id).click();
|
||||
await page.keyboard.up('Shift');
|
||||
await expect(thumbnailUtils.selectedAsset(page)).toHaveCount(3);
|
||||
});
|
||||
test('Add multiple to selection - Select day, shift-click end', async ({ page }) => {
|
||||
await pageUtils.openPhotosPage(page);
|
||||
await thumbnailUtils.withAssetId(page, assets[0].id).hover();
|
||||
await thumbnailUtils.selectButton(page, assets[0].id).click();
|
||||
await thumbnailUtils.clickAssetId(page, assets[2].id);
|
||||
await page.keyboard.down('Shift');
|
||||
await thumbnailUtils.clickAssetId(page, assets[4].id);
|
||||
await page.mouse.move(0, 0);
|
||||
await expect(thumbnailUtils.selectedAsset(page)).toHaveCount(4);
|
||||
});
|
||||
});
|
||||
test.describe('scroll', () => {
|
||||
test('Open /photos, random click scrubber 20x', async ({ page }) => {
|
||||
test.slow();
|
||||
await pageUtils.openPhotosPage(page);
|
||||
const rng = new SeededRandom(6637);
|
||||
const selectedMonths = selectRandomMultiple(yearMonths, 20, rng);
|
||||
for (const month of selectedMonths) {
|
||||
await page.locator(`[data-segment-year-month="${month}"]`).click({ force: true });
|
||||
const visibleMockAssetsYearMonths = await poll(page, async () => {
|
||||
const assetIds = await thumbnailUtils.getAllInViewport(
|
||||
page,
|
||||
(assetId: string) => getYearMonth(assets, assetId) === month,
|
||||
);
|
||||
const visibleMockAssetsYearMonths: string[] = [];
|
||||
for (const assetId of assetIds!) {
|
||||
const yearMonth = getYearMonth(assets, assetId);
|
||||
visibleMockAssetsYearMonths.push(yearMonth);
|
||||
if (yearMonth === month) {
|
||||
return [yearMonth];
|
||||
}
|
||||
}
|
||||
});
|
||||
if (page.isClosed()) {
|
||||
return;
|
||||
}
|
||||
expect(visibleMockAssetsYearMonths).toContain(month);
|
||||
}
|
||||
});
|
||||
test('Deep link to last photo, scroll up', async ({ page }) => {
|
||||
const lastAsset = assets.at(-1)!;
|
||||
await pageUtils.deepLinkPhotosPage(page, lastAsset.id);
|
||||
|
||||
await timelineUtils.locator(page).hover();
|
||||
for (let i = 0; i < 100; i++) {
|
||||
await page.mouse.wheel(0, -100);
|
||||
await page.waitForTimeout(25);
|
||||
}
|
||||
|
||||
await thumbnailUtils.expectInViewport(page, '14e5901f-fd7f-40c0-b186-4d7e7fc67968');
|
||||
});
|
||||
test('Deep link to first bucket, scroll down', async ({ page }) => {
|
||||
const lastAsset = assets.at(0)!;
|
||||
await pageUtils.deepLinkPhotosPage(page, lastAsset.id);
|
||||
await timelineUtils.locator(page).hover();
|
||||
for (let i = 0; i < 100; i++) {
|
||||
await page.mouse.wheel(0, 100);
|
||||
await page.waitForTimeout(25);
|
||||
}
|
||||
await thumbnailUtils.expectInViewport(page, 'b7983a13-4b4e-4950-a731-f2962d9a1555');
|
||||
});
|
||||
test('Deep link to last photo, drag scrubber to scroll up', async ({ page }) => {
|
||||
const lastAsset = assets.at(-1)!;
|
||||
await pageUtils.deepLinkPhotosPage(page, lastAsset.id);
|
||||
const lastMonth = yearMonths.at(-1);
|
||||
const firstScrubSegment = page.locator(`[data-segment-year-month="${yearMonths[0]}"]`);
|
||||
const lastScrubSegment = page.locator(`[data-segment-year-month="${lastMonth}"]`);
|
||||
const sourcebox = (await lastScrubSegment.boundingBox())!;
|
||||
const targetBox = (await firstScrubSegment.boundingBox())!;
|
||||
await firstScrubSegment.hover();
|
||||
const currentY = sourcebox.y;
|
||||
await page.mouse.move(sourcebox.x + sourcebox?.width / 2, currentY);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(sourcebox.x + sourcebox?.width / 2, targetBox.y, { steps: 100 });
|
||||
await page.mouse.up();
|
||||
await thumbnailUtils.expectInViewport(page, assets[0].id);
|
||||
});
|
||||
test('Deep link to first bucket, drag scrubber to scroll down', async ({ page }) => {
|
||||
await pageUtils.deepLinkPhotosPage(page, assets[0].id);
|
||||
const firstScrubSegment = page.locator(`[data-segment-year-month="${yearMonths[0]}"]`);
|
||||
const sourcebox = (await firstScrubSegment.boundingBox())!;
|
||||
await firstScrubSegment.hover();
|
||||
const currentY = sourcebox.y;
|
||||
await page.mouse.move(sourcebox.x + sourcebox?.width / 2, currentY);
|
||||
await page.mouse.down();
|
||||
const height = page.viewportSize()?.height;
|
||||
expect(height).toBeDefined();
|
||||
await page.mouse.move(sourcebox.x + sourcebox?.width / 2, height! - 10, {
|
||||
steps: 100,
|
||||
});
|
||||
await page.mouse.up();
|
||||
await thumbnailUtils.expectInViewport(page, assets.at(-1)!.id);
|
||||
});
|
||||
test('Buckets cancel on scroll', async ({ page }) => {
|
||||
await pageUtils.openPhotosPage(page);
|
||||
testContext.slowBucket = true;
|
||||
const failedUris: string[] = [];
|
||||
page.on('requestfailed', (request) => {
|
||||
failedUris.push(request.url());
|
||||
});
|
||||
const offscreenSegment = page.locator(`[data-segment-year-month="${yearMonths[12]}"]`);
|
||||
await offscreenSegment.click({ force: true });
|
||||
const lastSegment = page.locator(`[data-segment-year-month="${yearMonths.at(-1)!}"]`);
|
||||
await lastSegment.click({ force: true });
|
||||
const uris = await poll(page, async () => (failedUris.length > 0 ? failedUris : null));
|
||||
expect(uris).toEqual(expect.arrayContaining([expect.stringContaining(padYearMonth(yearMonths[12]!))]));
|
||||
});
|
||||
});
|
||||
test.describe('/albums', () => {
|
||||
test('Open album', async ({ page }) => {
|
||||
const album = timelineRestData.album;
|
||||
await pageUtils.openAlbumPage(page, album.id);
|
||||
await thumbnailUtils.expectInViewport(page, album.assetIds[0]);
|
||||
});
|
||||
test('Deep link to last photo', async ({ page }) => {
|
||||
const album = timelineRestData.album;
|
||||
const lastAsset = album.assetIds.at(-1);
|
||||
await pageUtils.deepLinkAlbumPage(page, album.id, lastAsset!);
|
||||
await thumbnailUtils.expectInViewport(page, album.assetIds.at(-1)!);
|
||||
await thumbnailUtils.expectBottomIsTimelineBottom(page, album.assetIds.at(-1)!);
|
||||
});
|
||||
test('Add photos to album pre-selects existing', async ({ page }) => {
|
||||
const album = timelineRestData.album;
|
||||
await pageUtils.openAlbumPage(page, album.id);
|
||||
await page.getByLabel('Add photos').click();
|
||||
const asset = getAsset(timelineRestData, album.assetIds[0])!;
|
||||
await pageUtils.goToAsset(page, asset.fileCreatedAt);
|
||||
await thumbnailUtils.expectInViewport(page, asset.id);
|
||||
await thumbnailUtils.expectSelectedReadonly(page, asset.id);
|
||||
});
|
||||
test('Add photos to album', async ({ page }) => {
|
||||
const album = timelineRestData.album;
|
||||
await pageUtils.openAlbumPage(page, album.id);
|
||||
await page.locator('nav button[aria-label="Add photos"]').click();
|
||||
const asset = getAsset(timelineRestData, album.assetIds[0])!;
|
||||
await pageUtils.goToAsset(page, asset.fileCreatedAt);
|
||||
await thumbnailUtils.expectInViewport(page, asset.id);
|
||||
await thumbnailUtils.expectSelectedReadonly(page, asset.id);
|
||||
await pageUtils.selectDay(page, 'Tue, Feb 27, 2024');
|
||||
const put = pageRoutePromise(page, `**/api/albums/${album.id}/assets`, async (route, request) => {
|
||||
const requestJson = request.postDataJSON();
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: requestJson.ids.map((id: string) => ({ id, success: true })),
|
||||
});
|
||||
changes.albumAdditions.push(...requestJson.ids);
|
||||
});
|
||||
await page.getByText('Done').click();
|
||||
await expect(put).resolves.toEqual({
|
||||
ids: [
|
||||
'c077ea7b-cfa1-45e4-8554-f86c00ee5658',
|
||||
'040fd762-dbbc-486d-a51a-2d84115e6229',
|
||||
'86af0b5f-79d3-4f75-bab3-3b61f6c72b23',
|
||||
],
|
||||
});
|
||||
const addedAsset = getAsset(timelineRestData, 'c077ea7b-cfa1-45e4-8554-f86c00ee5658')!;
|
||||
await pageUtils.goToAsset(page, addedAsset.fileCreatedAt);
|
||||
await thumbnailUtils.expectInViewport(page, 'c077ea7b-cfa1-45e4-8554-f86c00ee5658');
|
||||
await thumbnailUtils.expectInViewport(page, '040fd762-dbbc-486d-a51a-2d84115e6229');
|
||||
await thumbnailUtils.expectInViewport(page, '86af0b5f-79d3-4f75-bab3-3b61f6c72b23');
|
||||
});
|
||||
});
|
||||
test.describe('/trash', () => {
|
||||
test('open /photos, trash photo, open /trash, restore', async ({ page }) => {
|
||||
await pageUtils.openPhotosPage(page);
|
||||
const assetToTrash = assets[0];
|
||||
await thumbnailUtils.withAssetId(page, assetToTrash.id).hover();
|
||||
await thumbnailUtils.selectButton(page, assetToTrash.id).click();
|
||||
await page.getByLabel('Menu').click();
|
||||
const deleteRequest = pageRoutePromise(page, '**/api/assets', async (route, request) => {
|
||||
const requestJson = request.postDataJSON();
|
||||
changes.assetDeletions.push(...requestJson.ids);
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: requestJson.ids.map((id: string) => ({ id, success: true })),
|
||||
});
|
||||
});
|
||||
await page.getByRole('menuitem').getByText('Delete').click();
|
||||
await expect(deleteRequest).resolves.toEqual({
|
||||
force: false,
|
||||
ids: [assetToTrash.id],
|
||||
});
|
||||
await page.getByText('Trash', { exact: true }).click();
|
||||
await thumbnailUtils.expectInViewport(page, assetToTrash.id);
|
||||
await thumbnailUtils.withAssetId(page, assetToTrash.id).hover();
|
||||
await thumbnailUtils.selectButton(page, assetToTrash.id).click();
|
||||
const restoreRequest = pageRoutePromise(page, '**/api/trash/restore/assets', async (route, request) => {
|
||||
const requestJson = request.postDataJSON();
|
||||
changes.assetDeletions = changes.assetDeletions.filter((id) => !requestJson.ids.includes(id));
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: { count: requestJson.ids.length },
|
||||
});
|
||||
});
|
||||
await page.getByText('Restore', { exact: true }).click();
|
||||
await expect(restoreRequest).resolves.toEqual({
|
||||
ids: [assetToTrash.id],
|
||||
});
|
||||
await expect(thumbnailUtils.withAssetId(page, assetToTrash.id)).toHaveCount(0);
|
||||
await page.getByText('Photos', { exact: true }).click();
|
||||
await thumbnailUtils.expectInViewport(page, assetToTrash.id);
|
||||
});
|
||||
test('open album, trash photo, open /trash, restore', async ({ page }) => {
|
||||
const album = timelineRestData.album;
|
||||
await pageUtils.openAlbumPage(page, album.id);
|
||||
const assetToTrash = getAsset(timelineRestData, album.assetIds[0])!;
|
||||
await thumbnailUtils.withAssetId(page, assetToTrash.id).hover();
|
||||
await thumbnailUtils.selectButton(page, assetToTrash.id).click();
|
||||
await page.getByLabel('Menu').click();
|
||||
const deleteRequest = pageRoutePromise(page, '**/api/assets', async (route, request) => {
|
||||
const requestJson = request.postDataJSON();
|
||||
changes.assetDeletions.push(...requestJson.ids);
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: requestJson.ids.map((id: string) => ({ id, success: true })),
|
||||
});
|
||||
});
|
||||
await page.getByRole('menuitem').getByText('Delete').click();
|
||||
await expect(deleteRequest).resolves.toEqual({
|
||||
force: false,
|
||||
ids: [assetToTrash.id],
|
||||
});
|
||||
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
|
||||
await page.getByText('Trash', { exact: true }).click();
|
||||
await timelineUtils.waitForTimelineLoad(page);
|
||||
await thumbnailUtils.expectInViewport(page, assetToTrash.id);
|
||||
await thumbnailUtils.withAssetId(page, assetToTrash.id).hover();
|
||||
await thumbnailUtils.selectButton(page, assetToTrash.id).click();
|
||||
const restoreRequest = pageRoutePromise(page, '**/api/trash/restore/assets', async (route, request) => {
|
||||
const requestJson = request.postDataJSON();
|
||||
changes.assetDeletions = changes.assetDeletions.filter((id) => !requestJson.ids.includes(id));
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: { count: requestJson.ids.length },
|
||||
});
|
||||
});
|
||||
await page.getByText('Restore', { exact: true }).click();
|
||||
await expect(restoreRequest).resolves.toEqual({
|
||||
ids: [assetToTrash.id],
|
||||
});
|
||||
await expect(thumbnailUtils.withAssetId(page, assetToTrash.id)).toHaveCount(0);
|
||||
await pageUtils.openAlbumPage(page, album.id);
|
||||
await thumbnailUtils.expectInViewport(page, assetToTrash.id);
|
||||
});
|
||||
});
|
||||
test.describe('/archive', () => {
|
||||
test('open /photos, archive photo, open /archive, unarchive', async ({ page }) => {
|
||||
await pageUtils.openPhotosPage(page);
|
||||
const assetToArchive = assets[0];
|
||||
await thumbnailUtils.withAssetId(page, assetToArchive.id).hover();
|
||||
await thumbnailUtils.selectButton(page, assetToArchive.id).click();
|
||||
await page.getByLabel('Menu').click();
|
||||
const archive = pageRoutePromise(page, '**/api/assets', async (route, request) => {
|
||||
const requestJson = request.postDataJSON();
|
||||
if (requestJson.visibility !== 'archive') {
|
||||
return await route.continue();
|
||||
}
|
||||
await route.fulfill({
|
||||
status: 204,
|
||||
});
|
||||
changes.assetArchivals.push(...requestJson.ids);
|
||||
});
|
||||
await page.getByRole('menuitem').getByText('Archive').click();
|
||||
await expect(archive).resolves.toEqual({
|
||||
visibility: 'archive',
|
||||
ids: [assetToArchive.id],
|
||||
});
|
||||
await expect(thumbnailUtils.withAssetId(page, assetToArchive.id)).toHaveCount(0);
|
||||
await page.getByRole('link').getByText('Archive').click();
|
||||
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
|
||||
await thumbnailUtils.withAssetId(page, assetToArchive.id).hover();
|
||||
await thumbnailUtils.selectButton(page, assetToArchive.id).click();
|
||||
const unarchiveRequest = pageRoutePromise(page, '**/api/assets', async (route, request) => {
|
||||
const requestJson = request.postDataJSON();
|
||||
if (requestJson.visibility !== 'timeline') {
|
||||
return await route.continue();
|
||||
}
|
||||
changes.assetArchivals = changes.assetArchivals.filter((id) => !requestJson.ids.includes(id));
|
||||
await route.fulfill({
|
||||
status: 204,
|
||||
});
|
||||
});
|
||||
await page.getByLabel('Unarchive').click();
|
||||
await expect(unarchiveRequest).resolves.toEqual({
|
||||
visibility: 'timeline',
|
||||
ids: [assetToArchive.id],
|
||||
});
|
||||
await expect(thumbnailUtils.withAssetId(page, assetToArchive.id)).toHaveCount(0);
|
||||
await page.getByText('Photos', { exact: true }).click();
|
||||
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
|
||||
});
|
||||
test('open /archive, favorite photo, unfavorite', async ({ page }) => {
|
||||
const assetToFavorite = assets[0];
|
||||
changes.assetArchivals.push(assetToFavorite.id);
|
||||
await pageUtils.openArchivePage(page);
|
||||
const favorite = pageRoutePromise(page, '**/api/assets', async (route, request) => {
|
||||
const requestJson = request.postDataJSON();
|
||||
if (requestJson.isFavorite === undefined) {
|
||||
return await route.continue();
|
||||
}
|
||||
const isFavorite = requestJson.isFavorite;
|
||||
if (isFavorite) {
|
||||
changes.assetFavorites.push(...requestJson.ids);
|
||||
}
|
||||
await route.fulfill({
|
||||
status: 204,
|
||||
});
|
||||
});
|
||||
await thumbnailUtils.withAssetId(page, assetToFavorite.id).hover();
|
||||
await thumbnailUtils.selectButton(page, assetToFavorite.id).click();
|
||||
await page.getByLabel('Favorite').click();
|
||||
await expect(favorite).resolves.toEqual({
|
||||
isFavorite: true,
|
||||
ids: [assetToFavorite.id],
|
||||
});
|
||||
await expect(thumbnailUtils.withAssetId(page, assetToFavorite.id)).toHaveCount(1);
|
||||
await thumbnailUtils.expectInViewport(page, assetToFavorite.id);
|
||||
await thumbnailUtils.expectThumbnailIsFavorite(page, assetToFavorite.id);
|
||||
await thumbnailUtils.withAssetId(page, assetToFavorite.id).hover();
|
||||
await thumbnailUtils.selectButton(page, assetToFavorite.id).click();
|
||||
const unFavoriteRequest = pageRoutePromise(page, '**/api/assets', async (route, request) => {
|
||||
const requestJson = request.postDataJSON();
|
||||
if (requestJson.isFavorite === undefined) {
|
||||
return await route.continue();
|
||||
}
|
||||
changes.assetFavorites = changes.assetFavorites.filter((id) => !requestJson.ids.includes(id));
|
||||
await route.fulfill({
|
||||
status: 204,
|
||||
});
|
||||
});
|
||||
await page.getByLabel('Remove from favorites').click();
|
||||
await expect(unFavoriteRequest).resolves.toEqual({
|
||||
isFavorite: false,
|
||||
ids: [assetToFavorite.id],
|
||||
});
|
||||
await expect(thumbnailUtils.withAssetId(page, assetToFavorite.id)).toHaveCount(1);
|
||||
await thumbnailUtils.expectThumbnailIsNotFavorite(page, assetToFavorite.id);
|
||||
});
|
||||
test('open album, archive photo, open album, unarchive', async ({ page }) => {
|
||||
const album = timelineRestData.album;
|
||||
await pageUtils.openAlbumPage(page, album.id);
|
||||
const assetToArchive = getAsset(timelineRestData, album.assetIds[0])!;
|
||||
await thumbnailUtils.withAssetId(page, assetToArchive.id).hover();
|
||||
await thumbnailUtils.selectButton(page, assetToArchive.id).click();
|
||||
await page.getByLabel('Menu').click();
|
||||
const archive = pageRoutePromise(page, '**/api/assets', async (route, request) => {
|
||||
const requestJson = request.postDataJSON();
|
||||
if (requestJson.visibility !== 'archive') {
|
||||
return await route.continue();
|
||||
}
|
||||
changes.assetArchivals.push(...requestJson.ids);
|
||||
await route.fulfill({
|
||||
status: 204,
|
||||
});
|
||||
});
|
||||
await page.getByRole('menuitem').getByText('Archive').click();
|
||||
await expect(archive).resolves.toEqual({
|
||||
visibility: 'archive',
|
||||
ids: [assetToArchive.id],
|
||||
});
|
||||
await thumbnailUtils.expectThumbnailIsArchive(page, assetToArchive.id);
|
||||
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
|
||||
await page.getByRole('link').getByText('Archive').click();
|
||||
await timelineUtils.waitForTimelineLoad(page);
|
||||
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
|
||||
await thumbnailUtils.withAssetId(page, assetToArchive.id).hover();
|
||||
await thumbnailUtils.selectButton(page, assetToArchive.id).click();
|
||||
const unarchiveRequest = pageRoutePromise(page, '**/api/assets', async (route, request) => {
|
||||
const requestJson = request.postDataJSON();
|
||||
if (requestJson.visibility !== 'timeline') {
|
||||
return await route.continue();
|
||||
}
|
||||
changes.assetArchivals = changes.assetArchivals.filter((id) => !requestJson.ids.includes(id));
|
||||
await route.fulfill({
|
||||
status: 204,
|
||||
});
|
||||
});
|
||||
await page.getByLabel('Unarchive').click();
|
||||
await expect(unarchiveRequest).resolves.toEqual({
|
||||
visibility: 'timeline',
|
||||
ids: [assetToArchive.id],
|
||||
});
|
||||
await expect(thumbnailUtils.withAssetId(page, assetToArchive.id)).toHaveCount(0);
|
||||
await pageUtils.openAlbumPage(page, album.id);
|
||||
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
|
||||
});
|
||||
});
|
||||
test.describe('/favorite', () => {
|
||||
test('open /photos, favorite photo, open /favorites, remove favorite, open /photos', async ({ page }) => {
|
||||
await pageUtils.openPhotosPage(page);
|
||||
const assetToFavorite = assets[0];
|
||||
|
||||
await thumbnailUtils.withAssetId(page, assetToFavorite.id).hover();
|
||||
await thumbnailUtils.selectButton(page, assetToFavorite.id).click();
|
||||
const favorite = pageRoutePromise(page, '**/api/assets', async (route, request) => {
|
||||
const requestJson = request.postDataJSON();
|
||||
if (requestJson.isFavorite === undefined) {
|
||||
return await route.continue();
|
||||
}
|
||||
const isFavorite = requestJson.isFavorite;
|
||||
if (isFavorite) {
|
||||
changes.assetFavorites.push(...requestJson.ids);
|
||||
}
|
||||
await route.fulfill({
|
||||
status: 204,
|
||||
});
|
||||
});
|
||||
await page.getByLabel('Favorite').click();
|
||||
await expect(favorite).resolves.toEqual({
|
||||
isFavorite: true,
|
||||
ids: [assetToFavorite.id],
|
||||
});
|
||||
// ensure thumbnail still exists and has favorite icon
|
||||
await thumbnailUtils.expectThumbnailIsFavorite(page, assetToFavorite.id);
|
||||
await page.getByRole('link').getByText('Favorites').click();
|
||||
await thumbnailUtils.expectInViewport(page, assetToFavorite.id);
|
||||
await thumbnailUtils.withAssetId(page, assetToFavorite.id).hover();
|
||||
await thumbnailUtils.selectButton(page, assetToFavorite.id).click();
|
||||
const unFavoriteRequest = pageRoutePromise(page, '**/api/assets', async (route, request) => {
|
||||
const requestJson = request.postDataJSON();
|
||||
if (requestJson.isFavorite === undefined) {
|
||||
return await route.continue();
|
||||
}
|
||||
changes.assetFavorites = changes.assetFavorites.filter((id) => !requestJson.ids.includes(id));
|
||||
await route.fulfill({
|
||||
status: 204,
|
||||
});
|
||||
});
|
||||
await page.getByLabel('Remove from favorites').click();
|
||||
await expect(unFavoriteRequest).resolves.toEqual({
|
||||
isFavorite: false,
|
||||
ids: [assetToFavorite.id],
|
||||
});
|
||||
await expect(thumbnailUtils.withAssetId(page, assetToFavorite.id)).toHaveCount(0);
|
||||
await page.getByText('Photos', { exact: true }).click();
|
||||
await thumbnailUtils.expectInViewport(page, assetToFavorite.id);
|
||||
});
|
||||
test('open /favorites, archive photo, unarchive photo', async ({ page }) => {
|
||||
await pageUtils.openFavorites(page);
|
||||
const assetToArchive = getAsset(timelineRestData, 'ad31e29f-2069-4574-b9a9-ad86523c92cb')!;
|
||||
await thumbnailUtils.withAssetId(page, assetToArchive.id).hover();
|
||||
await thumbnailUtils.selectButton(page, assetToArchive.id).click();
|
||||
await page.getByLabel('Menu').click();
|
||||
const archive = pageRoutePromise(page, '**/api/assets', async (route, request) => {
|
||||
const requestJson = request.postDataJSON();
|
||||
if (requestJson.visibility !== 'archive') {
|
||||
return await route.continue();
|
||||
}
|
||||
await route.fulfill({
|
||||
status: 204,
|
||||
});
|
||||
changes.assetArchivals.push(...requestJson.ids);
|
||||
});
|
||||
await page.getByRole('menuitem').getByText('Archive').click();
|
||||
await expect(archive).resolves.toEqual({
|
||||
visibility: 'archive',
|
||||
ids: [assetToArchive.id],
|
||||
});
|
||||
await page.getByRole('link').getByText('Archive').click();
|
||||
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
|
||||
await thumbnailUtils.expectThumbnailIsNotArchive(page, assetToArchive.id);
|
||||
await thumbnailUtils.withAssetId(page, assetToArchive.id).hover();
|
||||
await thumbnailUtils.selectButton(page, assetToArchive.id).click();
|
||||
const unarchiveRequest = pageRoutePromise(page, '**/api/assets', async (route, request) => {
|
||||
const requestJson = request.postDataJSON();
|
||||
if (requestJson.visibility !== 'timeline') {
|
||||
return await route.continue();
|
||||
}
|
||||
changes.assetArchivals = changes.assetArchivals.filter((id) => !requestJson.ids.includes(id));
|
||||
await route.fulfill({
|
||||
status: 204,
|
||||
});
|
||||
});
|
||||
await page.getByLabel('Unarchive').click();
|
||||
await expect(unarchiveRequest).resolves.toEqual({
|
||||
visibility: 'timeline',
|
||||
ids: [assetToArchive.id],
|
||||
});
|
||||
await expect(thumbnailUtils.withAssetId(page, assetToArchive.id)).toHaveCount(0);
|
||||
await thumbnailUtils.expectThumbnailIsNotArchive(page, assetToArchive.id);
|
||||
});
|
||||
test('Open album, favorite photo, open /favorites, remove favorite, Open album', async ({ page }) => {
|
||||
const album = timelineRestData.album;
|
||||
await pageUtils.openAlbumPage(page, album.id);
|
||||
const assetToFavorite = getAsset(timelineRestData, album.assetIds[0])!;
|
||||
|
||||
await thumbnailUtils.withAssetId(page, assetToFavorite.id).hover();
|
||||
await thumbnailUtils.selectButton(page, assetToFavorite.id).click();
|
||||
const favorite = pageRoutePromise(page, '**/api/assets', async (route, request) => {
|
||||
const requestJson = request.postDataJSON();
|
||||
if (requestJson.isFavorite === undefined) {
|
||||
return await route.continue();
|
||||
}
|
||||
const isFavorite = requestJson.isFavorite;
|
||||
if (isFavorite) {
|
||||
changes.assetFavorites.push(...requestJson.ids);
|
||||
}
|
||||
await route.fulfill({
|
||||
status: 204,
|
||||
});
|
||||
});
|
||||
await page.getByLabel('Favorite').click();
|
||||
await expect(favorite).resolves.toEqual({
|
||||
isFavorite: true,
|
||||
ids: [assetToFavorite.id],
|
||||
});
|
||||
// ensure thumbnail still exists and has favorite icon
|
||||
await thumbnailUtils.expectThumbnailIsFavorite(page, assetToFavorite.id);
|
||||
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
|
||||
await page.getByRole('link').getByText('Favorites').click();
|
||||
await timelineUtils.waitForTimelineLoad(page);
|
||||
await pageUtils.goToAsset(page, assetToFavorite.fileCreatedAt);
|
||||
await thumbnailUtils.expectInViewport(page, assetToFavorite.id);
|
||||
await thumbnailUtils.withAssetId(page, assetToFavorite.id).hover();
|
||||
await thumbnailUtils.selectButton(page, assetToFavorite.id).click();
|
||||
const unFavoriteRequest = pageRoutePromise(page, '**/api/assets', async (route, request) => {
|
||||
const requestJson = request.postDataJSON();
|
||||
if (requestJson.isFavorite === undefined) {
|
||||
return await route.continue();
|
||||
}
|
||||
changes.assetFavorites = changes.assetFavorites.filter((id) => !requestJson.ids.includes(id));
|
||||
await route.fulfill({
|
||||
status: 204,
|
||||
});
|
||||
});
|
||||
await page.getByLabel('Remove from favorites').click();
|
||||
await expect(unFavoriteRequest).resolves.toEqual({
|
||||
isFavorite: false,
|
||||
ids: [assetToFavorite.id],
|
||||
});
|
||||
await expect(thumbnailUtils.withAssetId(page, assetToFavorite.id)).toHaveCount(0);
|
||||
await pageUtils.openAlbumPage(page, album.id);
|
||||
await thumbnailUtils.expectInViewport(page, assetToFavorite.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const getYearMonth = (assets: TimelineAssetConfig[], assetId: string) => {
|
||||
const mockAsset = assets.find((mockAsset) => mockAsset.id === assetId)!;
|
||||
const dateTime = DateTime.fromISO(mockAsset.fileCreatedAt!);
|
||||
return dateTime.year + '-' + dateTime.month;
|
||||
};
|
||||
@@ -1,238 +0,0 @@
|
||||
import { BrowserContext, expect, Page } from '@playwright/test';
|
||||
import { DateTime } from 'luxon';
|
||||
import { TimelineAssetConfig } from 'src/generators/timeline';
|
||||
|
||||
export const sleep = (ms: number) => {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
export const padYearMonth = (yearMonth: string) => {
|
||||
const [year, month] = yearMonth.split('-');
|
||||
return `${year}-${month.padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
export async function throttlePage(context: BrowserContext, page: Page) {
|
||||
const session = await context.newCDPSession(page);
|
||||
await session.send('Network.emulateNetworkConditions', {
|
||||
offline: false,
|
||||
downloadThroughput: (1.5 * 1024 * 1024) / 8,
|
||||
uploadThroughput: (750 * 1024) / 8,
|
||||
latency: 40,
|
||||
connectionType: 'cellular3g',
|
||||
});
|
||||
await session.send('Emulation.setCPUThrottlingRate', { rate: 10 });
|
||||
}
|
||||
|
||||
let activePollsAbortController = new AbortController();
|
||||
|
||||
export const cancelAllPollers = () => {
|
||||
activePollsAbortController.abort();
|
||||
activePollsAbortController = new AbortController();
|
||||
};
|
||||
|
||||
export const poll = async <T>(
|
||||
page: Page,
|
||||
query: () => Promise<T>,
|
||||
callback?: (result: Awaited<T> | undefined) => boolean,
|
||||
) => {
|
||||
let result;
|
||||
const timeout = Date.now() + 10_000;
|
||||
const signal = activePollsAbortController.signal;
|
||||
|
||||
const terminate = callback || ((result: Awaited<T> | undefined) => !!result);
|
||||
while (!terminate(result) && Date.now() < timeout) {
|
||||
if (signal.aborted) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
result = await query();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
if (signal.aborted) {
|
||||
return;
|
||||
}
|
||||
if (page.isClosed()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await page.waitForTimeout(50);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
// rerun to trigger error if any
|
||||
result = await query();
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export const thumbnailUtils = {
|
||||
locator(page: Page) {
|
||||
return page.locator('[data-thumbnail-focus-container]');
|
||||
},
|
||||
withAssetId(page: Page, assetId: string) {
|
||||
return page.locator(`[data-thumbnail-focus-container][data-asset="${assetId}"]`);
|
||||
},
|
||||
selectButton(page: Page, assetId: string) {
|
||||
return page.locator(`[data-thumbnail-focus-container][data-asset="${assetId}"] button`);
|
||||
},
|
||||
selectedAsset(page: Page) {
|
||||
return page.locator('[data-thumbnail-focus-container]:has(button[aria-checked])');
|
||||
},
|
||||
async clickAssetId(page: Page, assetId: string) {
|
||||
await thumbnailUtils.withAssetId(page, assetId).click();
|
||||
},
|
||||
async queryThumbnailInViewport(page: Page, collector: (assetId: string) => boolean) {
|
||||
const assetIds: string[] = [];
|
||||
for (const thumb of await this.locator(page).all()) {
|
||||
const box = await thumb.boundingBox();
|
||||
if (box) {
|
||||
const assetId = await thumb.evaluate((e) => e.dataset.asset);
|
||||
if (collector?.(assetId!)) {
|
||||
return [assetId!];
|
||||
}
|
||||
assetIds.push(assetId!);
|
||||
}
|
||||
}
|
||||
return assetIds;
|
||||
},
|
||||
async getFirstInViewport(page: Page) {
|
||||
return await poll(page, () => thumbnailUtils.queryThumbnailInViewport(page, () => true));
|
||||
},
|
||||
async getAllInViewport(page: Page, collector: (assetId: string) => boolean) {
|
||||
return await poll(page, () => thumbnailUtils.queryThumbnailInViewport(page, collector));
|
||||
},
|
||||
async expectThumbnailIsFavorite(page: Page, assetId: string) {
|
||||
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-favorite]')).toHaveCount(1);
|
||||
},
|
||||
async expectThumbnailIsNotFavorite(page: Page, assetId: string) {
|
||||
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-favorite]')).toHaveCount(0);
|
||||
},
|
||||
async expectThumbnailIsArchive(page: Page, assetId: string) {
|
||||
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(1);
|
||||
},
|
||||
async expectThumbnailIsNotArchive(page: Page, assetId: string) {
|
||||
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(0);
|
||||
},
|
||||
async expectSelectedReadonly(page: Page, assetId: string) {
|
||||
// todo - need a data attribute for selected
|
||||
await expect(
|
||||
page.locator(
|
||||
`[data-thumbnail-focus-container][data-asset="${assetId}"] > .group.cursor-not-allowed > .rounded-xl`,
|
||||
),
|
||||
).toBeVisible();
|
||||
},
|
||||
async expectTimelineHasOnScreenAssets(page: Page) {
|
||||
const first = await thumbnailUtils.getFirstInViewport(page);
|
||||
if (page.isClosed()) {
|
||||
return;
|
||||
}
|
||||
expect(first).toBeTruthy();
|
||||
},
|
||||
async expectInViewport(page: Page, assetId: string) {
|
||||
const box = await poll(page, () => thumbnailUtils.withAssetId(page, assetId).boundingBox());
|
||||
if (page.isClosed()) {
|
||||
return;
|
||||
}
|
||||
expect(box).toBeTruthy();
|
||||
},
|
||||
async expectBottomIsTimelineBottom(page: Page, assetId: string) {
|
||||
const box = await thumbnailUtils.withAssetId(page, assetId).boundingBox();
|
||||
const gridBox = await timelineUtils.locator(page).boundingBox();
|
||||
if (page.isClosed()) {
|
||||
return;
|
||||
}
|
||||
expect(box!.y + box!.height).toBeCloseTo(gridBox!.y + gridBox!.height, 0);
|
||||
},
|
||||
async expectTopIsTimelineTop(page: Page, assetId: string) {
|
||||
const box = await thumbnailUtils.withAssetId(page, assetId).boundingBox();
|
||||
const gridBox = await timelineUtils.locator(page).boundingBox();
|
||||
if (page.isClosed()) {
|
||||
return;
|
||||
}
|
||||
expect(box!.y).toBeCloseTo(gridBox!.y, 0);
|
||||
},
|
||||
};
|
||||
export const timelineUtils = {
|
||||
locator(page: Page) {
|
||||
return page.locator('#asset-grid');
|
||||
},
|
||||
async waitForTimelineLoad(page: Page) {
|
||||
await expect(timelineUtils.locator(page)).toBeInViewport();
|
||||
await expect.poll(() => thumbnailUtils.locator(page).count()).toBeGreaterThan(0);
|
||||
},
|
||||
async getScrollTop(page: Page) {
|
||||
const queryTop = () =>
|
||||
page.evaluate(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
return document.querySelector('#asset-grid').scrollTop;
|
||||
});
|
||||
await expect.poll(queryTop).toBeGreaterThan(0);
|
||||
return await queryTop();
|
||||
},
|
||||
};
|
||||
|
||||
export const assetViewerUtils = {
|
||||
locator(page: Page) {
|
||||
return page.locator('#immich-asset-viewer');
|
||||
},
|
||||
async waitForViewerLoad(page: Page, asset: TimelineAssetConfig) {
|
||||
await page
|
||||
.locator(`img[draggable="false"][src="/api/assets/${asset.id}/thumbnail?size=preview&c=${asset.thumbhash}"]`)
|
||||
.or(page.locator(`video[poster="/api/assets/${asset.id}/thumbnail?size=preview&c=${asset.thumbhash}"]`))
|
||||
.waitFor();
|
||||
},
|
||||
async expectActiveAssetToBe(page: Page, assetId: string) {
|
||||
const activeElement = () =>
|
||||
page.evaluate(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
return document.activeElement?.dataset?.asset;
|
||||
});
|
||||
await expect(poll(page, activeElement, (result) => result === assetId)).resolves.toBe(assetId);
|
||||
},
|
||||
};
|
||||
export const pageUtils = {
|
||||
async deepLinkPhotosPage(page: Page, assetId: string) {
|
||||
await page.goto(`/photos?at=${assetId}`);
|
||||
await timelineUtils.waitForTimelineLoad(page);
|
||||
},
|
||||
async openPhotosPage(page: Page) {
|
||||
await page.goto(`/photos`);
|
||||
await timelineUtils.waitForTimelineLoad(page);
|
||||
},
|
||||
async openFavorites(page: Page) {
|
||||
await page.goto(`/favorites`);
|
||||
await timelineUtils.waitForTimelineLoad(page);
|
||||
},
|
||||
async openAlbumPage(page: Page, albumId: string) {
|
||||
await page.goto(`/albums/${albumId}`);
|
||||
await timelineUtils.waitForTimelineLoad(page);
|
||||
},
|
||||
async openArchivePage(page: Page) {
|
||||
await page.goto(`/archive`);
|
||||
await timelineUtils.waitForTimelineLoad(page);
|
||||
},
|
||||
async deepLinkAlbumPage(page: Page, albumId: string, assetId: string) {
|
||||
await page.goto(`/albums/${albumId}?at=${assetId}`);
|
||||
await timelineUtils.waitForTimelineLoad(page);
|
||||
},
|
||||
async goToAsset(page: Page, assetDate: string) {
|
||||
await timelineUtils.locator(page).hover();
|
||||
const stringDate = DateTime.fromISO(assetDate).toFormat('MMddyyyy,hh:mm:ss.SSSa');
|
||||
await page.keyboard.press('g');
|
||||
await page.locator('#datetime').pressSequentially(stringDate);
|
||||
await page.getByText('Confirm').click();
|
||||
},
|
||||
async selectDay(page: Page, day: string) {
|
||||
await page.getByTitle(day).hover();
|
||||
await page.locator('[data-group] .w-8').click();
|
||||
},
|
||||
async pauseTestDebug() {
|
||||
console.log('NOTE: pausing test indefinately for debug');
|
||||
await new Promise(() => void 0);
|
||||
},
|
||||
};
|
||||
@@ -54,16 +54,12 @@ test.describe('User Administration', () => {
|
||||
|
||||
await page.getByRole('button', { name: 'Edit' }).click();
|
||||
await expect(page.getByLabel('Admin User')).not.toBeChecked();
|
||||
await page.getByLabel('Admin User').click();
|
||||
await page.getByText('Admin User').click();
|
||||
await expect(page.getByLabel('Admin User')).toBeChecked();
|
||||
await page.getByRole('button', { name: 'Confirm' }).click();
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const userAdmin = await getUserAdmin({ id: user.userId }, { headers: asBearerAuth(admin.accessToken) });
|
||||
return userAdmin.isAdmin;
|
||||
})
|
||||
.toBe(true);
|
||||
const updated = await getUserAdmin({ id: user.userId }, { headers: asBearerAuth(admin.accessToken) });
|
||||
expect(updated.isAdmin).toBe(true);
|
||||
});
|
||||
|
||||
test('revoke admin access', async ({ context, page }) => {
|
||||
@@ -83,15 +79,11 @@ test.describe('User Administration', () => {
|
||||
|
||||
await page.getByRole('button', { name: 'Edit' }).click();
|
||||
await expect(page.getByLabel('Admin User')).toBeChecked();
|
||||
await page.getByLabel('Admin User').click();
|
||||
await page.getByText('Admin User').click();
|
||||
await expect(page.getByLabel('Admin User')).not.toBeChecked();
|
||||
await page.getByRole('button', { name: 'Confirm' }).click();
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const userAdmin = await getUserAdmin({ id: user.userId }, { headers: asBearerAuth(admin.accessToken) });
|
||||
return userAdmin.isAdmin;
|
||||
})
|
||||
.toBe(false);
|
||||
const updated = await getUserAdmin({ id: user.userId }, { headers: asBearerAuth(admin.accessToken) });
|
||||
expect(updated.isAdmin).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"target": "es2023",
|
||||
"target": "es2022",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"incremental": true,
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"add_birthday": "Voeg 'n verjaarsdag by",
|
||||
"add_endpoint": "Voeg Koppelvlakpunt by",
|
||||
"add_exclusion_pattern": "Voeg uitsgluitingspatrone by",
|
||||
"add_import_path": "Voeg invoerpad by",
|
||||
"add_location": "Voeg ligging by",
|
||||
"add_more_users": "Voeg meer gebruikers by",
|
||||
"add_partner": "Voeg vennoot by",
|
||||
@@ -68,6 +69,7 @@
|
||||
"disable_login": "Deaktiveer aanmelding",
|
||||
"duplicate_detection_job_description": "Begin masjienleer op bates om soortgelyke beelde op te spoor. Maak staat op Smart Search",
|
||||
"exclusion_pattern_description": "Met uitsluitingspatrone kan jy lêers en vouers ignoreer wanneer jy jou biblioteek skandeer. Dit is nuttig as jy vouers het wat lêers bevat wat jy nie wil invoer nie, soos RAW-lêers.",
|
||||
"external_library_management": "Eksterne Biblioteekbestuur",
|
||||
"face_detection": "Gesig herkenning",
|
||||
"face_detection_description": "Identifiseer die gesigte in media deur middel van masjienleer. Vir videos word slegs die duimnaelskets oorweeg. “Herlaai” (ver)werk al die media weer. “Stel terug” verwyder alle huidige gesigdata. “Onverwerk” plaas bates in die tou wat nog nie verwerk is nie. Geidentifiseerde gesigte sal ná voltooiing van Gesigidentifikasie vir Gesigherkenning in die tou geplaas word, om hulle in bestaande of nuwe persone te groepeer.",
|
||||
"facial_recognition_job_description": "Groepeer gesigte in mense in. Die stap is vinniger nadat Gesig Deteksie klaar is. \"Herstel\" (her-)groepeer alle gesigte. \"Vermiste\" plaas gesigte in ry wat nie 'n persoon gekoppel het nie.",
|
||||
@@ -96,8 +98,10 @@
|
||||
"job_not_concurrency_safe": "Hierdie taak kan nie gelyktydig uitgevoer word nie.",
|
||||
"job_settings": "Agtergrondtaakinstellings",
|
||||
"job_settings_description": "Bestuur werkgelyktydigheid",
|
||||
"job_status": "Werkstatus",
|
||||
"library_created": "Biblioteek geskep: {library}",
|
||||
"library_deleted": "Biblioteek verwyder",
|
||||
"library_import_path_description": "Spesifiseer 'n leer om in te neem. Hierdie leer, en al die sub leers, gaan deursoek word vir prente en videos.",
|
||||
"library_scanning": "Periodieke Soek",
|
||||
"library_scanning_description": "Stel periodieke deursoek van biblioteek in",
|
||||
"library_scanning_enable_description": "Aktiveer periodieke biblioteekskandering",
|
||||
|
||||
131
i18n/ar.json
131
i18n/ar.json
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"about": "حول",
|
||||
"about": "حَوْل",
|
||||
"account": "حساب",
|
||||
"account_settings": "إعدادات الحساب",
|
||||
"acknowledge": "أُدرك ذلك",
|
||||
@@ -17,6 +17,7 @@
|
||||
"add_birthday": "أضف تاريخ الميلاد",
|
||||
"add_endpoint": "اضف نقطة نهاية",
|
||||
"add_exclusion_pattern": "إضافة نمط إستثناء",
|
||||
"add_import_path": "إضافة مسار الإستيراد",
|
||||
"add_location": "إضافة موقع",
|
||||
"add_more_users": "إضافة مستخدمين آخرين",
|
||||
"add_partner": "أضف شريكًا",
|
||||
@@ -31,7 +32,6 @@
|
||||
"add_to_album_toggle": "تبديل التحديد لـ{album}",
|
||||
"add_to_albums": "إضافة الى البومات",
|
||||
"add_to_albums_count": "إضافه إلى البومات ({count})",
|
||||
"add_to_bottom_bar": "اضف الى",
|
||||
"add_to_shared_album": "إضافة إلى ألبوم مشارك",
|
||||
"add_upload_to_stack": "اضف رفع الى حزمة",
|
||||
"add_url": "إضافة رابط",
|
||||
@@ -67,7 +67,6 @@
|
||||
"confirm_reprocess_all_faces": "هل أنت متأكد أنك تريد إعادة معالجة جميع الوجوه؟ سيخلي هذا كل الأشخاص الذين سَميتَهم.",
|
||||
"confirm_user_password_reset": "هل أنت متأكد أنك تريد إعادة تعيين كلمة مرور {user}؟",
|
||||
"confirm_user_pin_code_reset": "هل انت متاكد من اعادة ضبط رمز PIN الخاص ب {user}؟",
|
||||
"copy_config_to_clipboard_description": "انسخ اعدادات النظام الحالية بتنسيق JSON الى الحافظة",
|
||||
"create_job": "إنشاء وظيفة",
|
||||
"cron_expression": "تعبير Cron",
|
||||
"cron_expression_description": "اضبط الفاصل الزمني للفحص باستخدام تنسيق cron. لمزيد من المعلومات يُرجى الرجوع إلى <link>Crontab Guru</link> على سبيل المثال",
|
||||
@@ -75,8 +74,7 @@
|
||||
"disable_login": "تعطيل تسجيل الدخول",
|
||||
"duplicate_detection_job_description": "بدء التعلم الآلي على المحتوى للعثور على الصور المتشابهة. يعتمد على البحث الذكي",
|
||||
"exclusion_pattern_description": "تتيح لك أنماط الاستبعاد تجاهل الملفات والمجلدات عند فحص مكتبتك. يعد هذا مفيدًا إذا كان لديك مجلدات تحتوي على ملفات لا تريد استيرادها، مثل ملفات RAW.",
|
||||
"export_config_as_json_description": "تحميل اعدادات النظام الحالية كملف بصيغة JSON",
|
||||
"external_libraries_page_description": "مشرف صفحة مكتبة خارجية",
|
||||
"external_library_management": "إدارة المكتبة الخارجية",
|
||||
"face_detection": "إكتشاف الوجوه",
|
||||
"face_detection_description": "اكتشف الوجوه في الأصول باستخدام التعلم الآلي. بالنسبة لمقاطع الفيديو، يتم اعتبار الصورة المصغرة فقط. \"تحديث\" (إعادة) معالجة جميع الأصول. \"إعادة تعيين\" تمسح أيضًا جميع بيانات الوجوه الحالية. \"مفقود\" يضع الأصول التي لم تتم معالجتها بعد في قائمة الانتظار. سيتم وضع الوجوه المكتشفة في قائمة الانتظار للتعرف على الوجه بعد اكتمال اكتشاف الوجه، وتجميعها في أشخاص موجودين أو جدد.",
|
||||
"facial_recognition_job_description": "تجميع الوجوه المكتشفة كأشخاص. يتم تنفيذ هذه الخطوة بعد اكتمال اكتشاف الوجه. خيار \"إعادة التعيين\" يعيد تجميع جميع الوجوه. خيار \"المفقود\" يضع في قائمة الانتظار الوجوه التي لم يتم تعيين شخص لها.",
|
||||
@@ -104,27 +102,23 @@
|
||||
"image_thumbnail_description": "صورة مصغرة صغيرة مع بيانات وصفية مجردة، تُستخدم عند عرض مجموعات من الصور مثل الجدول الزمني الرئيسي",
|
||||
"image_thumbnail_quality_description": "تتراوح جودة الصورة المصغرة من 1 إلى 100. كلما كانت الجودة أعلى كان ذلك أفضل، ولكنها تنتج ملفات أكبر وقد تقلل من استجابة التطبيق.",
|
||||
"image_thumbnail_title": "إعدادات الصورة المصغرة",
|
||||
"import_config_from_json_description": "استيراد اعدادات النظام بتحميل ملف اعدادات بصيغة JSON",
|
||||
"job_concurrency": "تزامن {job}",
|
||||
"job_created": "تم إنشاء الوظيفة",
|
||||
"job_not_concurrency_safe": "هذه الوظيفة غير آمنة للتشغيل المتزامن.",
|
||||
"job_settings": "إعدادات الوظائف",
|
||||
"job_settings_description": "إدارة تزامن الوظائف",
|
||||
"job_status": "حالة الوظيفة",
|
||||
"jobs_delayed": "{jobCount, plural, other {# مؤجلة}}",
|
||||
"jobs_failed": "{jobCount, plural, other {# فشلت}}",
|
||||
"library_created": "تم إنشاء المكتبة: {library}",
|
||||
"library_deleted": "تم حذف المكتبة",
|
||||
"library_details": "تفاصيل المكتبة",
|
||||
"library_folder_description": "حدد مجلد للاستيراد. هذا المجلد مع المجلدات الفرعية، سيتم تفحصهم للصور والفديوات.",
|
||||
"library_remove_exclusion_pattern_prompt": "هل انت متاكد انك تريد ازالة نمط الاستبعاد هذا؟",
|
||||
"library_remove_folder_prompt": "هل انت متاكد انك تريد ازالة مجلد الاستيراد هذا؟",
|
||||
"library_import_path_description": "حدد مجلدًا للاستيراد. سيتم فحص هذا المجلد، بما في ذلك المجلدات الفرعية، بحثًا عن الصور ومقاطع الفيديو.",
|
||||
"library_scanning": "المسح الدوري",
|
||||
"library_scanning_description": "إعداد مسح المكتبة الدوري",
|
||||
"library_scanning_enable_description": "تفعيل مسح المكتبة الدوري",
|
||||
"library_settings": "المكتبة الخارجية",
|
||||
"library_settings_description": "إدارة إعدادات المكتبة الخارجية",
|
||||
"library_tasks_description": "مسح المكتبات الخارجية للعثور على الأصول الجديدة و/أو المتغيرة",
|
||||
"library_updated": "مكتبة محدثة",
|
||||
"library_watching_enable_description": "مراقبة المكتبات الخارجية لاكتشاف تغييرات الملفات",
|
||||
"library_watching_settings": "مراقبة المكتبات [تجريبي]",
|
||||
"library_watching_settings_description": "راقب تلقائيًا التغييرات في الملفات",
|
||||
@@ -160,18 +154,6 @@
|
||||
"machine_learning_min_detection_score_description": "الحد الأدنى لنقطة الثقة لاكتشاف الوجه، تتراوح من 0 إلى 1. القيم الأقل ستكشف عن المزيد من الوجوه ولكن قد تؤدي إلى نتائج إيجابية خاطئة.",
|
||||
"machine_learning_min_recognized_faces": "الحد الأدنى لعدد الوجوه المتعرف عليها",
|
||||
"machine_learning_min_recognized_faces_description": "الحد الأدنى لعدد الوجوه المتعرف عليها لإنشاء شخص. زيادة هذا الرقم يجعل التعرف على الوجوه أكثر دقة على حساب زيادة احتمال عدم تعيين الوجه لشخص ما.",
|
||||
"machine_learning_ocr": "التعرف البصري على الحروف",
|
||||
"machine_learning_ocr_description": "استخدم التعلم الآلي للتعرف على النصوص في الصور",
|
||||
"machine_learning_ocr_enabled": "تفعيل التعرف البصري على الحروف",
|
||||
"machine_learning_ocr_enabled_description": "في حال تعطيل هذه الميزة، لن تخضع الصور لعملية التعرف على النصوص.",
|
||||
"machine_learning_ocr_max_resolution": "أقصى دقة",
|
||||
"machine_learning_ocr_max_resolution_description": "سيتم تغيير حجم المعاينات التي تتجاوز هذه الدقة مع الحفاظ على نسبة العرض إلى الارتفاع. القيم الأعلى توفر دقة أكبر، ولكنها تستغرق وقتًا أطول للمعالجة وتستهلك المزيد من الذاكرة.",
|
||||
"machine_learning_ocr_min_detection_score": "الحد الأدنى لدرجة الكشف",
|
||||
"machine_learning_ocr_min_detection_score_description": "لحد الأدنى لدرجة الثقة المطلوبة لاكتشاف النص، وتتراوح قيمتها من 0 إلى 1. ستؤدي القيم الأقل إلى اكتشاف المزيد من النصوص ولكنها قد تؤدي إلى نتائج إيجابية خاطئة.",
|
||||
"machine_learning_ocr_min_recognition_score": "الحد الأدنى لدرجة التعرّف",
|
||||
"machine_learning_ocr_min_score_recognition_description": "الحد الأدنى لدرجة الثقة المطلوبة للنصوص المكتشفة ليتم التعرف عليها، وتتراوح من 0 إلى 1. ستؤدي القيم الأقل إلى التعرف على المزيد من النصوص ولكنها قد تؤدي إلى نتائج إيجابية خاطئة.",
|
||||
"machine_learning_ocr_model": "نموذج التعرف البصري على الحروف",
|
||||
"machine_learning_ocr_model_description": "تتميز نماذج الخوادم بدقة أكبر من نماذج الأجهزة المحمولة، ولكنها تستغرق وقتًا أطول في المعالجة وتستهلك ذاكرة أكبر.",
|
||||
"machine_learning_settings": "إعدادات التعلم الآلي",
|
||||
"machine_learning_settings_description": "إدارة ميزات وإعدادات التعلم الآلي",
|
||||
"machine_learning_smart_search": "البحث الذكي",
|
||||
@@ -179,12 +161,7 @@
|
||||
"machine_learning_smart_search_enabled": "تفعيل البحث الذكي",
|
||||
"machine_learning_smart_search_enabled_description": "إذا تم تعطيله، فلن يتم ترميز الصور للبحث الذكي.",
|
||||
"machine_learning_url_description": "عنوان URL لخادم التعلم الآلي. إذا تم توفير أكثر من عنوان URL واحد، سيتم محاولة الاتصال بكل خادم على حدة حتى يستجيب أحدهم بنجاح، بدءًا من الأول إلى الأخير. سيتم تجاهل الخوادم التي لا تستجيب مؤقتًا حتى تعود للعمل.",
|
||||
"maintenance_settings": "صيانة",
|
||||
"maintenance_settings_description": "ضع Immich في وضع الصيانة.",
|
||||
"maintenance_start": "ابدأ وضع الصيانة",
|
||||
"maintenance_start_error": "فشل البدء في وضع الصيانة.",
|
||||
"manage_concurrency": "إدارة التزامن",
|
||||
"manage_concurrency_description": "انتقل الى صفحة الاعمال لادارة تزامن المهام",
|
||||
"manage_log_settings": "إدارة إعدادات السجلات",
|
||||
"map_dark_style": "النمط الداكن",
|
||||
"map_enable_description": "تفعيل ميزات الخرائط",
|
||||
@@ -234,8 +211,6 @@
|
||||
"notification_email_ignore_certificate_errors_description": "تجاهل أخطاء التحقق من صحة شهادة TLS (غير مستحسن)",
|
||||
"notification_email_password_description": "كلمة المرور المستخدمة للمصادقة مع خادم البريد الإلكتروني",
|
||||
"notification_email_port_description": "منفذ خادم البريد الإلكتروني (مثلاً 25، 465، أو 587)",
|
||||
"notification_email_secure": "بروتوكول نقل البريد البسيط الآمن SMTPS",
|
||||
"notification_email_secure_description": "استخدم بروتوكول SMTPS (بروتوكول SMTP عبر TLS)",
|
||||
"notification_email_sent_test_email_button": "إرسال بريد إلكتروني تجريبي وحفظ التعديلات",
|
||||
"notification_email_setting_description": "إعدادات إرسال إشعارات البريد الإلكتروني",
|
||||
"notification_email_test_email": "إرسال بريد تجريبي",
|
||||
@@ -268,7 +243,6 @@
|
||||
"oauth_storage_quota_default_description": "الحصة بالجيجابايت التي سيتم استخدامها عندما لا يتم توفير مطالبة.",
|
||||
"oauth_timeout": "نفاذ وقت الطلب",
|
||||
"oauth_timeout_description": "نفاذ وقت الطلب بالميلي ثانية",
|
||||
"ocr_job_description": "استخدم التعلم الآلي للتعرف على النصوص في الصور",
|
||||
"password_enable_description": "تسجيل الدخول باستخدام البريد الكتروني وكلمة المرور",
|
||||
"password_settings": "تسجيل الدخول بكلمة المرور",
|
||||
"password_settings_description": "إدارة تسجيل الدخول بكلمة المرور",
|
||||
@@ -290,10 +264,8 @@
|
||||
"server_public_users_description": "يتم إدراج جميع المستخدمين (الاسم والبريد الإلكتروني) عند إضافة مستخدم إلى الألبومات المشتركة. عند تعطيل هذه الميزة، ستكون قائمة المستخدمين متاحة فقط لمستخدمي الإدارة.",
|
||||
"server_settings": "إعدادات الخادم",
|
||||
"server_settings_description": "إدارة إعدادات الخادم",
|
||||
"server_stats_page_description": "صفحة إحصائيات مسؤول الخادم",
|
||||
"server_welcome_message": "الرسالة الترحيبية",
|
||||
"server_welcome_message_description": "رسالة تُعرض على صفحة تسجيل الدخول.",
|
||||
"settings_page_description": "صفخة اعدادات المسؤول",
|
||||
"sidecar_job": "البيانات الوصفية الجانبية",
|
||||
"sidecar_job_description": "اكتشاف أو مزامنة البيانات التعريفية الجانبية من نظام الملفات",
|
||||
"slideshow_duration_description": "عدد الثواني لعرض كل صورة",
|
||||
@@ -412,8 +384,7 @@
|
||||
"user_restore_scheduled_removal": "استعادة المستخدم - تمت جدولة الإزالة في {date, date, long}",
|
||||
"user_settings": "إعدادات المستخدم",
|
||||
"user_settings_description": "إدارة إعدادات المستخدم",
|
||||
"user_successfully_removed": "المستخدم {email} تمت ازالته بنجاح.",
|
||||
"users_page_description": "صفحة ادارة المستخدمين",
|
||||
"user_successfully_removed": "تمت إزالة المستخدم {email} بنجاح.",
|
||||
"version_check_enabled_description": "تفعيل التحقق من الإصدارات الجديدة",
|
||||
"version_check_implications": "تعتمد ميزة التحقق من الإصدار على التواصل الدوري مع github.com",
|
||||
"version_check_settings": "التحقق من الإصدار",
|
||||
@@ -431,11 +402,11 @@
|
||||
"advanced_settings_prefer_remote_subtitle": "تكون بعض الأجهزة بطيئة للغاية في تحميل الصور المصغرة من الأصول المحلية. قم بتفعيل هذا الخيار لتحميل الصور البعيدة بدلاً من ذلك.",
|
||||
"advanced_settings_prefer_remote_title": "تفضل الصور البعيدة",
|
||||
"advanced_settings_proxy_headers_subtitle": "عرف عناوين الوكيل التي يستخدمها Immich لارسال كل طلب شبكي",
|
||||
"advanced_settings_proxy_headers_title": "عناوين الوكيل المخصصة [تجريبية]",
|
||||
"advanced_settings_proxy_headers_title": "عناوين الوكيل",
|
||||
"advanced_settings_readonly_mode_subtitle": "تتيح هذه الميزة وضع العرض فقط، حيث يمكن للمستخدم معاينة الصور فقط، بينما يتم تعطيل جميع الخيارات الأخرى مثل تحديد عدة صور، أو مشاركتها، أو بثها، أو حذفها. يمكن تفعيل/تعطيل وضع العرض فقط من خلال صورة المستخدم في الشاشة الرئيسية",
|
||||
"advanced_settings_readonly_mode_title": "وضع القراءة فقط",
|
||||
"advanced_settings_self_signed_ssl_subtitle": "تخطي التحقق من شهادة SSL لخادم النقطة النهائي. مكلوب للشهادات الموقعة ذاتيا.",
|
||||
"advanced_settings_self_signed_ssl_title": "السماح بشهادات SSL الموقعة ذاتيًا [تجريبية]",
|
||||
"advanced_settings_self_signed_ssl_title": "السماح بشهادات SSL الموقعة ذاتيًا",
|
||||
"advanced_settings_sync_remote_deletions_subtitle": "حذف او استعادة تلقائي للاصول على هذا الجهاز عند تنفيذ العملية على الويب",
|
||||
"advanced_settings_sync_remote_deletions_title": "مزامنة عمليات الحذف عن بعد [تجريبي]",
|
||||
"advanced_settings_tile_subtitle": "إعدادات المستخدم المتقدمة",
|
||||
@@ -444,7 +415,6 @@
|
||||
"age_months": "عمر {months, plural, one {# شهر} other {# أشهر}}",
|
||||
"age_year_months": "عمر سنة واحدة، {months, plural, one {# شهر} other {# أشهر}}",
|
||||
"age_years": "{years, plural, other {العمر #}}",
|
||||
"album": "البوم",
|
||||
"album_added": "تمت إضافة الألبوم",
|
||||
"album_added_notification_setting_description": "تلقي إشعارًا بالبريد الإلكتروني عند إضافتك إلى ألبوم مشترك",
|
||||
"album_cover_updated": "تم تحديث غلاف الألبوم",
|
||||
@@ -490,21 +460,16 @@
|
||||
"allow_edits": "إسمح بالتعديل",
|
||||
"allow_public_user_to_download": "السماح لأي مستخدم عام بالتنزيل",
|
||||
"allow_public_user_to_upload": "السماح للمستخدم العام بالرفع",
|
||||
"allowed": "مسموح",
|
||||
"alt_text_qr_code": "صورة رمز الاستجابة السريعة (QR)",
|
||||
"anti_clockwise": "عكس اتجاه عقارب الساعة",
|
||||
"api_key": "مفتاح API",
|
||||
"api_key_description": "سيتم عرض هذه القيمة مرة واحدة فقط. يرجى التأكد من نسخها قبل إغلاق النافذة.",
|
||||
"api_key_empty": "يجب ألا يكون اسم مفتاح API فارغًا",
|
||||
"api_keys": "مفاتيح API",
|
||||
"app_architecture_variant": "متغير (الهندسة المعمارية)",
|
||||
"app_bar_signout_dialog_content": "هل أنت متأكد أنك تريد تسجيل الخروج؟",
|
||||
"app_bar_signout_dialog_ok": "نعم",
|
||||
"app_bar_signout_dialog_title": "خروج",
|
||||
"app_download_links": "روابط تحميل التطبيق",
|
||||
"app_settings": "إعدادات التطبيق",
|
||||
"app_stores": "متاجر التطبيقات",
|
||||
"app_update_available": "تحديث التطبيق متاح",
|
||||
"appears_in": "يظهر في",
|
||||
"apply_count": "تطبيق ({count, number})",
|
||||
"archive": "الأرشيف",
|
||||
@@ -588,7 +553,6 @@
|
||||
"backup_albums_sync": "مزامنة ألبومات النسخ الاحتياطي",
|
||||
"backup_all": "الجميع",
|
||||
"backup_background_service_backup_failed_message": "فشل في النسخ الاحتياطي للأصول. جارٍ إعادة المحاولة…",
|
||||
"backup_background_service_complete_notification": "تم الانتهاء من النسخ الاحتياطي للأصول",
|
||||
"backup_background_service_connection_failed_message": "فشل في الاتصال بالخادم. جارٍ إعادة المحاولة…",
|
||||
"backup_background_service_current_upload_notification": "تحميل {filename}",
|
||||
"backup_background_service_default_notification": "التحقق من الأصول الجديدة…",
|
||||
@@ -698,8 +662,6 @@
|
||||
"change_password_description": "هذه إما هي المرة الأولى التي تقوم فيها بتسجيل الدخول إلى النظام أو أنه تم تقديم طلب لتغيير كلمة المرور الخاصة بك. الرجاء إدخال كلمة المرور الجديدة أدناه.",
|
||||
"change_password_form_confirm_password": "تأكيد كلمة المرور",
|
||||
"change_password_form_description": "مرحبًا {name}،\n\nاما ان تكون هذه هي المرة الأولى التي تقوم فيها بالتسجيل في النظام أو تم تقديم طلب لتغيير كلمة المرور الخاصة بك. الرجاء إدخال كلمة المرور الجديدة أدناه.",
|
||||
"change_password_form_log_out": "تسجيل الخروج من جميع الأجهزة الأخرى",
|
||||
"change_password_form_log_out_description": "يُنصح بتسجيل الخروج من جميع الأجهزة الأخرى",
|
||||
"change_password_form_new_password": "كلمة المرور الجديدة",
|
||||
"change_password_form_password_mismatch": "كلمة المرور غير مطابقة",
|
||||
"change_password_form_reenter_new_password": "أعد إدخال كلمة مرور جديدة",
|
||||
@@ -727,14 +689,13 @@
|
||||
"client_cert_invalid_msg": "ملف شهادة عميل غير صالحة او كلمة سر غير صحيحة",
|
||||
"client_cert_remove_msg": "تم ازالة شهادة العميل",
|
||||
"client_cert_subtitle": "يدعم صيغ PKCS12 (.p12, .pfx)فقط. استيراد/ازالة الشهادات متاح فقط قبل تسجيل الدخول",
|
||||
"client_cert_title": "شهادة مستخدم SSL [تجريبية]",
|
||||
"client_cert_title": "شهادة مستخدم SSL",
|
||||
"clockwise": "باتجاه عقارب الساعة",
|
||||
"close": "إغلاق",
|
||||
"collapse": "طي",
|
||||
"collapse_all": "طيّ الكل",
|
||||
"color": "اللون",
|
||||
"color_theme": "نمط الألوان",
|
||||
"command": "امر",
|
||||
"comment_deleted": "تم حذف التعليق",
|
||||
"comment_options": "خيارات التعليق",
|
||||
"comments_and_likes": "التعليقات والإعجابات",
|
||||
@@ -778,7 +739,6 @@
|
||||
"create": "انشاء",
|
||||
"create_album": "إنشاء ألبوم",
|
||||
"create_album_page_untitled": "بدون اسم",
|
||||
"create_api_key": "إنشاء مفتاح API",
|
||||
"create_library": "إنشاء مكتبة",
|
||||
"create_link": "إنشاء رابط",
|
||||
"create_link_to_share": "إنشاء رابط للمشاركة",
|
||||
@@ -808,7 +768,6 @@
|
||||
"daily_title_text_date_year": "E ، MMM DD ، yyyy",
|
||||
"dark": "معتم",
|
||||
"dark_theme": "تبديل المظهر الداكن",
|
||||
"date": "تاريخ",
|
||||
"date_after": "التارخ بعد",
|
||||
"date_and_time": "التاريخ و الوقت",
|
||||
"date_before": "التاريخ قبل",
|
||||
@@ -911,6 +870,8 @@
|
||||
"edit_description_prompt": "الرجاء اختيار وصف جديد:",
|
||||
"edit_exclusion_pattern": "تعديل نمط الاستبعاد",
|
||||
"edit_faces": "تعديل الوجوه",
|
||||
"edit_import_path": "تعديل مسار الاستيراد",
|
||||
"edit_import_paths": "تعديل مسارات الاستيراد",
|
||||
"edit_key": "تعديل المفتاح",
|
||||
"edit_link": "تغيير الرابط",
|
||||
"edit_location": "تعديل الموقع",
|
||||
@@ -982,8 +943,8 @@
|
||||
"failed_to_stack_assets": "فشل في تكديس المحتويات",
|
||||
"failed_to_unstack_assets": "فشل في فصل المحتويات",
|
||||
"failed_to_update_notification_status": "فشل في تحديث حالة الإشعار",
|
||||
"import_path_already_exists": "مسار الاستيراد هذا موجود مسبقًا.",
|
||||
"incorrect_email_or_password": "بريد أو كلمة مرور غير صحيحة",
|
||||
"library_folder_already_exists": "مسار الاستيراد موجود بالفعل.",
|
||||
"paths_validation_failed": "فشل في التحقق من {paths, plural, one {# مسار} other {# مسارات}}",
|
||||
"profile_picture_transparent_pixels": "لا يمكن أن تحتوي صور الملف الشخصي على أجزاء/بكسلات شفافة. يرجى التكبير و/أو تحريك الصورة.",
|
||||
"quota_higher_than_disk_size": "لقد قمت بتعيين حصة نسبية أعلى من حجم القرص",
|
||||
@@ -992,6 +953,7 @@
|
||||
"unable_to_add_assets_to_shared_link": "تعذر إضافة المحتويات إلى الرابط المشترك",
|
||||
"unable_to_add_comment": "تعذر إضافة التعليق",
|
||||
"unable_to_add_exclusion_pattern": "تعذر إضافة نمط الإستبعاد",
|
||||
"unable_to_add_import_path": "تعذر إضافة مسار الإستيراد",
|
||||
"unable_to_add_partners": "تعذر إضافة الشركاء",
|
||||
"unable_to_add_remove_archive": "تعذر {archived, select, true {إزالة المحتوى من} other {إضافة المحتوى إلى}} الأرشيف",
|
||||
"unable_to_add_remove_favorites": "تعذر {favorite, select, true {إضافة المحتوى إلى} other {إزالة المحتوى من}} المفضلة",
|
||||
@@ -1014,10 +976,12 @@
|
||||
"unable_to_delete_asset": "غير قادر على حذف المحتوى",
|
||||
"unable_to_delete_assets": "حدث خطأ أثناء حذف المحتويات",
|
||||
"unable_to_delete_exclusion_pattern": "غير قادر على حذف نمط الاستبعاد",
|
||||
"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_empty_trash": "غير قادر على إفراغ سلة المهملات",
|
||||
"unable_to_enter_fullscreen": "غير قادر على الدخول إلى وضع ملء الشاشة",
|
||||
"unable_to_exit_fullscreen": "غير قادر على الخروج من وضع ملء الشاشة",
|
||||
@@ -1068,13 +1032,11 @@
|
||||
"unable_to_update_user": "غير قادر على تحديث المستخدم",
|
||||
"unable_to_upload_file": "تعذر رفع الملف"
|
||||
},
|
||||
"exclusion_pattern": "نمط استبعاد",
|
||||
"exif": "Exif (صيغة ملف صوري قابل للتبادل)",
|
||||
"exif_bottom_sheet_description": "اضف وصفا...",
|
||||
"exif_bottom_sheet_description_error": "خطأ في تحديث الوصف",
|
||||
"exif_bottom_sheet_details": "تفاصيل",
|
||||
"exif_bottom_sheet_location": "موقع",
|
||||
"exif_bottom_sheet_no_description": "لا يوجد وصف",
|
||||
"exif_bottom_sheet_people": "الناس",
|
||||
"exif_bottom_sheet_person_add_person": "اضف اسما",
|
||||
"exit_slideshow": "خروج من العرض التقديمي",
|
||||
@@ -1113,7 +1075,6 @@
|
||||
"features_setting_description": "إدارة ميزات التطبيق",
|
||||
"file_name": "إسم الملف",
|
||||
"file_name_or_extension": "اسم الملف أو امتداده",
|
||||
"file_size": "حجم الملف",
|
||||
"filename": "اسم الملف",
|
||||
"filetype": "نوع الملف",
|
||||
"filter": "تصفية",
|
||||
@@ -1128,7 +1089,6 @@
|
||||
"folders_feature_description": "تصفح عرض المجلد للصور ومقاطع الفيديو الموجودة على نظام الملفات",
|
||||
"forgot_pin_code_question": "هل نسيت رمز الPIN الخاص بك؟",
|
||||
"forward": "إلى الأمام",
|
||||
"full_path": "مسار كامل:{path}",
|
||||
"gcast_enabled": "كوكل كاست",
|
||||
"gcast_enabled_description": "تقوم هذه الميزة بتحميل الموارد الخارجية من Google حتى تعمل.",
|
||||
"general": "عام",
|
||||
@@ -1154,7 +1114,7 @@
|
||||
"hash_asset": "عمل Hash للأصل (للملف)",
|
||||
"hashed_assets": "أصول (ملفات) تم عمل Hash لها",
|
||||
"hashing": "يتم عمل Hash",
|
||||
"header_settings_add_header_tip": "إضافة رأس الصفحة",
|
||||
"header_settings_add_header_tip": "اضاف راس",
|
||||
"header_settings_field_validator_msg": "القيمة لا يمكن ان تكون فارغة",
|
||||
"header_settings_header_name_input": "اسم الرأس",
|
||||
"header_settings_header_value_input": "قيمة الرأس",
|
||||
@@ -1165,7 +1125,6 @@
|
||||
"hide_named_person": "إخفاء الشخص {name}",
|
||||
"hide_password": "اخفاء كلمة المرور",
|
||||
"hide_person": "اخفاء الشخص",
|
||||
"hide_text_recognition": "اخفاء التعرف على النص",
|
||||
"hide_unnamed_people": "إخفاء الأشخاص بدون إسم",
|
||||
"home_page_add_to_album_conflicts": "تمت إضافة {added} أصول إلى الألبوم {album}. {failed} أصول موجودة بالفعل في الألبوم.",
|
||||
"home_page_add_to_album_err_local": "لا يمكن إضافة الأصول المحلية إلى الألبومات حتى الآن ، سوف يتخطى",
|
||||
@@ -1211,8 +1170,6 @@
|
||||
"import_path": "مسار الاستيراد",
|
||||
"in_albums": "في {count, plural, one {# ألبوم } other {# ألبومات}}",
|
||||
"in_archive": "في الأرشيف",
|
||||
"in_year": "في {year}",
|
||||
"in_year_selector": "في",
|
||||
"include_archived": "تشمل الأرشفة",
|
||||
"include_shared_albums": "تضمين الألبومات المشتركة",
|
||||
"include_shared_partner_assets": "تضمين محتويات الشريك المشتركة",
|
||||
@@ -1249,7 +1206,6 @@
|
||||
"language_setting_description": "اختر لغتك المفضلة",
|
||||
"large_files": "ملفات كبيرة",
|
||||
"last": "الاخير",
|
||||
"last_months": "{count, plural, one {شهر فائت} other {اشهر # فائتة}}",
|
||||
"last_seen": "اخر ظهور",
|
||||
"latest_version": "احدث اصدار",
|
||||
"latitude": "خط العرض",
|
||||
@@ -1259,8 +1215,6 @@
|
||||
"let_others_respond": "دع الآخرين يستجيبون",
|
||||
"level": "المستوى",
|
||||
"library": "مكتبة",
|
||||
"library_add_folder": "اضافة مجلد",
|
||||
"library_edit_folder": "تعديل مجلد",
|
||||
"library_options": "خيارات المكتبة",
|
||||
"library_page_device_albums": "ألبومات على الجهاز",
|
||||
"library_page_new_album": "البوم جديد",
|
||||
@@ -1284,7 +1238,6 @@
|
||||
"local_media_summary": "ملخص الملفات المحلية",
|
||||
"local_network": "شبكة محلية",
|
||||
"local_network_sheet_info": "سيتصل التطبيق بالخادم من خلال عنوان URL هذا عند استخدام شبكة Wi-Fi المحددة",
|
||||
"location": "موقع",
|
||||
"location_permission": "اذن الموقع",
|
||||
"location_permission_content": "من أجل استخدام ميزة التبديل التلقائي، يحتاج Immich إلى إذن موقع دقيق حتى يتمكن من قراءة اسم شبكة Wi-Fi الحالية",
|
||||
"location_picker_choose_on_map": "اختر على الخريطة",
|
||||
@@ -1332,17 +1285,8 @@
|
||||
"loop_videos_description": "فَعْل لتكرار مقطع فيديو تلقائيًا في عارض التفاصيل.",
|
||||
"main_branch_warning": "أنت تستخدم إصداراً قيد التطوير؛ ونحن نوصي بشدة باستخدام إصدار النشر!",
|
||||
"main_menu": "القائمة الرئيسية",
|
||||
"maintenance_description": "يجب وضع Immich في وضع الصيانة <link>وضع الصيانة</link>.",
|
||||
"maintenance_end": "انهاء وضع الصيانة",
|
||||
"maintenance_end_error": "فشل في انهاء وضع الصيانة.",
|
||||
"maintenance_logged_in_as": "حاليا مسجل باسم {user}",
|
||||
"maintenance_title": "غير متوفر مؤقتا",
|
||||
"make": "صنع",
|
||||
"manage_geolocation": "إدارة الموقع",
|
||||
"manage_media_access_rationale": "الاذن المطلوب للتعامل السليم لنقل الاصول الى سلة المهملات واعادتها منها.",
|
||||
"manage_media_access_settings": "فتح الاعدادات",
|
||||
"manage_media_access_subtitle": "السماح لبرنامج Immich بإدارة ونقل ملفات الوسائط.",
|
||||
"manage_media_access_title": "وصول ادارة الوسائط",
|
||||
"manage_shared_links": "إدارة الروابط المشتركة",
|
||||
"manage_sharing_with_partners": "إدارة المشاركة مع الشركاء",
|
||||
"manage_the_app_settings": "إدارة إعدادات التطبيق",
|
||||
@@ -1398,15 +1342,12 @@
|
||||
"minute": "دقيقة",
|
||||
"minutes": "دقائق",
|
||||
"missing": "المفقودة",
|
||||
"mobile_app": "تطبيق الجوال",
|
||||
"mobile_app_download_onboarding_note": "قم بتنزيل التطبيق المصاحب للهاتف المحمول باستخدام الخيارات التالية",
|
||||
"model": "نموذج",
|
||||
"month": "شهر",
|
||||
"monthly_title_text_date_format": "ط ط ط",
|
||||
"more": "المزيد",
|
||||
"move": "تحريك",
|
||||
"move_off_locked_folder": "تحريك خارج المجلد المقفل",
|
||||
"move_to": "نقل الى",
|
||||
"move_to_lock_folder_action_prompt": "{count} اضيف إلى المجلد المقفل",
|
||||
"move_to_locked_folder": "النقل الى مجلد مغلق",
|
||||
"move_to_locked_folder_confirmation": "هذه الصور والفديوات ستتم ازالتها من جميع الالبومات، ويمكنان تتم مشاهدتها فقط من خلال المجلد المقفل",
|
||||
@@ -1419,8 +1360,6 @@
|
||||
"my_albums": "ألبوماتي",
|
||||
"name": "الاسم",
|
||||
"name_or_nickname": "الاسم أو اللقب",
|
||||
"navigate": "التنقل",
|
||||
"navigate_to_time": "انتقل إلى الوقت",
|
||||
"network_requirement_photos_upload": "استخدام بيانات الهاتف المحمول لعمل نسخة احتياطية للصور",
|
||||
"network_requirement_videos_upload": "استخدام بيانات الهاتف المحمول لعمل نسخة احتياطية لمقاطع الفيديو",
|
||||
"network_requirements": "متطلبات الشبكة",
|
||||
@@ -1430,13 +1369,11 @@
|
||||
"never": "أبداً",
|
||||
"new_album": "البوم جديد",
|
||||
"new_api_key": "مفتاح API جديد",
|
||||
"new_date_range": "نطاق تاريخ جديد",
|
||||
"new_password": "كلمة المرور الجديدة",
|
||||
"new_person": "شخص جديد",
|
||||
"new_pin_code": "رمز PIN الجديد",
|
||||
"new_pin_code_subtitle": "هذه أول مرة تدخل فيها إلى المجلد المقفل. أنشئ رمزًا PIN للوصول بامان إلى هذه الصفحة",
|
||||
"new_timeline": "الخط الزمني الجديد",
|
||||
"new_update": "تحديث جديد",
|
||||
"new_user_created": "تم إنشاء مستخدم جديد",
|
||||
"new_version_available": "إصدار جديد متاح",
|
||||
"newest_first": "الأحدث أولاً",
|
||||
@@ -1452,14 +1389,12 @@
|
||||
"no_cast_devices_found": "لم يتم ايجاد جهاز بث",
|
||||
"no_checksum_local": "لا توجد بيانات تحقق متاحة - يتعذر تحميل الاصول المحلية",
|
||||
"no_checksum_remote": "لا يوجد رمز تحقق متاح - يتعذر تحميل الاصل من الموقع البعيد",
|
||||
"no_devices": "لا يوجد اجهزة مرخصة",
|
||||
"no_duplicates_found": "لم يتم العثور على أي تكرارات.",
|
||||
"no_exif_info_available": "لا تتوفر معلومات exif",
|
||||
"no_explore_results_message": "قم برفع المزيد من الصور لاستكشاف مجموعتك.",
|
||||
"no_favorites_message": "أضف المفضلة للعثور بسرعة على أفضل الصور ومقاطع الفيديو",
|
||||
"no_libraries_message": "إنشاء مكتبة خارجية لعرض الصور ومقاطع الفيديو الخاصة بك",
|
||||
"no_local_assets_found": "لم يتم العثور على أي اصول محلية تتطابق مع قيمة التحقق هذه",
|
||||
"no_location_set": "لم يتم تحديد موقع",
|
||||
"no_locked_photos_message": "الصور والفديوهات في المجلد المقفل مخفية ولن تصهر في التصفح او البحث في مكتبتك.",
|
||||
"no_name": "لا اسم",
|
||||
"no_notifications": "لا توجد تنبيهات",
|
||||
@@ -1470,7 +1405,6 @@
|
||||
"no_results_description": "جرب كلمة رئيسية مرادفة أو أكثر عمومية",
|
||||
"no_shared_albums_message": "قم بإنشاء ألبوم لمشاركة الصور ومقاطع الفيديو مع الأشخاص في شبكتك",
|
||||
"no_uploads_in_progress": "لا يوجد اي ملفات قيد الرفع",
|
||||
"not_allowed": "غير مسموح",
|
||||
"not_available": "غير متاح",
|
||||
"not_in_any_album": "ليست في أي ألبوم",
|
||||
"not_selected": "لم يختار",
|
||||
@@ -1485,9 +1419,6 @@
|
||||
"notifications": "إشعارات",
|
||||
"notifications_setting_description": "إدارة الإشعارات",
|
||||
"oauth": "OAuth",
|
||||
"obtainium_configurator": "مُهيئ Obtainium",
|
||||
"obtainium_configurator_instructions": "استخدم Obtainium لتثبيت تطبيق Android وتحديثه مباشرةً من صفحة إصدارات Immich على GitHub. أنشئ مفتاح API واختر الإصدار المناسب لإنشاء رابط تهيئة Obtainium الخاص بك",
|
||||
"ocr": "التعرف البصري على الحروف",
|
||||
"official_immich_resources": "الموارد الرسمية لشركة Immich",
|
||||
"offline": "غير متصل",
|
||||
"offset": "ازاحة",
|
||||
@@ -1519,7 +1450,6 @@
|
||||
"other_variables": "متغيرات أخرى",
|
||||
"owned": "مملوكة",
|
||||
"owner": "المالك",
|
||||
"page": "صفحة",
|
||||
"partner": "شريك",
|
||||
"partner_can_access": "يستطيع {partner} الوصول",
|
||||
"partner_can_access_assets": "جميع الصور ومقاطع الفيديو الخاصة بك باستثناء تلك الموجودة في المؤرشفة والمحذوفة",
|
||||
@@ -1582,8 +1512,6 @@
|
||||
"photos_count": "{count, plural, one {{count, number} صورة} other {{count, number} صور}}",
|
||||
"photos_from_previous_years": "صور من السنوات السابقة",
|
||||
"pick_a_location": "اختر موقعًا",
|
||||
"pick_custom_range": "نطاق مخصص",
|
||||
"pick_date_range": "حدد نطاق التاريخ",
|
||||
"pin_code_changed_successfully": "تم تغير رمز PIN بنجاح",
|
||||
"pin_code_reset_successfully": "تم اعادة تعيين رمز PIN بنجاح",
|
||||
"pin_code_setup_successfully": "تم انشاء رمز PIN بنجاح",
|
||||
@@ -1595,9 +1523,6 @@
|
||||
"play_memories": "تشغيل الذكريات",
|
||||
"play_motion_photo": "تشغيل الصور المتحركة",
|
||||
"play_or_pause_video": "تشغيل الفيديو أو إيقافه مؤقتًا",
|
||||
"play_original_video": "تشغيل الفيديو الأصلي",
|
||||
"play_original_video_setting_description": "تفضيل تشغيل مقاطع الفيديو الأصلية بدلاً من مقاطع الفيديو المحولة. إذا لم يكن الملف الأصلي متوافقًا، فقد لا يتم تشغيله بشكل صحيح.",
|
||||
"play_transcoded_video": "تشغيل الفيديو المُعاد ترميزه",
|
||||
"please_auth_to_access": "الرجاء القيام بالمصادقة للوصول",
|
||||
"port": "المنفذ",
|
||||
"preferences_settings_subtitle": "ادارة تفضيلات التطبيق",
|
||||
@@ -1734,7 +1659,6 @@
|
||||
"reset_sqlite_confirmation": "هل أنت متأكد من رغبتك في إعادة ضبط قاعدة بيانات SQLite؟ ستحتاج إلى تسجيل الخروج ثم تسجيل الدخول مرة أخرى لإعادة مزامنة البيانات",
|
||||
"reset_sqlite_success": "تم إعادة تعيين قاعدة بيانات SQLite بنجاح",
|
||||
"reset_to_default": "إعادة التعيين إلى الافتراضي",
|
||||
"resolution": "دقة",
|
||||
"resolve_duplicates": "معالجة النسخ المكررة",
|
||||
"resolved_all_duplicates": "تم حل جميع التكرارات",
|
||||
"restore": "الاستعاده من سلة المهملات",
|
||||
@@ -1753,7 +1677,6 @@
|
||||
"running": "قيد التشغيل",
|
||||
"save": "حفظ",
|
||||
"save_to_gallery": "حفظ الى المعرض",
|
||||
"saved": "تم الحفظ",
|
||||
"saved_api_key": "تم حفظ مفتاح الـ API",
|
||||
"saved_profile": "تم حفظ الملف",
|
||||
"saved_settings": "تم حفظ الإعدادات",
|
||||
@@ -1770,9 +1693,6 @@
|
||||
"search_by_description_example": "يوم المشي لمسافات طويلة في سابا",
|
||||
"search_by_filename": "البحث بإسم الملف أو نوعه",
|
||||
"search_by_filename_example": "كـ IMG_1234.JPG أو PNG",
|
||||
"search_by_ocr": "البحث عن طريق التعرف البصري على الحروف",
|
||||
"search_by_ocr_example": "لاتيه",
|
||||
"search_camera_lens_model": "بحث نموذج العدسة...",
|
||||
"search_camera_make": "البحث حسب الشركة المصنعة للكاميرا...",
|
||||
"search_camera_model": "البحث حسب موديل الكاميرا...",
|
||||
"search_city": "البحث حسب المدينة...",
|
||||
@@ -1789,7 +1709,6 @@
|
||||
"search_filter_location_title": "اختر الموقع",
|
||||
"search_filter_media_type": "نوع الوسائط",
|
||||
"search_filter_media_type_title": "اختر نوع الوسائط",
|
||||
"search_filter_ocr": "البحث عن طريق التعرف البصري على الحروف",
|
||||
"search_filter_people_title": "اختر الاشخاص",
|
||||
"search_for": "البحث عن",
|
||||
"search_for_existing_person": "البحث عن شخص موجود",
|
||||
@@ -1851,10 +1770,7 @@
|
||||
"server_offline": "الخادم غير متصل",
|
||||
"server_online": "الخادم متصل",
|
||||
"server_privacy": "خصوصية الخادم",
|
||||
"server_restarting_description": "سيتم تحديث هذه الصفحة بعد لحضات.",
|
||||
"server_restarting_title": "يتم اعادة تشغيل الخادم",
|
||||
"server_stats": "إحصائيات الخادم",
|
||||
"server_update_available": "تحديث الخادم متاح",
|
||||
"server_version": "إصدار الخادم",
|
||||
"set": "تحديد",
|
||||
"set_as_album_cover": "تحديد كغلاف للألبوم",
|
||||
@@ -1883,8 +1799,6 @@
|
||||
"setting_notifications_subtitle": "اضبط تفضيلات الإخطار",
|
||||
"setting_notifications_total_progress_subtitle": "التقدم التحميل العام (تم القيام به/إجمالي الأصول)",
|
||||
"setting_notifications_total_progress_title": "إظهار النسخ الاحتياطي الخلفية التقدم المحرز",
|
||||
"setting_video_viewer_auto_play_subtitle": "بدء تشغيل مقاطع الفيديو تلقائيًا عند فتحها",
|
||||
"setting_video_viewer_auto_play_title": "تشغيل الفيديوهات تلقائيًا",
|
||||
"setting_video_viewer_looping_title": "تكرار مقطع فيديو تلقائيًا",
|
||||
"setting_video_viewer_original_video_subtitle": "عند بث فيديو من الخادم، شغّل النسخة الأصلية حتى مع توفر ترميز بديل. قد يؤدي ذلك إلى تقطيع اثناء العرض . تُشغّل الفيديوهات المتوفرة محليًا بجودة أصلية بغض النظر عن هذا الإعداد.",
|
||||
"setting_video_viewer_original_video_title": "اجبار عرض الفديو الاصلي",
|
||||
@@ -1976,7 +1890,6 @@
|
||||
"show_slideshow_transition": "إظهار انتقال عرض الشرائح",
|
||||
"show_supporter_badge": "شارة المؤيد",
|
||||
"show_supporter_badge_description": "إظهار شارة المؤيد",
|
||||
"show_text_recognition": "اضهار التعرف على النصوص",
|
||||
"show_text_search_menu": "عرض قائمة خيارات البحث في النص",
|
||||
"shuffle": "خلط",
|
||||
"sidebar": "الشريط الجانبي",
|
||||
@@ -2047,7 +1960,6 @@
|
||||
"tags": "العلامات",
|
||||
"tap_to_run_job": "انقر لتشغيل المهمة",
|
||||
"template": "النموذج",
|
||||
"text_recognition": "التعرف على النصوص",
|
||||
"theme": "مظهر",
|
||||
"theme_selection": "اختيار السمة",
|
||||
"theme_selection_description": "قم بتعيين السمة تلقائيًا على اللون الفاتح أو الداكن بناءً على تفضيلات نظام المتصفح الخاص بك",
|
||||
@@ -2066,9 +1978,7 @@
|
||||
"theme_setting_three_stage_loading_title": "تمكين تحميل ثلاث مراحل",
|
||||
"they_will_be_merged_together": "سيتم دمجهم معًا",
|
||||
"third_party_resources": "موارد الطرف الثالث",
|
||||
"time": "وقت",
|
||||
"time_based_memories": "ذكريات استنادًا للوقت",
|
||||
"time_based_memories_duration": "عدد الثواني لاظهار كل صورة.",
|
||||
"timeline": "الخط الزمني",
|
||||
"timezone": "المنطقة الزمنية",
|
||||
"to_archive": "أرشفة",
|
||||
@@ -2080,7 +1990,6 @@
|
||||
"to_select": "للتحديد",
|
||||
"to_trash": "حذف",
|
||||
"toggle_settings": "الإعدادات",
|
||||
"toggle_theme_description": "تبديل السمة",
|
||||
"total": "الإجمالي",
|
||||
"total_usage": "الاستخدام الإجمالي",
|
||||
"trash": "المهملات",
|
||||
@@ -2101,7 +2010,6 @@
|
||||
"troubleshoot": "استكشاف المشاكل",
|
||||
"type": "النوع",
|
||||
"unable_to_change_pin_code": "تفيير رمز PIN غير ممكن",
|
||||
"unable_to_check_version": "تعذر التحقق من إصدار التطبيق أو الخادم",
|
||||
"unable_to_setup_pin_code": "انشاء رمز PIN غير ممكن",
|
||||
"unarchive": "أخرج من الأرشيف",
|
||||
"unarchive_action_prompt": "{count} ازيل من الارشيف",
|
||||
@@ -2210,13 +2118,12 @@
|
||||
"welcome": "مرحباً",
|
||||
"welcome_to_immich": "مرحباً بك في Immich",
|
||||
"wifi_name": "اسم شبكة Wi-Fi",
|
||||
"workflow": "سير العمل",
|
||||
"wrong_pin_code": "رمز التعريف الشخصي خاطئ",
|
||||
"wrong_pin_code": "رمز PIN خاطئ",
|
||||
"year": "سنة",
|
||||
"years_ago": "{years, plural, one {# سنة} other {# سنوات}} مضت",
|
||||
"years_ago": "منذ {years, plural, one {# سنة} other {# سنوات}}",
|
||||
"yes": "نعم",
|
||||
"you_dont_have_any_shared_links": "ليس لديك أي روابط مشتركة",
|
||||
"your_wifi_name": "اسم شبكة الاتصال اللاسلكي الخاص بك",
|
||||
"your_wifi_name": "اسم شبكة Wi-Fi الخاص بك",
|
||||
"zoom_image": "تكبير الصورة",
|
||||
"zoom_to_bounds": "تكبير حتى حدود المنطقة"
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"action_common_update": "Yenilə",
|
||||
"actions": "Əməliyyatlar",
|
||||
"active": "Aktiv",
|
||||
"active_count": "Aktiv: {count}",
|
||||
"activity": "Fəaliyyət",
|
||||
"activity_changed": "Fəaliyyət {enabled, select, true {aktivdir} other {aktiv deyil}}",
|
||||
"add": "Əlavə et",
|
||||
@@ -18,6 +17,7 @@
|
||||
"add_birthday": "Doğum günü əlavə et",
|
||||
"add_endpoint": "Son nöqtə əlavə et",
|
||||
"add_exclusion_pattern": "Çıxarma nümunəsi əlavə et",
|
||||
"add_import_path": "İdxal yolu əlavə et",
|
||||
"add_location": "Məkan əlavə et",
|
||||
"add_more_users": "Daha çox istifadəçi əlavə et",
|
||||
"add_partner": "Partnyor əlavə et",
|
||||
@@ -32,9 +32,7 @@
|
||||
"add_to_album_toggle": "{album} üçün seçimi dəyişin",
|
||||
"add_to_albums": "Albomlara əlavə et",
|
||||
"add_to_albums_count": "({count}) albomlarına əlavə et",
|
||||
"add_to_bottom_bar": "Əlavə et",
|
||||
"add_to_shared_album": "Paylaşılan alboma əlavə et",
|
||||
"add_upload_to_stack": "Yeni yüklənmə əlavə et",
|
||||
"add_url": "URL əlavə et",
|
||||
"added_to_archive": "Arxivə əlavə edildi",
|
||||
"added_to_favorites": "Sevimlilələrə əlavə edildi",
|
||||
@@ -66,6 +64,7 @@
|
||||
"confirm_user_password_reset": "{user} adlı istifadəçinin şifrəsini sıfırlamaq istədiyinizdən əminmisiniz?",
|
||||
"disable_login": "Giriş etməni söndür",
|
||||
"duplicate_detection_job_description": "Bənzər şəkilləri tapmaq üçün maşın öyrənməsini işə salın. Bu prosses Smart Search funksiyasına əsaslanır",
|
||||
"external_library_management": "Xarici kitabxana idarəetməsi",
|
||||
"face_detection": "Üz tanıma",
|
||||
"force_delete_user_warning": "XƏBƏRDARLIQ: Bu əməliyyat istifadəçi və bütün məlumatları siləcəkdir. Bu prossesi və silinən faylları geri qaytarmaq olmaz.",
|
||||
"image_format_description": "WebP, JPEG faylına görə daha kiçik həcmə sahibdir, lakin onu kodlaşdırmaq daha çox vaxt alır.",
|
||||
@@ -81,10 +80,12 @@
|
||||
"job_not_concurrency_safe": "Bu iş eyni vaxtda icra üçün təhlükəsiz deyil.",
|
||||
"job_settings": "Tapşırıq parametrləri",
|
||||
"job_settings_description": "Parallel şəkildə fəaliyyət göstərən tapşırıqları idarə et",
|
||||
"job_status": "Tapşırıq statusu",
|
||||
"jobs_delayed": "{jobCount, plural, other {# gecikməli}}",
|
||||
"jobs_failed": "{jobCount, plural, other {# uğursuz}}",
|
||||
"library_created": "{library} kitabxanası yaradıldı",
|
||||
"library_deleted": "Kitabxana silindi",
|
||||
"library_import_path_description": "İdxal olunacaq qovluöu seçin. Bu qovluq, alt qovluqlar daxil olmaqla şəkil və videolar üçün skan ediləcəkdir.",
|
||||
"library_scanning": "Periodik skan",
|
||||
"library_scanning_description": "Periodik kitabxana skanını confiqurasiya et",
|
||||
"library_scanning_enable_description": "Periodik kitabxana skanını aktivləşdir",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"add_birthday": "Дадаць дзень нараджэння",
|
||||
"add_endpoint": "Дадаць кропку доступу",
|
||||
"add_exclusion_pattern": "Дадаць шаблон выключэння",
|
||||
"add_import_path": "Дадаць шлях імпарту",
|
||||
"add_location": "Дадайце месца",
|
||||
"add_more_users": "Дадаць больш карыстальнікаў",
|
||||
"add_partner": "Дадаць партнёра",
|
||||
@@ -72,6 +73,7 @@
|
||||
"disable_login": "Адключыць уваход",
|
||||
"duplicate_detection_job_description": "Запусціць машыннае навучанне на актывах для выяўлення падобных выяў. Залежыць ад Smart Search",
|
||||
"exclusion_pattern_description": "Шаблоны выключэння дазваляюць ігнараваць файлы і папкі пры сканаванні вашай бібліятэкі. Гэта карысна, калі ў вас ёсць папкі, якія змяшчаюць файлы, якія вы не хочаце імпартаваць, напрыклад, файлы RAW.",
|
||||
"external_library_management": "Кіраванне знешняй бібліятэкай",
|
||||
"face_detection": "Выяўленне твараў",
|
||||
"face_detection_description": "Выяўляць твары на фотаздымках і відэа з дапамогай машыннага навучання. Для відэа ўлічваецца толькі мініяцюра. \"Абнавіць\" (пера)апрацоўвае ўсе медыя. \"Скінуць\" дадаткова ачышчае ўсе бягучыя даныя пра твары. \"Адсутнічае\" ставіць у чаргу медыя, якія яшчэ не былі апрацаваныя. Выяўленыя твары будуць пастаўлены ў чаргу для распазнавання асоб пасля завяршэння выяўлення твараў, з групаваннем іх па існуючых або новых людзях.",
|
||||
"facial_recognition_job_description": "Групаваць выяўленыя твары па асобах. Гэты этап выконваецца пасля завяршэння выяўлення твараў. \"Скінуць\" (паўторна) перагрупоўвае ўсе твары. \"Адсутнічае\" ставіць у чаргу твары, якія яшчэ не прыпісаныя да якой-небудзь асобы.",
|
||||
@@ -102,6 +104,7 @@
|
||||
"job_not_concurrency_safe": "Гэта заданне небяспечнае для канкурэнтнага(адначасовага, паралельнага) выканання.",
|
||||
"job_settings": "Налады заданняў",
|
||||
"job_settings_description": "Кіраваць наладамі адначасовага (паралельнага) выканання задання",
|
||||
"job_status": "Становішча задання",
|
||||
"jobs_delayed": "{jobCount, plural, other {# адкладзена}}",
|
||||
"jobs_failed": "{jobCount, plural, other {# не выканалася}}",
|
||||
"library_created": "Створана бібліятэка: {library}",
|
||||
@@ -169,6 +172,7 @@
|
||||
"user_restore_description": "Уліковы запіс карыстальніка <b>{user}</b> будзе адноўлены.",
|
||||
"user_settings": "Налады карыстальніка",
|
||||
"user_settings_description": "Кіраванне наладамі карыстальніка",
|
||||
"user_successfully_removed": "Карыстальнік {email} быў паспяхова выдалены.",
|
||||
"version_check_enabled_description": "Уключыць праверку версіі",
|
||||
"version_check_implications": "Функцыя праверкі версіі перыядычна звяртаецца да github.com",
|
||||
"version_check_settings": "Праверка версіі",
|
||||
@@ -314,6 +318,8 @@
|
||||
"edit_description": "Рэдагаваць апісанне",
|
||||
"edit_description_prompt": "Выберыце новае апісанне:",
|
||||
"edit_faces": "Рэдагаваць твары",
|
||||
"edit_import_path": "Рэдагаваць шлях імпарту",
|
||||
"edit_import_paths": "Рэдагаваць шляхі імпарту",
|
||||
"edit_key": "Рэдагаваць ключ",
|
||||
"edit_link": "Рэдагаваць спасылку",
|
||||
"edit_location": "Рэдагаваць месцазнаходжанне",
|
||||
@@ -392,8 +398,6 @@
|
||||
"partner_list_user_photos": "Фота карыстальніка {user}",
|
||||
"pause": "Прыпыніць",
|
||||
"people": "Людзі",
|
||||
"permanent_deletion_warning": "Папярэджанне аб канчатковым выдаленні",
|
||||
"permanent_deletion_warning_setting_description": "Паказаць папярэджанне пры канчатковым выдаленні рэсурсаў",
|
||||
"permission_onboarding_back": "Назад",
|
||||
"permission_onboarding_continue_anyway": "Усё адно працягнуць",
|
||||
"photos": "Фота",
|
||||
|
||||
110
i18n/bg.json
110
i18n/bg.json
@@ -7,7 +7,6 @@
|
||||
"action_common_update": "Обнови",
|
||||
"actions": "Действия",
|
||||
"active": "Активни",
|
||||
"active_count": "Активни: {count}",
|
||||
"activity": "Дейност",
|
||||
"activity_changed": "Дейността е {enabled, select, true {включена} other {изключена}}",
|
||||
"add": "Добави",
|
||||
@@ -18,6 +17,7 @@
|
||||
"add_birthday": "Добави дата на раждане",
|
||||
"add_endpoint": "Добави крайна точка",
|
||||
"add_exclusion_pattern": "Добави модел за изключване",
|
||||
"add_import_path": "Добави път за импортиране",
|
||||
"add_location": "Дoбави местоположение",
|
||||
"add_more_users": "Добави още потребители",
|
||||
"add_partner": "Добави партньор",
|
||||
@@ -32,7 +32,6 @@
|
||||
"add_to_album_toggle": "Сменете избора за {album}",
|
||||
"add_to_albums": "Добавяне в албуми",
|
||||
"add_to_albums_count": "Добавяне в албуми ({count})",
|
||||
"add_to_bottom_bar": "Добави към",
|
||||
"add_to_shared_album": "Добави към споделен албум",
|
||||
"add_upload_to_stack": "Добави качените в група",
|
||||
"add_url": "Добави URL",
|
||||
@@ -68,7 +67,6 @@
|
||||
"confirm_reprocess_all_faces": "Сигурни ли сте, че искате да се обработят лицата отново? Това ще изчисти всички именувани хора.",
|
||||
"confirm_user_password_reset": "Сигурни ли сте, че искате да нулирате паролата на {user}?",
|
||||
"confirm_user_pin_code_reset": "Наистина ли искате да смените PIN кода на потребителя {user}?",
|
||||
"copy_config_to_clipboard_description": "Копирай текущата системна конфигурация като JSON обект в клипборда",
|
||||
"create_job": "Създайте задача",
|
||||
"cron_expression": "Cron израз",
|
||||
"cron_expression_description": "Настрой интервала на сканиране използвайки cron формата. За повече информация <link>Crontab Guru</link>",
|
||||
@@ -76,8 +74,7 @@
|
||||
"disable_login": "Изключете вписването",
|
||||
"duplicate_detection_job_description": "Стартиране машинно обучение върху елементи, за откриване на подобни изображения. Разчита на Интелигентно Търсене",
|
||||
"exclusion_pattern_description": "Модели за изключване позволяват да игнорирате файлове и папки, когато сканирате вашата библиотека. Това е потребно, ако имате папки, които съдържат файлове, които не искате да импортирате. Примерно - RAW файлове.",
|
||||
"export_config_as_json_description": "Запази текущата системна конфигурация като JSON файл",
|
||||
"external_libraries_page_description": "Администриране на външната страница на библиотеката",
|
||||
"external_library_management": "Управление на външните библиотеки",
|
||||
"face_detection": "Откриване на лица",
|
||||
"face_detection_description": "Да се разпознават лица в елементи чрез машинно обучение. За видеата се използва само миниатюрата. \"Всички\" обработва отново всички елементи. \"Липсващи\" зарежда за обработка елементи, които на се обработени все още. Откритите лица ще бъдат подредени в опашка за разпознаване на лица след завършване на функцията за откриване на лица, като се групират в съществуващи или нови хора.",
|
||||
"facial_recognition_job_description": "Групирайте откритите лица в хора. Тази стъпка се изпълнява след завършване на разпознаването на лица. „Нулиране“ прегрупира всички лица. „Липсващи“ поставя в опашка лицата, които нямат назначен човек.",
|
||||
@@ -105,28 +102,23 @@
|
||||
"image_thumbnail_description": "Малка миниатюра с премахнати метаданни, използвана при преглед на групи снимки, като основния екран",
|
||||
"image_thumbnail_quality_description": "Качество на миниатюрата от 1 до 100. По-високата стойност е по-добра, но води до по-големи файлове и може да намали бързодействието на приложението.",
|
||||
"image_thumbnail_title": "Настройки на миниатюрите",
|
||||
"import_config_from_json_description": "Импорт на системна конфигурация чрез качване на JSON файл",
|
||||
"job_concurrency": "Паралелност на {job}",
|
||||
"job_created": "Задачата е създадена",
|
||||
"job_not_concurrency_safe": "Тази задача не е безопасна за паралелно изпълнение.",
|
||||
"job_settings": "Настройки за задачите",
|
||||
"job_settings_description": "Управление на паралелността на задачите",
|
||||
"job_status": "Статус на задачата",
|
||||
"jobs_delayed": "{jobCount, plural, other {# забавени}}",
|
||||
"jobs_failed": "{jobCount, plural, other {# неуспешни}}",
|
||||
"jobs_over_time": "Работа във времето",
|
||||
"library_created": "Създадена библиотека: {library}",
|
||||
"library_deleted": "Библиотека е изтрита",
|
||||
"library_details": "Подробности за библиотеката",
|
||||
"library_folder_description": "Изберете папка за импортиране. Папката и подпапките в нея ще бъдат сканирани за изображения и видеа.",
|
||||
"library_remove_exclusion_pattern_prompt": "Сигурни ли сте, че искате да премахнете този шаблон за игнориране?",
|
||||
"library_remove_folder_prompt": "Сигурни ли сте, че искате да премахнете тази папка за импортиране?",
|
||||
"library_import_path_description": "Посочете папка за импортиране. Тази папка, включително подпапките, ще бъдат сканирани за изображения и видеоклипове.",
|
||||
"library_scanning": "Периодично сканиране",
|
||||
"library_scanning_description": "Конфигурирай периодично сканиране на библиотеката",
|
||||
"library_scanning_enable_description": "Включване на периодичното сканиране на библиотеката",
|
||||
"library_settings": "Външна библиотека",
|
||||
"library_settings_description": "Управление на настройките за външна библиотека",
|
||||
"library_tasks_description": "Сканирайте външни библиотеки за нови и/или променени елементи",
|
||||
"library_updated": "Обновена библиотека",
|
||||
"library_watching_enable_description": "Наблюдаване за промяна на файловете във външната библиотека",
|
||||
"library_watching_settings": "Наблюдаване на библиотеката [ЕКСПЕРИМЕНТАЛНО]",
|
||||
"library_watching_settings_description": "Автоматично наблюдавай за променени файлове",
|
||||
@@ -159,21 +151,9 @@
|
||||
"machine_learning_max_recognition_distance": "Максимално разстояние за разпознаване",
|
||||
"machine_learning_max_recognition_distance_description": "Максимално разстояние между две лица, за да се считат за едно и също лице, в диапазона 0-2. Намаляването му може да предотврати определянето на две лица като едно и също лице, а увеличаването му може да предотврати определянето на едно и също лице като две различни лица. Имайте предвид, че е по-лесно да се слеят две лица, отколкото да се раздели едно лице на две, така че по възможност изберете по-ниска стойност.",
|
||||
"machine_learning_min_detection_score": "Минимална оценка за откриване",
|
||||
"machine_learning_min_detection_score_description": "Минимална оценка на доверие, за да бъде считано лице като открито - от 0 до 1. По-ниските стойности ще открият повече лица, но може да доведат до фалшиви положителни резултати.",
|
||||
"machine_learning_min_detection_score_description": "Минимална оценка на доверието, за да бъде считано лице като открито - от 0 до 1. По-ниските стойности ще открият повече лица, но може да доведат до фалшиви положителни резултати.",
|
||||
"machine_learning_min_recognized_faces": "Минимум разпознати лица",
|
||||
"machine_learning_min_recognized_faces_description": "Минималният брой разпознати лица, необходими за създаването на лице. Увеличаването му прави разпознаването на лица по-прецизно за сметка на увеличаването на вероятността дадено лице да не бъде причислено към лице.",
|
||||
"machine_learning_ocr": "Оптично разпознаване на текст",
|
||||
"machine_learning_ocr_description": "Използвайте машинно обучение за разпознаване на текст в изображенията",
|
||||
"machine_learning_ocr_enabled": "Включи разпознаване на текст",
|
||||
"machine_learning_ocr_enabled_description": "Ако е забранено, няма да се прави разпознаване на текст в изображенията.",
|
||||
"machine_learning_ocr_max_resolution": "Максимална резолюция",
|
||||
"machine_learning_ocr_max_resolution_description": "Изображения с резолюция над зададената ще бъдат преоразмерени при запазване на пропорцията. Голяма стойност позволява по-прецизно разпознаване, но обработката използва повече време и повече памет.",
|
||||
"machine_learning_ocr_min_detection_score": "Минимална оценка за откриванe",
|
||||
"machine_learning_ocr_min_detection_score_description": "Минималната оценка на доверие за откриване на текст може да бъде между 0 и 1. По-ниска стойност ще открива повече текст, но може да доведе до грешни резултати.",
|
||||
"machine_learning_ocr_min_recognition_score": "Минимална оценкa за откриване",
|
||||
"machine_learning_ocr_min_score_recognition_description": "Минимална оценка на доверие, за да бъде считан текст като открит - от 0 до 1. По-ниските стойности ще открият повече текст, но може да доведат до фалшиви положителни резултати.",
|
||||
"machine_learning_ocr_model": "Модел за разпознаване на текст",
|
||||
"machine_learning_ocr_model_description": "Сървърните модели са по-точни от мобилните модели, но изискват повече време и използват повече памет.",
|
||||
"machine_learning_settings": "Настройки на машинното обучение",
|
||||
"machine_learning_settings_description": "Управление на функциите и настройките за машинно обучение",
|
||||
"machine_learning_smart_search": "Интелигентно Търсене",
|
||||
@@ -181,12 +161,7 @@
|
||||
"machine_learning_smart_search_enabled": "Включване на Интелигентно Търсене",
|
||||
"machine_learning_smart_search_enabled_description": "Ако е деактивирано, изображенията няма да бъдат кодирани за Интелигентно Търсене.",
|
||||
"machine_learning_url_description": "URL на сървъра за машинно обучение. Ако са предоставени повече от един URL, всеки сървър ще бъде опитан един по един, докато един отговори успешно, в реда от първия до последния. Сървъри, които не отговорят, ще бъдат временно игнорирани, докато не се върнат онлайн.",
|
||||
"maintenance_settings": "Обслужване",
|
||||
"maintenance_settings_description": "Преквлючване на сървъра Immich в режим на обслужване.",
|
||||
"maintenance_start": "Започни режим на обслужване",
|
||||
"maintenance_start_error": "Неуспешно преминаване в режим на обслужване.",
|
||||
"manage_concurrency": "Управление на паралелност",
|
||||
"manage_concurrency_description": "Отидете на страницата със задачи, за да управлявате едновременността им",
|
||||
"manage_log_settings": "Управление на настройките на записване",
|
||||
"map_dark_style": "Тъмен стил",
|
||||
"map_enable_description": "Активиране на картата",
|
||||
@@ -270,20 +245,15 @@
|
||||
"oauth_storage_quota_default_description": "Квота в GiB, която да се използва, когато не е посочено друго.",
|
||||
"oauth_timeout": "Време на изчакване при заявка",
|
||||
"oauth_timeout_description": "Време за изчакване на отговор на заявка, в милисекунди",
|
||||
"ocr_job_description": "Използване на машинно обучение за разпознаване на текст в изображенията",
|
||||
"password_enable_description": "Влизане с имейл и парола",
|
||||
"password_settings": "Вписване с парола",
|
||||
"password_settings_description": "Управление на настройките за влизане с парола",
|
||||
"paths_validated_successfully": "Всички пътища са валидирани успешно",
|
||||
"person_cleanup_job": "Почистване на лица",
|
||||
"queue_details": "Детайли по Опашката",
|
||||
"queues": "Опашки за задачи",
|
||||
"queues_page_description": "Страница с опашки за администраторски задачи",
|
||||
"quota_size_gib": "Размер на квотата (GiB)",
|
||||
"refreshing_all_libraries": "Опресняване на всички библиотеки",
|
||||
"registration": "Администраторска регистрация",
|
||||
"registration_description": "Тъй като сте първият потребител в системата, ще бъдете назначен като администратор и ще отговаряте за административните задачи, а допълнителните потребители ще бъдат създадени от вас.",
|
||||
"remove_failed_jobs": "Премахване на неуспешни задачи",
|
||||
"require_password_change_on_login": "Изискване за промяна паролата при първо влизане",
|
||||
"reset_settings_to_default": "Възстановяване на настройките по подразбиране",
|
||||
"reset_settings_to_recent_saved": "Възстановяване на настройките до последните запазени настройки",
|
||||
@@ -296,10 +266,8 @@
|
||||
"server_public_users_description": "Всички потребители (име и имейл) са изброени при добавяне на потребител в споделени албуми. Когато е деактивирано, списъкът с потребители ще бъде достъпен само за администраторите.",
|
||||
"server_settings": "Настройки на сървъра",
|
||||
"server_settings_description": "Управление на настройките на сървъра",
|
||||
"server_stats_page_description": "Администраторска страница със статистика за сървъра",
|
||||
"server_welcome_message": "Поздравително съобщение",
|
||||
"server_welcome_message_description": "Съобщение, показващо се на страницата за вход.",
|
||||
"settings_page_description": "Страница с настройки за администратора",
|
||||
"sidecar_job": "Метаданни от свързани (sidecar) файлове",
|
||||
"sidecar_job_description": "Откриване или синхронизиране на странични (sidecar) метаданни от файловата система",
|
||||
"slideshow_duration_description": "Брой секунди за показване на всяко изображение",
|
||||
@@ -418,8 +386,7 @@
|
||||
"user_restore_scheduled_removal": "Възстановяване на потребител – с насрочено премахване на {date, date, long}",
|
||||
"user_settings": "Настройки на потребителя",
|
||||
"user_settings_description": "Управление на потребителските настройки",
|
||||
"user_successfully_removed": "Потребител {email} е успешно премахнат.",
|
||||
"users_page_description": "Страница за администриране на потребители",
|
||||
"user_successfully_removed": "Потребителят {email} е успешно премахнат.",
|
||||
"version_check_enabled_description": "Активирай проверка на версията",
|
||||
"version_check_implications": "Функцията за проверка на версията разчита на периодична комуникация с github.com",
|
||||
"version_check_settings": "Проверка на версията",
|
||||
@@ -450,7 +417,6 @@
|
||||
"age_months": "Възраст {months, plural, one {# месец} other {# месеци}}",
|
||||
"age_year_months": "Възраст 1 година, {months, plural, one {# месец} other {# месеци}}",
|
||||
"age_years": "{years, plural, other {Година #}}",
|
||||
"album": "Албум",
|
||||
"album_added": "Албумът е добавен",
|
||||
"album_added_notification_setting_description": "Получавайте известие по имейл, когато бъдете добавени към споделен албум",
|
||||
"album_cover_updated": "Обложката на албума е актуализирана",
|
||||
@@ -496,7 +462,6 @@
|
||||
"allow_edits": "Позволяване на редакции",
|
||||
"allow_public_user_to_download": "Позволете на публичен потребител да може да изтегля",
|
||||
"allow_public_user_to_upload": "Позволете на публичния потребител да може да качва",
|
||||
"allowed": "Разрешено",
|
||||
"alt_text_qr_code": "Изображение на QR код",
|
||||
"anti_clockwise": "Обратно на часовниковата стрелка",
|
||||
"api_key": "API ключ",
|
||||
@@ -704,8 +669,6 @@
|
||||
"change_password_description": "Това е или първият път, когато влизате в системата, или е направена заявка за промяна на паролата ви. Моля, въведете новата парола по-долу.",
|
||||
"change_password_form_confirm_password": "Потвърди паролата",
|
||||
"change_password_form_description": "Здравейте {name},\n\nТова или е първото ви вписване в системата или има подадена заявка за смяна на паролата. Моля, въведете нова парола в полето по-долу.",
|
||||
"change_password_form_log_out": "Излизане от профила на всички други устройства",
|
||||
"change_password_form_log_out_description": "Препоръчваме да се излезе от профила на всички други устройства",
|
||||
"change_password_form_new_password": "Нова парола",
|
||||
"change_password_form_password_mismatch": "Паролите не съвпадат",
|
||||
"change_password_form_reenter_new_password": "Повтори новата парола",
|
||||
@@ -740,7 +703,6 @@
|
||||
"collapse_all": "Свиване на всичко",
|
||||
"color": "Цвят",
|
||||
"color_theme": "Цветова тема",
|
||||
"command": "Команда",
|
||||
"comment_deleted": "Коментарът е изтрит",
|
||||
"comment_options": "Опции за коментар",
|
||||
"comments_and_likes": "Коментари и харесвания",
|
||||
@@ -814,7 +776,6 @@
|
||||
"daily_title_text_date_year": "E, dd MMM yyyy",
|
||||
"dark": "Тъмен",
|
||||
"dark_theme": "Тъмна тема",
|
||||
"date": "Дата",
|
||||
"date_after": "Дата след",
|
||||
"date_and_time": "Дата и час",
|
||||
"date_before": "Дата преди",
|
||||
@@ -917,6 +878,8 @@
|
||||
"edit_description_prompt": "Моля, избери ново описание:",
|
||||
"edit_exclusion_pattern": "Редактиране на шаблон за изключване",
|
||||
"edit_faces": "Редактиране на лица",
|
||||
"edit_import_path": "Редактиране на пътя за импортиране",
|
||||
"edit_import_paths": "Редактиране на пътища за импортиране",
|
||||
"edit_key": "Редактиране на ключ",
|
||||
"edit_link": "Редактиране на линк",
|
||||
"edit_location": "Редактиране на местоположението",
|
||||
@@ -988,8 +951,8 @@
|
||||
"failed_to_stack_assets": "Неуспешно подреждане на обекти",
|
||||
"failed_to_unstack_assets": "Неуспешно премахване на подредбата на обекти",
|
||||
"failed_to_update_notification_status": "Неуспешно обновяване на състоянието на известията",
|
||||
"import_path_already_exists": "Този път за импортиране вече съществува.",
|
||||
"incorrect_email_or_password": "Неправилен имейл или парола",
|
||||
"library_folder_already_exists": "Тази папка вече съществува.",
|
||||
"paths_validation_failed": "{paths, plural, one {# път} other {# пътища}} не преминаха валидация",
|
||||
"profile_picture_transparent_pixels": "Профилните снимки не могат да имат прозрачни пиксели. Моля, увеличете и/или преместете изображението.",
|
||||
"quota_higher_than_disk_size": "Зададена е квота, по-голяма от размера на диска",
|
||||
@@ -998,6 +961,7 @@
|
||||
"unable_to_add_assets_to_shared_link": "Неуспешно добавяне на обекти в споделен линк",
|
||||
"unable_to_add_comment": "Неуспешно добавяне на коментар",
|
||||
"unable_to_add_exclusion_pattern": "Неуспешно добавяне на шаблон за изключение",
|
||||
"unable_to_add_import_path": "Неуспешно добавяне на път за импортиране",
|
||||
"unable_to_add_partners": "Неуспешно добавяне на партньори",
|
||||
"unable_to_add_remove_archive": "Неуспешно {archived, select, true {премахване на обект от} other {добавяне на обект в}} архива",
|
||||
"unable_to_add_remove_favorites": "Неуспешно {favorite, select, true {добавяне на обект в} other {премахване на обект от}} любими",
|
||||
@@ -1020,10 +984,12 @@
|
||||
"unable_to_delete_asset": "Не може да изтрие файла",
|
||||
"unable_to_delete_assets": "Грешка при изтриване на файлове",
|
||||
"unable_to_delete_exclusion_pattern": "Не може да изтрие шаблон за изключване",
|
||||
"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_empty_trash": "Неуспешно изпразване на кошчето",
|
||||
"unable_to_enter_fullscreen": "Не може да се отвори в цял екран",
|
||||
"unable_to_exit_fullscreen": "Не може да излезе от цял екран",
|
||||
@@ -1074,7 +1040,6 @@
|
||||
"unable_to_update_user": "Неуспешно обновяване на потребителя",
|
||||
"unable_to_upload_file": "Неуспешно качване на файл"
|
||||
},
|
||||
"exclusion_pattern": "Шаблон за изключение",
|
||||
"exif": "Exif",
|
||||
"exif_bottom_sheet_description": "Добави Описание...",
|
||||
"exif_bottom_sheet_description_error": "Неуспешно обновяване на описание",
|
||||
@@ -1105,7 +1070,6 @@
|
||||
"external_network_sheet_info": "Когато няма връзка с предпочитаната Wi-Fi мрежа, приложението ще опитва да се свърже със сървъра чрез първия достъпен URL адрес, започвайки отгоре надолу",
|
||||
"face_unassigned": "Незададено",
|
||||
"failed": "Неуспешно",
|
||||
"failed_count": "Неуспешни: {count}",
|
||||
"failed_to_authenticate": "Неуспешна автентикация",
|
||||
"failed_to_load_assets": "Неуспешно зареждане на елементи",
|
||||
"failed_to_load_folder": "Неуспешно зареждане на папка",
|
||||
@@ -1120,7 +1084,6 @@
|
||||
"features_setting_description": "Управление на функциите на приложението",
|
||||
"file_name": "Име на файла",
|
||||
"file_name_or_extension": "Име на файл или разширение",
|
||||
"file_size": "Размер на файла",
|
||||
"filename": "Име на файл",
|
||||
"filetype": "Тип на файл",
|
||||
"filter": "Филтър",
|
||||
@@ -1135,7 +1098,6 @@
|
||||
"folders_feature_description": "Преглеждане на папката за снимките и видеоклиповете в файловата система",
|
||||
"forgot_pin_code_question": "Забравили сте своя ПИН код?",
|
||||
"forward": "Напред",
|
||||
"full_path": "Пълен път: {path}",
|
||||
"gcast_enabled": "Google Cast",
|
||||
"gcast_enabled_description": "За да работи тази функция зарежда външни ресурси от Google.",
|
||||
"general": "Общи",
|
||||
@@ -1172,7 +1134,6 @@
|
||||
"hide_named_person": "Скрий човек {name}",
|
||||
"hide_password": "Скрий парола",
|
||||
"hide_person": "Скрий човек",
|
||||
"hide_text_recognition": "Скрий разпознатия текст",
|
||||
"hide_unnamed_people": "Скрий неназовани хора",
|
||||
"home_page_add_to_album_conflicts": "Добавени са {added} обекта в албума {album}. Вече има {failed} обекта.",
|
||||
"home_page_add_to_album_err_local": "Все още не е възможно да се добавят локални обекти в албумите, пропускане",
|
||||
@@ -1218,8 +1179,6 @@
|
||||
"import_path": "Път за импортиране",
|
||||
"in_albums": "В {count, plural, one {# албум} other {# албума}}",
|
||||
"in_archive": "В архив",
|
||||
"in_year": "{year} г.",
|
||||
"in_year_selector": "През",
|
||||
"include_archived": "Включване на архивирани",
|
||||
"include_shared_albums": "Включване на споделени албуми",
|
||||
"include_shared_partner_assets": "Включване на споделените с партньор елементи",
|
||||
@@ -1256,7 +1215,6 @@
|
||||
"language_setting_description": "Изберете предпочитан език",
|
||||
"large_files": "Големи файлове",
|
||||
"last": "Последен",
|
||||
"last_months": "{count, plural, one {Последния месец} other {Последните # месеца}}",
|
||||
"last_seen": "Последно видяно",
|
||||
"latest_version": "Последна версия",
|
||||
"latitude": "Ширина",
|
||||
@@ -1266,8 +1224,6 @@
|
||||
"let_others_respond": "Позволете на другите да отговорят",
|
||||
"level": "Ниво",
|
||||
"library": "Библиотека",
|
||||
"library_add_folder": "Добави папка",
|
||||
"library_edit_folder": "Редактиране на папка",
|
||||
"library_options": "Опции на библиотеката",
|
||||
"library_page_device_albums": "Албуми в устройството",
|
||||
"library_page_new_album": "Нов албум",
|
||||
@@ -1291,7 +1247,6 @@
|
||||
"local_media_summary": "Обобщение на локалните файлове",
|
||||
"local_network": "Локална мрежа",
|
||||
"local_network_sheet_info": "Приложението ще се свърже със сървъра на този URL, когато устройството е свързано към зададената Wi-Fi мрежа",
|
||||
"location": "Място",
|
||||
"location_permission": "Разрешение за местоположение",
|
||||
"location_permission_content": "За да работи функцията автоматично превключване, Immich се нуждае от разрешение за точно местоположение, за да може да чете името на текущата Wi-Fi мрежа",
|
||||
"location_picker_choose_on_map": "Избери на карта",
|
||||
@@ -1339,17 +1294,8 @@
|
||||
"loop_videos_description": "Позволи автоматично повтаряне на видеото в изгледа на детайлите.",
|
||||
"main_branch_warning": "Използвате версия за разработчици, силно препоръчваме да използвате официална версия!",
|
||||
"main_menu": "Главно меню",
|
||||
"maintenance_description": "Сървъра Immich е поставен в <link>режим на обслужване</link>.",
|
||||
"maintenance_end": "Край на режима на обслужване",
|
||||
"maintenance_end_error": "Неуспешно завършване на режима на обслужване.",
|
||||
"maintenance_logged_in_as": "Текущия потребител е {user}",
|
||||
"maintenance_title": "Временно недостъпен",
|
||||
"make": "Марка",
|
||||
"manage_geolocation": "Управление на местоположенията",
|
||||
"manage_media_access_rationale": "Това разрешение е необходимо за правилно преместване на обекти в кошчето и за възстановяване от там.",
|
||||
"manage_media_access_settings": "Отвори Настройки",
|
||||
"manage_media_access_subtitle": "Разрешете приложението Immich да управлява и мести медийни файлове.",
|
||||
"manage_media_access_title": "Управление на медийни файлове",
|
||||
"manage_shared_links": "Управление на споделени връзки",
|
||||
"manage_sharing_with_partners": "Управление на споделянето с партньори",
|
||||
"manage_the_app_settings": "Управление на настройките на приложението",
|
||||
@@ -1413,7 +1359,6 @@
|
||||
"more": "Още",
|
||||
"move": "Премести",
|
||||
"move_off_locked_folder": "Извади от заключената папка",
|
||||
"move_to": "Премести към",
|
||||
"move_to_lock_folder_action_prompt": "{count} са добавени в заключената папка",
|
||||
"move_to_locked_folder": "Премести в заключена папка",
|
||||
"move_to_locked_folder_confirmation": "Тези снимки и видеа ще бъдат изтрити от всички албуми и ще са достъпни само в заключената папка",
|
||||
@@ -1443,7 +1388,6 @@
|
||||
"new_pin_code": "Нов PIN код",
|
||||
"new_pin_code_subtitle": "Това е първи достъп до заключена папка. Създайте PIN код за защитен достъп до тази страница",
|
||||
"new_timeline": "Нова времева линия",
|
||||
"new_update": "Ново обновление",
|
||||
"new_user_created": "Създаден нов потребител",
|
||||
"new_version_available": "НАЛИЧНА НОВА ВЕРСИЯ",
|
||||
"newest_first": "Най-новите първи",
|
||||
@@ -1459,14 +1403,12 @@
|
||||
"no_cast_devices_found": "Няма намерени устройства за предаване",
|
||||
"no_checksum_local": "Липсват контролни суми - не може да се получат локални обекти",
|
||||
"no_checksum_remote": "Липсват контролни суми - не може да се получат обекти от сървъра",
|
||||
"no_devices": "Няма оторизирани устройства",
|
||||
"no_duplicates_found": "Не бяха открити дубликати.",
|
||||
"no_exif_info_available": "Няма exif информация",
|
||||
"no_explore_results_message": "Качете още снимки, за да разгледате колекцията си.",
|
||||
"no_favorites_message": "Добавете в любими, за да намирате бързо най-добрите си снимки и видеоклипове",
|
||||
"no_libraries_message": "Създайте външна библиотека за да разглеждате снимки и видеоклипове",
|
||||
"no_local_assets_found": "Не е намерен локален обект с такава контролна сума",
|
||||
"no_location_set": "Не е зададено местоположение",
|
||||
"no_locked_photos_message": "Снимките и видеата в заключената папка са скрити и не се показват при разглеждане на библиотеката.",
|
||||
"no_name": "Без име",
|
||||
"no_notifications": "Няма известия",
|
||||
@@ -1477,7 +1419,6 @@
|
||||
"no_results_description": "Опитайте със синоним или по-обща ключова дума",
|
||||
"no_shared_albums_message": "Създайте албум, за да споделяте снимки и видеоклипове с хората в мрежата си",
|
||||
"no_uploads_in_progress": "Няма качване в момента",
|
||||
"not_allowed": "Не е разрешено",
|
||||
"not_available": "Неналично",
|
||||
"not_in_any_album": "Не е в никой албум",
|
||||
"not_selected": "Не е избрано",
|
||||
@@ -1494,7 +1435,6 @@
|
||||
"oauth": "OAuth",
|
||||
"obtainium_configurator": "Конфигуратор за получаване",
|
||||
"obtainium_configurator_instructions": "Използвайте Obtainium за инсталация и обновяване на приложението за Android директно от GitHub на Immich. Създайте API ключ и изберете вариант за да създадете Obtainium конфигурационен линк",
|
||||
"ocr": "Оптично разпознаване на текст",
|
||||
"official_immich_resources": "Официална информация за Immich",
|
||||
"offline": "Офлайн",
|
||||
"offset": "Отместване",
|
||||
@@ -1526,7 +1466,6 @@
|
||||
"other_variables": "Други променливи",
|
||||
"owned": "Моите",
|
||||
"owner": "Собственик",
|
||||
"page": "Страница",
|
||||
"partner": "Партньор",
|
||||
"partner_can_access": "{partner} има достъп",
|
||||
"partner_can_access_assets": "Всички ваши снимки и видеоклипове, с изключение на тези в Архивирани и Изтрити",
|
||||
@@ -1589,8 +1528,6 @@
|
||||
"photos_count": "{count, plural, one {{count, number} Снимка} other {{count, number} Снимки}}",
|
||||
"photos_from_previous_years": "Снимки от предходни години",
|
||||
"pick_a_location": "Избери локация",
|
||||
"pick_custom_range": "Произволен период",
|
||||
"pick_date_range": "Изберете период",
|
||||
"pin_code_changed_successfully": "Успешно сменен PIN код",
|
||||
"pin_code_reset_successfully": "Успешно нулиран PIN код",
|
||||
"pin_code_setup_successfully": "Успешно зададен PIN код",
|
||||
@@ -1602,9 +1539,6 @@
|
||||
"play_memories": "Възпроизвеждане на спомени",
|
||||
"play_motion_photo": "Възпроизведи Motion Photo",
|
||||
"play_or_pause_video": "Възпроизвеждане или пауза на видео",
|
||||
"play_original_video": "Пусни оригиналното видео",
|
||||
"play_original_video_setting_description": "Предпочитане на показване на оригиналното видео, вместо транскодирани. Ако формата на оригиналния файл не се поддържа, възпроизвеждането може да бъде неправилно.",
|
||||
"play_transcoded_video": "Покажи транскодирано видео",
|
||||
"please_auth_to_access": "Моля, удостовери за достъп",
|
||||
"port": "Порт",
|
||||
"preferences_settings_subtitle": "Управление на предпочитанията на приложението",
|
||||
@@ -1696,7 +1630,7 @@
|
||||
"regenerating_thumbnails": "Пресъздаване на миниатюрите",
|
||||
"remote": "На сървъра",
|
||||
"remote_assets": "Обекти на сървъра",
|
||||
"remote_media_summary": "Обобщение на файловете на сървъра",
|
||||
"remote_media_summary": "Обобщение на медийните файлове на сървъра",
|
||||
"remove": "Премахни",
|
||||
"remove_assets_album_confirmation": "Сигурни ли сте, че искате да премахнете {count, plural, one {# елемент} other {# елемента}} от албума?",
|
||||
"remove_assets_shared_link_confirmation": "Сигурни ли сте, че искате да премахнете {count, plural, one {# елемент} other {# елемента}} от този споеделен линк?",
|
||||
@@ -1741,7 +1675,6 @@
|
||||
"reset_sqlite_confirmation": "Наистина ли искате да нулирате базата данни SQLite? Ще трябва да излезете от системата и да се впишете отново за нова синхронизация на данните",
|
||||
"reset_sqlite_success": "Успешно нулиране на базата данни SQLite",
|
||||
"reset_to_default": "Връщане на фабрични настройки",
|
||||
"resolution": "Резолюция",
|
||||
"resolve_duplicates": "Реши дубликатите",
|
||||
"resolved_all_duplicates": "Всички дубликати са решени",
|
||||
"restore": "Възстановяване",
|
||||
@@ -1760,7 +1693,6 @@
|
||||
"running": "Изпълняване",
|
||||
"save": "Запази",
|
||||
"save_to_gallery": "Запази в галерията",
|
||||
"saved": "Записано",
|
||||
"saved_api_key": "Запазен API Key",
|
||||
"saved_profile": "Запазен профил",
|
||||
"saved_settings": "Запазени настройки",
|
||||
@@ -1777,9 +1709,6 @@
|
||||
"search_by_description_example": "Разходка в Сапа",
|
||||
"search_by_filename": "Търси по име на файла или разширение",
|
||||
"search_by_filename_example": "например IMG_1234.JPG или PNG",
|
||||
"search_by_ocr": "Търсене на текст",
|
||||
"search_by_ocr_example": "Lattе",
|
||||
"search_camera_lens_model": "Търсене на модел на обектива...",
|
||||
"search_camera_make": "Търси производител на камерата...",
|
||||
"search_camera_model": "Търси модел на камерата...",
|
||||
"search_city": "Търси град...",
|
||||
@@ -1796,7 +1725,6 @@
|
||||
"search_filter_location_title": "Избери място",
|
||||
"search_filter_media_type": "Тип на файла",
|
||||
"search_filter_media_type_title": "Избери тип на файла",
|
||||
"search_filter_ocr": "Търсене нa текст",
|
||||
"search_filter_people_title": "Избери хора",
|
||||
"search_for": "Търси за",
|
||||
"search_for_existing_person": "Търси съществуващ човек",
|
||||
@@ -1858,8 +1786,6 @@
|
||||
"server_offline": "Сървър офлайн",
|
||||
"server_online": "Сървър онлайн",
|
||||
"server_privacy": "Поверителност на сървъра",
|
||||
"server_restarting_description": "Страницата ще се обнови всеки момент.",
|
||||
"server_restarting_title": "Рестартиране на сървъра",
|
||||
"server_stats": "Статус на сървъра",
|
||||
"server_update_available": "Налична е нова версия за сървъра",
|
||||
"server_version": "Версия на сървъра",
|
||||
@@ -1983,7 +1909,6 @@
|
||||
"show_slideshow_transition": "Покажи прехода на слайдшоуто",
|
||||
"show_supporter_badge": "Значка поддръжник",
|
||||
"show_supporter_badge_description": "Покажи значка поддръжник",
|
||||
"show_text_recognition": "Покажи разпознатия текст",
|
||||
"show_text_search_menu": "Покажи менюто за търсене на текст",
|
||||
"shuffle": "Разбъркване",
|
||||
"sidebar": "Странична лента",
|
||||
@@ -2054,10 +1979,9 @@
|
||||
"tags": "Етикет",
|
||||
"tap_to_run_job": "Докоснете, за да стартирате задачата",
|
||||
"template": "Шаблон",
|
||||
"text_recognition": "Разпознаване на текст",
|
||||
"theme": "Тема",
|
||||
"theme_selection": "Избор на тема",
|
||||
"theme_selection_description": "Автоматично задаване на светла или тъмна тема спрямо системните предпочитания на браузъра ви",
|
||||
"theme_selection_description": "Автоматично задаване на светла или тъмна тема въз основа на системните предпочитания на вашия браузър",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Показвай индикатор за хранилището в заглавията на обектите",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Брой обекти на ред ({count})",
|
||||
"theme_setting_colorful_interface_subtitle": "Нанеси основен цвят върху фоновите повърхности.",
|
||||
@@ -2073,9 +1997,7 @@
|
||||
"theme_setting_three_stage_loading_title": "Включи три-степенно зареждане",
|
||||
"they_will_be_merged_together": "Те ще бъдат обединени",
|
||||
"third_party_resources": "Ресурси от трети страни",
|
||||
"time": "Време",
|
||||
"time_based_memories": "Спомени, базирани на времето",
|
||||
"time_based_memories_duration": "Продължителност в секунди за показване на всяка картина.",
|
||||
"timeline": "Хронология",
|
||||
"timezone": "Часова зона",
|
||||
"to_archive": "Архивирай",
|
||||
@@ -2087,7 +2009,6 @@
|
||||
"to_select": "за избор",
|
||||
"to_trash": "Кошче",
|
||||
"toggle_settings": "Превключване на настройките",
|
||||
"toggle_theme_description": "Превключване на темата",
|
||||
"total": "Общо",
|
||||
"total_usage": "Общо използвано",
|
||||
"trash": "Кошче",
|
||||
@@ -2196,7 +2117,6 @@
|
||||
"view_album": "Разгледай албума",
|
||||
"view_all": "Преглед на всички",
|
||||
"view_all_users": "Преглед на всички потребители",
|
||||
"view_asset_owners": "Преглед на собствениците на активи",
|
||||
"view_details": "Подробности за изгледа",
|
||||
"view_in_timeline": "Покажи във времева линия",
|
||||
"view_link": "Преглед на връзката",
|
||||
@@ -2213,13 +2133,11 @@
|
||||
"viewer_unstack": "Премахни от опашката",
|
||||
"visibility_changed": "Видимостта е променена за {count, plural, one {# човек} other {# човека}}",
|
||||
"waiting": "в изчакване",
|
||||
"waiting_count": "В изчакване: {count}",
|
||||
"warning": "Внимание",
|
||||
"week": "Седмица",
|
||||
"welcome": "Добре дошли",
|
||||
"welcome_to_immich": "Добре дошли в Immich",
|
||||
"wifi_name": "Wi-Fi мрежа",
|
||||
"workflow": "Работен процес",
|
||||
"wrong_pin_code": "Грешен PIN код",
|
||||
"year": "Година",
|
||||
"years_ago": "преди {years, plural, one {# година} other {# години}}",
|
||||
|
||||
20
i18n/bi.json
20
i18n/bi.json
@@ -12,28 +12,12 @@
|
||||
"add_a_name": "Putem nam blo hem",
|
||||
"add_a_title": "Putem wan name blo hem",
|
||||
"add_exclusion_pattern": "Putem wan paten wae hemi karem aot",
|
||||
"add_import_path": "Putem wan pat blo import",
|
||||
"add_location": "Putem wan place blo hem",
|
||||
"add_more_users": "Putem mor man",
|
||||
"readonly_mode_enabled": "Mod blo yu no save janjem i on",
|
||||
"reassigned_assets_to_new_person": "Janjem{count, plural, one {# asset} other {# assets}} blo nu man",
|
||||
"reassing_hint": "janjem ol sumtin yu bin joos i go blo wan man",
|
||||
"recent-albums": "album i no old tu mas",
|
||||
"recent_searches": "lukabout wea i no old tu mas",
|
||||
"time_based_memories_duration": "hao mus second blo wan wan imij i stap lo scrin.",
|
||||
"timezone": "taemzon",
|
||||
"to_change_password": "janjem pasword",
|
||||
"to_login": "Login",
|
||||
"to_multi_select": "to jusem mani",
|
||||
"to_parent": "go lo parent",
|
||||
"to_select": "to selectem",
|
||||
"to_trash": "toti",
|
||||
"toggle_settings": "sho settings",
|
||||
"total": "Total",
|
||||
"trash": "Toti",
|
||||
"trash_action_prompt": "{count} igo lo plaes lo toti",
|
||||
"trash_all": "Putem ol i go lo toti",
|
||||
"trash_count": "Toti {count, number}",
|
||||
"trash_emptied": "basket blo toti i empti nomo",
|
||||
"trash_no_results_message": "Foto mo video lo basket blo toti yu save lukem lo plaes ia.",
|
||||
"trash_page_delete_all": "Delete oli ol"
|
||||
"recent_searches": "lukabout wea i no old tu mas"
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"add_birthday": "একটি জন্মদিন যোগ করুন",
|
||||
"add_endpoint": "এন্ডপয়েন্ট যোগ করুন",
|
||||
"add_exclusion_pattern": "বহির্ভূতকরণ নমুনা",
|
||||
"add_import_path": "ইমপোর্ট করার পাথ যুক্ত করুন",
|
||||
"add_location": "অবস্থান যুক্ত করুন",
|
||||
"add_more_users": "আরো ব্যবহারকারী যুক্ত করুন",
|
||||
"add_partner": "অংশীদার যোগ করুন",
|
||||
@@ -72,6 +73,7 @@
|
||||
"disable_login": "লগইন অক্ষম করুন",
|
||||
"duplicate_detection_job_description": "অনুরূপ ছবি সনাক্ত করতে সম্পদগুলিতে মেশিন লার্নিং চালান। স্মার্ট অনুসন্ধানের উপর নির্ভর করে",
|
||||
"exclusion_pattern_description": "এক্সক্লুশন প্যাটার্ন ব্যবহার করে আপনি আপনার লাইব্রেরি স্ক্যান করার সময় ফাইল এবং ফোল্ডারগুলিকে উপেক্ষা করতে পারবেন। যদি আপনার এমন ফোল্ডার থাকে যেখানে এমন ফাইল থাকে যা আপনি আমদানি করতে চান না, যেমন RAW ফাইল।",
|
||||
"external_library_management": "বহিরাগত গ্রন্থাগার ব্যবস্থাপনা",
|
||||
"face_detection": "মুখ সনাক্তকরণ",
|
||||
"face_detection_description": "মেশিন লার্নিং ব্যবহার করে অ্যাসেটে থাকা মুখ/চেহারা গুলি সনাক্ত করুন। ভিডিও গুলির জন্য, শুধুমাত্র থাম্বনেইল বিবেচনা করা হয়। \"রিফ্রেশ\" (পুনরায়) সমস্ত অ্যাসেট প্রক্রিয়া করে। \"রিসেট\" করার মাধ্যমে অতিরিক্তভাবে সমস্ত বর্তমান মুখের ডেটা সাফ করে। \"অনুপস্থিত\" অ্যাসেটগুলিকে সারিবদ্ধ করে যা এখনও প্রক্রিয়া করা হয়নি। সনাক্ত করা মুখগুলিকে ফেসিয়াল রিকগনিশনের জন্য সারিবদ্ধ করা হবে, ফেসিয়াল ডিটেকশন সম্পূর্ণ হওয়ার পরে, বিদ্যমান বা নতুন ব্যক্তিদের মধ্যে গোষ্ঠীবদ্ধ করে।",
|
||||
"facial_recognition_job_description": "শনাক্ত করা মুখগুলিকে মানুষের মধ্যে গোষ্ঠীভুক্ত/গ্রুপ করুন। মুখ সনাক্তকরণ সম্পূর্ণ হওয়ার পরে এই ধাপটি চলে। \"রিসেট\" (পুনরায়) সমস্ত মুখকে ক্লাস্টার করে। \"অনুপস্থিত/মিসিং\" মুখগুলিকে সারিতে রাখে যেগুলো কোনও ব্যক্তিকে এসাইন/বরাদ্দ করা হয়নি।",
|
||||
@@ -104,10 +106,12 @@
|
||||
"job_not_concurrency_safe": "এই কাজটি সমান্তরালভাবে চালানো নিরাপদ নয়",
|
||||
"job_settings": "কাজের সেটিংস",
|
||||
"job_settings_description": "কাজের সমান্তরালতা পরিচালনা করুন",
|
||||
"job_status": "চাকরির অবস্থা",
|
||||
"jobs_delayed": "{jobCount, plural, other {# বিলম্বিত}}",
|
||||
"jobs_failed": "{jobCount, plural, other {# ব্যর্থ}}",
|
||||
"library_created": "লাইব্রেরি তৈরি করা হয়েছেঃ {library}",
|
||||
"library_deleted": "লাইব্রেরি মুছে ফেলা হয়েছে",
|
||||
"library_import_path_description": "ইম্পোর্ট/যোগ করার জন্য একটি ফোল্ডার নির্দিষ্ট করুন। সাবফোল্ডার সহ এই ফোল্ডারটি ছবি এবং ভিডিওর জন্য স্ক্যান করা হবে।",
|
||||
"library_scanning": "পর্যায়ক্রমিক স্ক্যানিং",
|
||||
"library_scanning_description": "পর্যায়ক্রমিক লাইব্রেরি স্ক্যানিং কনফিগার করুন",
|
||||
"library_scanning_enable_description": "পর্যায়ক্রমিক লাইব্রেরি স্ক্যানিং সক্ষম করুন",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
124
i18n/ca.json
124
i18n/ca.json
@@ -7,7 +7,6 @@
|
||||
"action_common_update": "Actualitzar",
|
||||
"actions": "Accions",
|
||||
"active": "Actiu",
|
||||
"active_count": "Activat: {count}",
|
||||
"activity": "Activitat",
|
||||
"activity_changed": "L'activitat està {enabled, select, true {activada} other {desactivada}}",
|
||||
"add": "Afegir",
|
||||
@@ -18,6 +17,7 @@
|
||||
"add_birthday": "Afegeix la data de naixement",
|
||||
"add_endpoint": "afegir endpoint",
|
||||
"add_exclusion_pattern": "Afegir un patró d'exclusió",
|
||||
"add_import_path": "Afegir una ruta d'importació",
|
||||
"add_location": "Afegir la ubicació",
|
||||
"add_more_users": "Afegir més usuaris",
|
||||
"add_partner": "Afegir company/a",
|
||||
@@ -32,7 +32,6 @@
|
||||
"add_to_album_toggle": "Commutar selecció de {album}",
|
||||
"add_to_albums": "Afegir als àlbums",
|
||||
"add_to_albums_count": "Afegir als àlbums ({count})",
|
||||
"add_to_bottom_bar": "Afegir a",
|
||||
"add_to_shared_album": "Afegir a un àlbum compartit",
|
||||
"add_upload_to_stack": "Afegeix la càrrega a la pila",
|
||||
"add_url": "Afegir URL",
|
||||
@@ -68,7 +67,6 @@
|
||||
"confirm_reprocess_all_faces": "Esteu segurs que voleu reprocessar totes les cares? Això també esborrarà la gent que heu anomenat.",
|
||||
"confirm_user_password_reset": "Esteu segur que voleu reinicialitzar la contrasenya de l'usuari {user}?",
|
||||
"confirm_user_pin_code_reset": "Esteu segur que voleu restablir el codi PIN de {user}?",
|
||||
"copy_config_to_clipboard_description": "Copia la configuració actual del sistema com a objecte JSON al porta-retalls",
|
||||
"create_job": "Crear tasca",
|
||||
"cron_expression": "Expressió Cron",
|
||||
"cron_expression_description": "Estableix l'interval d'escaneig amb el format cron. Per obtenir més informació, consulteu, p.e <link>Crontab Guru</link>",
|
||||
@@ -76,8 +74,7 @@
|
||||
"disable_login": "Deshabiliteu l'inici de sessió",
|
||||
"duplicate_detection_job_description": "Executa l'aprenentatge automàtic en els elements per a detectar imatges semblants. Fa servir la cerca intel·ligent",
|
||||
"exclusion_pattern_description": "Els patrons d'exclusió permeten ignorar fitxers i carpetes quan escanegeu una llibreria. Això és útil si teniu carpetes que contenen fitxer que no voleu importar, com els fitxers RAW.",
|
||||
"export_config_as_json_description": "Baixa la configuració actual del sistema com a fitxer JSON",
|
||||
"external_libraries_page_description": "Pàgina de la biblioteca externa de l'administrador",
|
||||
"external_library_management": "Gestió de llibreries externes",
|
||||
"face_detection": "Detecció de cares",
|
||||
"face_detection_description": "Detecta les cares fent servir aprenentatge automàtic. Per a videos només és té en compte la miniatura. \"Actualitzar\" reprocessa tots els elements. \"Resetejar\" esborra tota la informació de cares actuals. \"Pendent\" afegeix a la cua els elements que encara no han estat processats. Les cares detectades s'afegiran a la cua per al Reconeixement Facial després de completar la Detecció Facial, tot agrupant-les entre noves persones o les ja existents.",
|
||||
"facial_recognition_job_description": "Agrupa les cares detectades per persona. Aquest pas s'executa després de completar la detecció de cares. \"Resetejar\" reagrupa totes les cares. \"Pendent\" afegeix a la cua les cares que no tenen cap persona assignada.",
|
||||
@@ -105,28 +102,23 @@
|
||||
"image_thumbnail_description": "Miniatura petita amb metadades eliminades, que s'utilitza quan es visualitzen grups de fotos com la línia de temps principal",
|
||||
"image_thumbnail_quality_description": "Qualitat de miniatura d'1 a 100. Més alt és millor, però produeix fitxers més grans i pot reduir la capacitat de resposta de l'aplicació.",
|
||||
"image_thumbnail_title": "Configuració de les miniatures",
|
||||
"import_config_from_json_description": "Importa la configuració del sistema pujant un fitxer de configuració JSON",
|
||||
"job_concurrency": "{job} simultàniament",
|
||||
"job_created": "Tasca creada",
|
||||
"job_not_concurrency_safe": "Aquesta tasca no és segura per a la conconcurrència.",
|
||||
"job_settings": "Configuració de les tasques",
|
||||
"job_settings_description": "Gestiona la concurrència de tasques",
|
||||
"job_status": "Estat de les tasques",
|
||||
"jobs_delayed": "{jobCount, plural, other {# posposades}}",
|
||||
"jobs_failed": "{jobCount, plural, other {# fallides}}",
|
||||
"jobs_over_time": "Feines al llarg del temps",
|
||||
"library_created": "Bilbioteca creada: {library}",
|
||||
"library_deleted": "Bilbioteca eliminada",
|
||||
"library_details": "Detalls de la llibreria",
|
||||
"library_folder_description": "Especifiqueu una carpeta per importar. Aquesta carpeta, incloses les subcarpetes, s'escanejarà a la recerca d'imatges i vídeos.",
|
||||
"library_remove_exclusion_pattern_prompt": "Esteu segur que voleu eliminar aquest patró d'exclusió?",
|
||||
"library_remove_folder_prompt": "Esteu segur que voleu eliminar aquesta carpeta d'importació?",
|
||||
"library_import_path_description": "Especifiqueu una carpeta a importar. Aquesta carpeta, incloses les seves subcarpetes, serà escanejada per cercar-hi imatges i vídeos.",
|
||||
"library_scanning": "Escaneig periòdic",
|
||||
"library_scanning_description": "Configurar l'escaneig periòdic de bilbioteques",
|
||||
"library_scanning_enable_description": "Habilita l'escaneig periòdic de biblioteques",
|
||||
"library_settings": "Llibreria externes",
|
||||
"library_settings_description": "Gestiona la configuració de les llibreries externes",
|
||||
"library_tasks_description": "Escaneja les biblioteques externes per trobar arxius nous o canviats",
|
||||
"library_updated": "Llibreria actualitzada",
|
||||
"library_watching_enable_description": "Consultar llibreries externes per detectar canvis en fitxers",
|
||||
"library_watching_settings": "Monitoratge de la llibreria (EXPERIMENTAL)",
|
||||
"library_watching_settings_description": "Monitorització automàtica de fitxers modificats",
|
||||
@@ -162,18 +154,6 @@
|
||||
"machine_learning_min_detection_score_description": "La puntuació mínima de confiança per detectar una cara és de 0 a 1. Valors més baixos detectaran més cares, però poden donar lloc a falsos positius.",
|
||||
"machine_learning_min_recognized_faces": "Nombre mínim de cares reconegudes",
|
||||
"machine_learning_min_recognized_faces_description": "El nombre mínim de cares reconegudes per crear una persona. Augmentar aquest valor fa que el reconeixement facial sigui més precís, però augmenta la possibilitat que una cara no sigui assignada a una persona.",
|
||||
"machine_learning_ocr": "OCR",
|
||||
"machine_learning_ocr_description": "Fes servir machine learning per reconèixer text a imatges",
|
||||
"machine_learning_ocr_enabled": "Activar OCR",
|
||||
"machine_learning_ocr_enabled_description": "Si està desactivat, les imatges no seran objecte de reconeixement de text.",
|
||||
"machine_learning_ocr_max_resolution": "Màxima resolució",
|
||||
"machine_learning_ocr_max_resolution_description": "Vista prèvia per sobre d'aquesta resolució serà reescalada per preservar la relació d'aspecte. Resolucions altes són més precises, però triguen més i gasten més memòria.",
|
||||
"machine_learning_ocr_min_detection_score": "Puntuació mínima de detecció",
|
||||
"machine_learning_ocr_min_detection_score_description": "Puntuació de mínima confiança per la detecció del text entre 0-1. Valors baixos detectaran més text pero pot donar falsos positius.",
|
||||
"machine_learning_ocr_min_recognition_score": "Puntuació mínima de reconeixement",
|
||||
"machine_learning_ocr_min_score_recognition_description": "Puntuació de confiança mínima pel reconeixement del text entre 0-1. Valors baixos reconeixen més text però pot donar falsos positius.",
|
||||
"machine_learning_ocr_model": "Model OCR",
|
||||
"machine_learning_ocr_model_description": "Models de servidor són més precisos que els de móbil, pero triguen més a processar i usen més memòria.",
|
||||
"machine_learning_settings": "Configuració d'aprenentatge automàtic",
|
||||
"machine_learning_settings_description": "Gestiona funcions i configuració d'aprenentatge automàtic",
|
||||
"machine_learning_smart_search": "Cerca intel·ligent",
|
||||
@@ -181,12 +161,7 @@
|
||||
"machine_learning_smart_search_enabled": "Activa la cerca intel·ligent",
|
||||
"machine_learning_smart_search_enabled_description": "Si està desactivada, les imatges no es codificaran per la cerca intel·ligent.",
|
||||
"machine_learning_url_description": "L'URL del servidor d'aprenentatge automàtic. Si es proporciona més d'un URL, s'intentarà accedir a cada servidor en ordre fins que un d'ells respongui correctament.",
|
||||
"maintenance_settings": "En manteniment",
|
||||
"maintenance_settings_description": "Posar Immich en mode de manteniment.",
|
||||
"maintenance_start": "Iniciar el mode de manteniment",
|
||||
"maintenance_start_error": "Error en iniciar el mode de manteniment.",
|
||||
"manage_concurrency": "Gestiona la concurrència",
|
||||
"manage_concurrency_description": "Ves a la pàgina de tasques per gestionar la concurrència de tasques",
|
||||
"manage_log_settings": "Gestiona la configuració del registre",
|
||||
"map_dark_style": "Tema fosc",
|
||||
"map_enable_description": "Habilita característiques del mapa",
|
||||
@@ -236,8 +211,6 @@
|
||||
"notification_email_ignore_certificate_errors_description": "Ignora els errors de validació de certificat TLS (no recomanat)",
|
||||
"notification_email_password_description": "Contrasenya per a autenticar-se amb el servidor de correu electrònic",
|
||||
"notification_email_port_description": "Port del servidor de correu electrònic (p.ex. 25, 465 o 587)",
|
||||
"notification_email_secure": "SMTPS",
|
||||
"notification_email_secure_description": "Fes servir SMTPS (SMTP sobre TLS)",
|
||||
"notification_email_sent_test_email_button": "Envia correu de prova i desa",
|
||||
"notification_email_setting_description": "Configuració per l'enviament de notificacions per correu electrònic",
|
||||
"notification_email_test_email": "Envia correu de prova",
|
||||
@@ -270,20 +243,15 @@
|
||||
"oauth_storage_quota_default_description": "Quota disponible en GB quan no s'estableixi cap valor (Entreu 0 per a quota il·limitada).",
|
||||
"oauth_timeout": "Solicitud caducada",
|
||||
"oauth_timeout_description": "Timeout per a sol·licituds en mil·lisegons",
|
||||
"ocr_job_description": "Fes servir machine learning per reconèixer text a les imatges",
|
||||
"password_enable_description": "Inicia sessió amb correu electrònic i contrasenya",
|
||||
"password_settings": "Inici de sessió amb contrasenya",
|
||||
"password_settings_description": "Gestiona la configuració de l'inici de sessió amb contrasenya",
|
||||
"paths_validated_successfully": "Totes les rutes han estat validades amb èxit",
|
||||
"person_cleanup_job": "Neteja de persona",
|
||||
"queue_details": "Detall de les cues",
|
||||
"queues": "Cues de treball",
|
||||
"queues_page_description": "Pàgina de cues de treballs d'administrador",
|
||||
"quota_size_gib": "Tamany de la quota (GiB)",
|
||||
"refreshing_all_libraries": "Actualitzant totes les biblioteques",
|
||||
"registration": "Registre d'administrador",
|
||||
"registration_description": "Com que ets el primer usuari del sistema, seràs designat com a administrador i seràs responsable de les tasques administratives. També seràs l'encarregat de crear usuaris addicionals.",
|
||||
"remove_failed_jobs": "Eliminar treballs fallits",
|
||||
"require_password_change_on_login": "Requerir que l'usuari canviï la contrasenya en el primer inici de sessió",
|
||||
"reset_settings_to_default": "Restablir configuracions per defecte",
|
||||
"reset_settings_to_recent_saved": "Restablir la configuració guardada més recent",
|
||||
@@ -296,10 +264,8 @@
|
||||
"server_public_users_description": "Tots els usuaris (nom i correu electrònic) apareixen a la llista a l'afegir un usuari als àlbums compartits. Si es desactiva, la llista només serà disponible pels usuaris administradors.",
|
||||
"server_settings": "Configuració del servidor",
|
||||
"server_settings_description": "Gestiona la configuració del servidor",
|
||||
"server_stats_page_description": "Pàgina d'estadístiques del servidor de l'administrador",
|
||||
"server_welcome_message": "Missatge de benvinguda",
|
||||
"server_welcome_message_description": "Missatge que es mostra a la pàgina d'inici de sessió.",
|
||||
"settings_page_description": "Pàgina de configuració de l'administrador",
|
||||
"sidecar_job": "Metadades auxiliars",
|
||||
"sidecar_job_description": "Descobreix o sincronitza metadades auxiliars des del sistema de fitxers",
|
||||
"slideshow_duration_description": "Segons per mostrar cada imatge",
|
||||
@@ -419,7 +385,6 @@
|
||||
"user_settings": "Configuració d'usuaris",
|
||||
"user_settings_description": "Gestiona la configuració dels usuaris",
|
||||
"user_successfully_removed": "L'usuari {email} s'ha eliminat correctament.",
|
||||
"users_page_description": "Pàgina d'usuaris de l'administrador",
|
||||
"version_check_enabled_description": "Activa la comprovació de la versió",
|
||||
"version_check_implications": "La funció de comprovació de versions depèn de comunicacions periòdiques amb github.com",
|
||||
"version_check_settings": "Comprovació de versió",
|
||||
@@ -437,11 +402,11 @@
|
||||
"advanced_settings_prefer_remote_subtitle": "Alguns dispositius són molt lents en carregar miniatures dels elements locals. Activeu aquest paràmetre per carregar imatges remotes en el seu lloc.",
|
||||
"advanced_settings_prefer_remote_title": "Prefereix imatges remotes",
|
||||
"advanced_settings_proxy_headers_subtitle": "Definiu les capçaleres de proxy que Immich per enviar amb cada sol·licitud de xarxa",
|
||||
"advanced_settings_proxy_headers_title": "Capçaleres de proxy particulars [EXPERIMENTAL]",
|
||||
"advanced_settings_proxy_headers_title": "Capçaleres de proxy",
|
||||
"advanced_settings_readonly_mode_subtitle": "Habilita el només de lectura mode on les fotos poden ser només vist, a coses els agrada seleccionant imatges múltiples, compartint, càsting, elimina és tot discapacitat. Habilita/Desactiva només de lectura via avatar d'usuari des de la pantalla major",
|
||||
"advanced_settings_readonly_mode_title": "Mode de només lectura",
|
||||
"advanced_settings_self_signed_ssl_subtitle": "Omet la verificació del certificat SSL del servidor. Requerit per a certificats autosignats.",
|
||||
"advanced_settings_self_signed_ssl_title": "Permet certificats SSL autosignats [EXPERIMENTAL]",
|
||||
"advanced_settings_self_signed_ssl_title": "Permet certificats SSL autosignats",
|
||||
"advanced_settings_sync_remote_deletions_subtitle": "Suprimeix o restaura automàticament un actiu en aquest dispositiu quan es realitzi aquesta acció al web",
|
||||
"advanced_settings_sync_remote_deletions_title": "Sincronitza les eliminacions remotes",
|
||||
"advanced_settings_tile_subtitle": "Configuració avançada de l'usuari",
|
||||
@@ -450,7 +415,6 @@
|
||||
"age_months": "{months, plural, one {# mes} other {# mesos}}",
|
||||
"age_year_months": "Un any i {months, plural, one {# mes} other {# mesos}}",
|
||||
"age_years": "{years, plural, one {# any} other {# anys}}",
|
||||
"album": "Àlbum",
|
||||
"album_added": "Àlbum afegit",
|
||||
"album_added_notification_setting_description": "Rep una notificació per correu quan siguis afegit a un àlbum compartit",
|
||||
"album_cover_updated": "Portada de l'àlbum actualitzada",
|
||||
@@ -496,21 +460,16 @@
|
||||
"allow_edits": "Permet editar",
|
||||
"allow_public_user_to_download": "Permet que l'usuari públic pugui descarregar",
|
||||
"allow_public_user_to_upload": "Permet que l'usuari públic pugui carregar",
|
||||
"allowed": "Permès",
|
||||
"alt_text_qr_code": "Codi QR",
|
||||
"anti_clockwise": "En sentit antihorari",
|
||||
"api_key": "Clau API",
|
||||
"api_key_description": "Aquest valor només es mostrarà una vegada. Assegureu-vos de copiar-lo abans de tancar la finestra.",
|
||||
"api_key_empty": "El nom de la clau de l'API no pot estar buit",
|
||||
"api_keys": "Claus API",
|
||||
"app_architecture_variant": "Variant (Arquitectura)",
|
||||
"app_bar_signout_dialog_content": "Estàs segur que vols tancar la sessió?",
|
||||
"app_bar_signout_dialog_ok": "Sí",
|
||||
"app_bar_signout_dialog_title": "Tanca la sessió",
|
||||
"app_download_links": "App descarrega enllaços",
|
||||
"app_settings": "Configuració de l'app",
|
||||
"app_stores": "Botiga App",
|
||||
"app_update_available": "Actualització App disponible",
|
||||
"appears_in": "Apareix a",
|
||||
"apply_count": "Aplicar ({count, number})",
|
||||
"archive": "Arxiu",
|
||||
@@ -594,7 +553,6 @@
|
||||
"backup_albums_sync": "Sincronització d'àlbums de còpia de seguretat",
|
||||
"backup_all": "Tots",
|
||||
"backup_background_service_backup_failed_message": "No s'ha pogut copiar els elements. Tornant a intentar…",
|
||||
"backup_background_service_complete_notification": "Backup completat d'actius",
|
||||
"backup_background_service_connection_failed_message": "No s'ha pogut connectar al servidor. Tornant a intentar…",
|
||||
"backup_background_service_current_upload_notification": "Pujant {filename}",
|
||||
"backup_background_service_default_notification": "Cercant nous elements…",
|
||||
@@ -652,7 +610,6 @@
|
||||
"backup_options_page_title": "Opcions de còpia de seguretat",
|
||||
"backup_setting_subtitle": "Gestiona la configuració de càrrega en segon pla i en primer pla",
|
||||
"backup_settings_subtitle": "Administra la configuració de pujada",
|
||||
"backup_upload_details_page_more_details": "Toqueu per obtenir més detalls",
|
||||
"backward": "Enrere",
|
||||
"biometric_auth_enabled": "Autentificació biomètrica activada",
|
||||
"biometric_locked_out": "Esteu bloquejats fora de l'autenticació biomètrica",
|
||||
@@ -705,8 +662,6 @@
|
||||
"change_password_description": "Aquesta és la primera vegada que inicieu la sessió al sistema o s'ha fet una sol·licitud per canviar la contrasenya. Introduïu la nova contrasenya a continuació.",
|
||||
"change_password_form_confirm_password": "Confirma la contrasenya",
|
||||
"change_password_form_description": "Hola {name},\n\nAquesta és la primera vegada que inicies sessió al sistema o bé s'ha sol·licitat canviar la teva contrasenya. Si us plau, introdueix la nova contrasenya a continuació.",
|
||||
"change_password_form_log_out": "Fer fora de tots els altres dispositius",
|
||||
"change_password_form_log_out_description": "Es recomana fer fora de tots els altres dispositius",
|
||||
"change_password_form_new_password": "Nova contrasenya",
|
||||
"change_password_form_password_mismatch": "Les contrasenyes no coincideixen",
|
||||
"change_password_form_reenter_new_password": "Torna a introduir la nova contrasenya",
|
||||
@@ -719,7 +674,6 @@
|
||||
"check_corrupt_asset_backup_button": "Realitzar comprovació",
|
||||
"check_corrupt_asset_backup_description": "Executeu aquesta comprovació només mitjançant Wi-Fi i un cop s'hagi fet una còpia de seguretat de tots els actius. El procediment pot trigar uns minuts.",
|
||||
"check_logs": "Comprovar els registres",
|
||||
"checksum": "Suma de control",
|
||||
"choose_matching_people_to_merge": "Trieu les persones que coincideixin per combinar-les",
|
||||
"city": "Ciutat",
|
||||
"clear": "Buida",
|
||||
@@ -742,7 +696,6 @@
|
||||
"collapse_all": "Redueix-ho tot",
|
||||
"color": "Color",
|
||||
"color_theme": "Tema de color",
|
||||
"command": "Ordre",
|
||||
"comment_deleted": "Comentari esborrat",
|
||||
"comment_options": "Opcions de comentari",
|
||||
"comments_and_likes": "Comentaris i agradaments",
|
||||
@@ -786,7 +739,6 @@
|
||||
"create": "Crea",
|
||||
"create_album": "Crear un àlbum",
|
||||
"create_album_page_untitled": "Sense títol",
|
||||
"create_api_key": "Crear clau API",
|
||||
"create_library": "Crea una llibreria",
|
||||
"create_link": "Crear enllaç",
|
||||
"create_link_to_share": "Crear enllaç per compartir",
|
||||
@@ -816,7 +768,6 @@
|
||||
"daily_title_text_date_year": "E, dd MMM, yyyy",
|
||||
"dark": "Fosc",
|
||||
"dark_theme": "Canviar a tema fosc",
|
||||
"date": "Data",
|
||||
"date_after": "Data posterior a",
|
||||
"date_and_time": "Data i hora",
|
||||
"date_before": "Data anterior a",
|
||||
@@ -919,6 +870,8 @@
|
||||
"edit_description_prompt": "Si us plau, selecciona una nova descripció:",
|
||||
"edit_exclusion_pattern": "Edita patró d'exclusió",
|
||||
"edit_faces": "Edita les cares",
|
||||
"edit_import_path": "Edita la ruta d'importació",
|
||||
"edit_import_paths": "Edita les rutes d'importació",
|
||||
"edit_key": "Edita clau",
|
||||
"edit_link": "Edita enllaç",
|
||||
"edit_location": "Edita ubicació",
|
||||
@@ -990,8 +943,8 @@
|
||||
"failed_to_stack_assets": "No s'han pogut apilar els elements",
|
||||
"failed_to_unstack_assets": "No s'han pogut desapilar els elements",
|
||||
"failed_to_update_notification_status": "Error en actualitzar l'estat de les notificacions",
|
||||
"import_path_already_exists": "Aquesta ruta d'importació ja existeix.",
|
||||
"incorrect_email_or_password": "Correu electrònic o contrasenya incorrectes",
|
||||
"library_folder_already_exists": "Aquesta ruta d'importació ja existeix.",
|
||||
"paths_validation_failed": "{paths, plural, one {# ruta} other {# rutes}} no ha pogut validar",
|
||||
"profile_picture_transparent_pixels": "Les fotos de perfil no poden tenir píxels transparents. Per favor, feu zoom in, mogueu la imatge o ambdues.",
|
||||
"quota_higher_than_disk_size": "Heu establert una quota més gran que la mida de disc",
|
||||
@@ -1000,6 +953,7 @@
|
||||
"unable_to_add_assets_to_shared_link": "No s'han pogut afegir els elements a l'enllaç compartit",
|
||||
"unable_to_add_comment": "No es pot afegir el comentari",
|
||||
"unable_to_add_exclusion_pattern": "No s'ha pogut afegir el patró d’exclusió",
|
||||
"unable_to_add_import_path": "No s'ha pogut afegir la ruta d'importació",
|
||||
"unable_to_add_partners": "No es poden afegir companys",
|
||||
"unable_to_add_remove_archive": "No s'ha pogut {archived, select, true {eliminar l'element de} other {afegir l'element a}} l'arxiu",
|
||||
"unable_to_add_remove_favorites": "No s'ha pogut {favorite, select, true {afegir l'element als} other {eliminar l'element dels}} preferits",
|
||||
@@ -1022,10 +976,12 @@
|
||||
"unable_to_delete_asset": "No es pot suprimir el recurs",
|
||||
"unable_to_delete_assets": "S'ha produït un error en suprimir recursos",
|
||||
"unable_to_delete_exclusion_pattern": "No es pot suprimir el patró d'exclusió",
|
||||
"unable_to_delete_import_path": "No es pot suprimir la ruta d'importació",
|
||||
"unable_to_delete_shared_link": "No es pot suprimir l'enllaç compartit",
|
||||
"unable_to_delete_user": "No es pot eliminar l'usuari",
|
||||
"unable_to_download_files": "No es poden descarregar fitxers",
|
||||
"unable_to_edit_exclusion_pattern": "No es pot editar el patró d'exclusió",
|
||||
"unable_to_edit_import_path": "No es pot editar la ruta d'importació",
|
||||
"unable_to_empty_trash": "No es pot buidar la paperera",
|
||||
"unable_to_enter_fullscreen": "No es pot entrar a la pantalla completa",
|
||||
"unable_to_exit_fullscreen": "No es pot sortir de la pantalla completa",
|
||||
@@ -1076,7 +1032,6 @@
|
||||
"unable_to_update_user": "No es pot actualitzar l'usuari",
|
||||
"unable_to_upload_file": "No es pot carregar el fitxer"
|
||||
},
|
||||
"exclusion_pattern": "Patró d'exclusió",
|
||||
"exif": "EXIF",
|
||||
"exif_bottom_sheet_description": "Afegeix descripció...",
|
||||
"exif_bottom_sheet_description_error": "No s'ha pogut actualitzar la descripció",
|
||||
@@ -1107,7 +1062,6 @@
|
||||
"external_network_sheet_info": "Quan no estigui a la xarxa Wi-Fi preferida, l'aplicació es connectarà al servidor mitjançant el primer dels URL següents a què pot arribar, començant de dalt a baix",
|
||||
"face_unassigned": "Sense assignar",
|
||||
"failed": "Fallat",
|
||||
"failed_count": "Fallits: {count}",
|
||||
"failed_to_authenticate": "No s'ha pogut autenticar",
|
||||
"failed_to_load_assets": "Error carregant recursos",
|
||||
"failed_to_load_folder": "No s'ha pogut carregar la carpeta",
|
||||
@@ -1122,7 +1076,6 @@
|
||||
"features_setting_description": "Administrar les funcions de l'aplicació",
|
||||
"file_name": "Nom de l'arxiu",
|
||||
"file_name_or_extension": "Nom de l'arxiu o extensió",
|
||||
"file_size": "Mida del fitxer",
|
||||
"filename": "Nom del fitxer",
|
||||
"filetype": "Tipus d'arxiu",
|
||||
"filter": "Filtrar",
|
||||
@@ -1137,7 +1090,6 @@
|
||||
"folders_feature_description": "Explorar la vista de carpetes per les fotos i vídeos del sistema d'arxius",
|
||||
"forgot_pin_code_question": "Has oblidat el teu PIN?",
|
||||
"forward": "Endavant",
|
||||
"full_path": "Ruta completa: {path}",
|
||||
"gcast_enabled": "Google Cast",
|
||||
"gcast_enabled_description": "Aquesta funció carrega recursos externs de Google per funcionar.",
|
||||
"general": "General",
|
||||
@@ -1168,14 +1120,12 @@
|
||||
"header_settings_header_name_input": "Nom de la capçalera",
|
||||
"header_settings_header_value_input": "Valor de la capçalera",
|
||||
"headers_settings_tile_title": "Capçaleres proxy personalitzades",
|
||||
"height": "Alçada",
|
||||
"hi_user": "Hola {name} ({email})",
|
||||
"hide_all_people": "Amaga totes les persones",
|
||||
"hide_gallery": "Amaga la galeria",
|
||||
"hide_named_person": "Amaga la persona {name}",
|
||||
"hide_password": "Amaga la contrasenya",
|
||||
"hide_person": "Amaga la persona",
|
||||
"hide_text_recognition": "Oculta el reconeixement de text",
|
||||
"hide_unnamed_people": "Amaga persones sense nom",
|
||||
"home_page_add_to_album_conflicts": "S'han afegit {added} elements a l'àlbum {album}. {failed} elements ja existeixen a l'àlbum.",
|
||||
"home_page_add_to_album_err_local": "Encara no es poden afegir elements locals als àlbums, ometent",
|
||||
@@ -1221,8 +1171,6 @@
|
||||
"import_path": "Ruta d'importació",
|
||||
"in_albums": "A {count, plural, one {# àlbum} other {# àlbums}}",
|
||||
"in_archive": "En arxiu",
|
||||
"in_year": "En {year}",
|
||||
"in_year_selector": "En",
|
||||
"include_archived": "Incloure arxivats",
|
||||
"include_shared_albums": "Inclou àlbums compartits",
|
||||
"include_shared_partner_assets": "Incloure elements dels companys",
|
||||
@@ -1259,7 +1207,6 @@
|
||||
"language_setting_description": "Seleccioneu el vostre idioma",
|
||||
"large_files": "Fitxers Grans",
|
||||
"last": "Últim",
|
||||
"last_months": "{count, plural, one {Últim mes} other {Últims # mesos}}",
|
||||
"last_seen": "Vist per últim cop",
|
||||
"latest_version": "Última versió",
|
||||
"latitude": "Latitud",
|
||||
@@ -1269,8 +1216,6 @@
|
||||
"let_others_respond": "Deixa que els altres responguin",
|
||||
"level": "Nivell",
|
||||
"library": "Bibilioteca",
|
||||
"library_add_folder": "Afegir carpeta",
|
||||
"library_edit_folder": "Editar carpeta",
|
||||
"library_options": "Opcions de biblioteca",
|
||||
"library_page_device_albums": "Àlbums al Dispositiu",
|
||||
"library_page_new_album": "Nou àlbum",
|
||||
@@ -1291,11 +1236,9 @@
|
||||
"local": "Local",
|
||||
"local_asset_cast_failed": "No es pot convertir un actiu que no s'ha penjat al servidor",
|
||||
"local_assets": "Recursos Locals",
|
||||
"local_id": "ID local",
|
||||
"local_media_summary": "Resum de Mitjans Locals",
|
||||
"local_network": "Xarxa local",
|
||||
"local_network_sheet_info": "L'aplicació es connectarà al servidor mitjançant aquest URL quan utilitzeu la xarxa Wi-Fi especificada",
|
||||
"location": "Localització",
|
||||
"location_permission": "Permís d'ubicació",
|
||||
"location_permission_content": "Per utilitzar la funció de canvi automàtic, Immich necessita un permís d'ubicació precisa perquè pugui llegir el nom de la xarxa Wi-Fi actual",
|
||||
"location_picker_choose_on_map": "Escollir en el mapa",
|
||||
@@ -1343,17 +1286,8 @@
|
||||
"loop_videos_description": "Habilita la reproducció en bucle del vídeo en els detalls.",
|
||||
"main_branch_warning": "Esteu utilitzant una versió en desenvolupament; Recomanem fer servir una versió publicada!",
|
||||
"main_menu": "Menú principal",
|
||||
"maintenance_description": "Immich ha estat posat en <link>mode de manteniment</link>.",
|
||||
"maintenance_end": "Finalitzar el mode de manteniment",
|
||||
"maintenance_end_error": "Error al finalitzar el mode de manteniment.",
|
||||
"maintenance_logged_in_as": "Actualment la sessió esta iniciada per {user}",
|
||||
"maintenance_title": "Temporalment inaccessible",
|
||||
"make": "Fabricant",
|
||||
"manage_geolocation": "Gestioneu la vostra ubicació",
|
||||
"manage_media_access_rationale": "Aquest permís es necessari per a la correcta gestió dels actius que es mouen a la paperera i es restauren d'ella.",
|
||||
"manage_media_access_settings": "Configuració oberta",
|
||||
"manage_media_access_subtitle": "Permet a l'Immich gestionar i moure fitxers multimèdia.",
|
||||
"manage_media_access_title": "Accés a la gestió de mitjans",
|
||||
"manage_shared_links": "Administrar enllaços compartits",
|
||||
"manage_sharing_with_partners": "Gestiona la compartició amb els companys",
|
||||
"manage_the_app_settings": "Gestioneu la configuració de l'aplicació",
|
||||
@@ -1410,14 +1344,12 @@
|
||||
"minutes": "Minuts",
|
||||
"missing": "Restants",
|
||||
"mobile_app": "Aplicació mòbil",
|
||||
"mobile_app_download_onboarding_note": "Descarregar la App de mòbil fent servir les seguents opcions",
|
||||
"model": "Model",
|
||||
"month": "Mes",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"more": "Més",
|
||||
"move": "Moure",
|
||||
"move_off_locked_folder": "Moure fora de la carpeta bloquejada",
|
||||
"move_to": "Moure a",
|
||||
"move_to_lock_folder_action_prompt": "{count} afegides a la carpeta protegida",
|
||||
"move_to_locked_folder": "Moure a la carpeta bloquejada",
|
||||
"move_to_locked_folder_confirmation": "Aquestes fotos i vídeos seran eliminades de tots els àlbums, i només podran ser vistes des de la carpeta bloquejada",
|
||||
@@ -1431,7 +1363,6 @@
|
||||
"name": "Nom",
|
||||
"name_or_nickname": "Nom o sobrenom",
|
||||
"navigate": "Navegar",
|
||||
"navigate_to_time": "Navegar a un punt en el temps",
|
||||
"network_requirement_photos_upload": "Fes servir dades mòbils per a còpies de seguretat de fotos",
|
||||
"network_requirement_videos_upload": "Fes servir dades mòbils per a còpies de seguretat de videos",
|
||||
"network_requirements": "Requeriments de Xarxa",
|
||||
@@ -1441,13 +1372,11 @@
|
||||
"never": "Mai",
|
||||
"new_album": "Nou Àlbum",
|
||||
"new_api_key": "Nova clau de l'API",
|
||||
"new_date_range": "Navegar a un reng de dates",
|
||||
"new_password": "Nova contrasenya",
|
||||
"new_person": "Persona nova",
|
||||
"new_pin_code": "Nou codi PIN",
|
||||
"new_pin_code_subtitle": "Aquesta és la primera vegada que accedeixes a la carpeta bloquejada. Crea una codi PIN i accedeix de manera segura a aquesta pàgina",
|
||||
"new_timeline": "Nova Línia de Temps",
|
||||
"new_update": "Nova actualització",
|
||||
"new_user_created": "Nou usuari creat",
|
||||
"new_version_available": "NOVA VERSIÓ DISPONIBLE",
|
||||
"newest_first": "El més nou primer",
|
||||
@@ -1463,14 +1392,12 @@
|
||||
"no_cast_devices_found": "No s'han trobat dispositius per transmetre",
|
||||
"no_checksum_local": "Cap checksum disponible - no s'han pogut carregar els recursos locals",
|
||||
"no_checksum_remote": "Cap checksum disponible - no s'ha pogut obtenir el recurs remot",
|
||||
"no_devices": "No hi ha dispositius autoritzats",
|
||||
"no_duplicates_found": "No s'han trobat duplicats.",
|
||||
"no_exif_info_available": "No hi ha informació d'exif disponible",
|
||||
"no_explore_results_message": "Penja més fotos per explorar la teva col·lecció.",
|
||||
"no_favorites_message": "Afegiu preferits per trobar les millors fotos i vídeos a l'instant",
|
||||
"no_libraries_message": "Creeu una llibreria externa per veure les vostres fotos i vídeos",
|
||||
"no_local_assets_found": "No s'ha trobat cap recurs local amb aquest checksum",
|
||||
"no_location_set": "No s'ha definit cap ubicació",
|
||||
"no_locked_photos_message": "Les fotos i vídeos d'aquesta carpeta estan ocultes, i no es mostraran a mesura que navegues o cerques a la teva biblioteca.",
|
||||
"no_name": "Sense nom",
|
||||
"no_notifications": "No hi ha notificacions",
|
||||
@@ -1481,7 +1408,6 @@
|
||||
"no_results_description": "Proveu un sinònim o una paraula clau més general",
|
||||
"no_shared_albums_message": "Creeu un àlbum per compartir fotos i vídeos amb persones a la vostra xarxa",
|
||||
"no_uploads_in_progress": "Cap pujada en progrés",
|
||||
"not_allowed": "No permès",
|
||||
"not_available": "N/A",
|
||||
"not_in_any_album": "En cap àlbum",
|
||||
"not_selected": "No seleccionat",
|
||||
@@ -1496,9 +1422,6 @@
|
||||
"notifications": "Notificacions",
|
||||
"notifications_setting_description": "Gestiona les notificacions",
|
||||
"oauth": "OAuth",
|
||||
"obtainium_configurator": "Configurador Obtainium",
|
||||
"obtainium_configurator_instructions": "Utilitza Obtainium per instal·lar una actualització a la app directament des de Github-Immich. Crear una clau API i seleccionar una variant per crear un enllaç a la configuració Obtainium",
|
||||
"ocr": "OCR",
|
||||
"official_immich_resources": "Recursos oficials d'Immich",
|
||||
"offline": "Fora de línia",
|
||||
"offset": "Diferència",
|
||||
@@ -1530,7 +1453,6 @@
|
||||
"other_variables": "Altres variables",
|
||||
"owned": "Propi",
|
||||
"owner": "Propietari",
|
||||
"page": "Pàgina",
|
||||
"partner": "Company/a",
|
||||
"partner_can_access": "{partner} hi té accés",
|
||||
"partner_can_access_assets": "Totes les vostres fotos i vídeos excepte les arxivades i eliminades",
|
||||
@@ -1593,8 +1515,6 @@
|
||||
"photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}",
|
||||
"photos_from_previous_years": "Fotos d'anys anteriors",
|
||||
"pick_a_location": "Triar una ubicació",
|
||||
"pick_custom_range": "Rang personalitzat",
|
||||
"pick_date_range": "Seleccioni un rang de dates",
|
||||
"pin_code_changed_successfully": "Codi PIN canviat correctament",
|
||||
"pin_code_reset_successfully": "S'ha restablert correctament el codi PIN",
|
||||
"pin_code_setup_successfully": "S'ha configurat correctament un codi PIN",
|
||||
@@ -1606,9 +1526,6 @@
|
||||
"play_memories": "Reproduir records",
|
||||
"play_motion_photo": "Reproduir Fotos en Moviment",
|
||||
"play_or_pause_video": "Reproduir o posar en pausa el vídeo",
|
||||
"play_original_video": "Veure el video original",
|
||||
"play_original_video_setting_description": "Preferir la reproducció del video original sobre el video recodificat. Si el video original no es compatible potser no es reprodueixi correctament.",
|
||||
"play_transcoded_video": "Veure el video recodificat",
|
||||
"please_auth_to_access": "Per favor, autentica't per accedir",
|
||||
"port": "Port",
|
||||
"preferences_settings_subtitle": "Gestiona les preferències de l'aplicació",
|
||||
@@ -1745,7 +1662,6 @@
|
||||
"reset_sqlite_confirmation": "Segur que vols reiniciar la base de dades SQLite? Hauràs de tancar la sessió i tornar a accedir per a resincronitzar les dades",
|
||||
"reset_sqlite_success": "S'ha reiniciat la base de dades correctament",
|
||||
"reset_to_default": "Restableix els valors predeterminats",
|
||||
"resolution": "Resolució",
|
||||
"resolve_duplicates": "Resoldre duplicats",
|
||||
"resolved_all_duplicates": "Tots els duplicats resolts",
|
||||
"restore": "Recupera",
|
||||
@@ -1764,7 +1680,6 @@
|
||||
"running": "En execució",
|
||||
"save": "Desa",
|
||||
"save_to_gallery": "Desa a galeria",
|
||||
"saved": "Guardat",
|
||||
"saved_api_key": "Clau d'API guardada",
|
||||
"saved_profile": "Perfil guardat",
|
||||
"saved_settings": "Configuració guardada",
|
||||
@@ -1781,9 +1696,6 @@
|
||||
"search_by_description_example": "Jornada de senderisme a Sapa",
|
||||
"search_by_filename": "Cerca per nom de fitxer o extensió",
|
||||
"search_by_filename_example": "per exemple IMG_1234.JPG o PNG",
|
||||
"search_by_ocr": "Buscar per OCR",
|
||||
"search_by_ocr_example": "Després",
|
||||
"search_camera_lens_model": "Buscar model de lents....",
|
||||
"search_camera_make": "Buscar per fabricant de càmara...",
|
||||
"search_camera_model": "Buscar per model de càmera...",
|
||||
"search_city": "Buscar per ciutat...",
|
||||
@@ -1800,7 +1712,6 @@
|
||||
"search_filter_location_title": "Selecciona l'ubicació",
|
||||
"search_filter_media_type": "Tipus de multimèdia",
|
||||
"search_filter_media_type_title": "Selecciona tipus de multimèdia",
|
||||
"search_filter_ocr": "Buscar per OCR",
|
||||
"search_filter_people_title": "Selecciona persones",
|
||||
"search_for": "Cercar",
|
||||
"search_for_existing_person": "Busca una persona existent",
|
||||
@@ -1862,8 +1773,6 @@
|
||||
"server_offline": "Servidor fora de línia",
|
||||
"server_online": "Servidor en línia",
|
||||
"server_privacy": "Privadesa del servidor",
|
||||
"server_restarting_description": "Aquesta pàgina es refrescarà momentàniament.",
|
||||
"server_restarting_title": "El servidor s'està reiniciant",
|
||||
"server_stats": "Estadístiques del servidor",
|
||||
"server_update_available": "Actualització del servidor disponible",
|
||||
"server_version": "Versió del servidor",
|
||||
@@ -1987,7 +1896,6 @@
|
||||
"show_slideshow_transition": "Mostra la transició de la presentació de diapositives",
|
||||
"show_supporter_badge": "Insígnia de contribuent",
|
||||
"show_supporter_badge_description": "Mostra una insígnia de contributor",
|
||||
"show_text_recognition": "Mostra el reconeixement de text",
|
||||
"show_text_search_menu": "Mostra el menú de cerca amb text",
|
||||
"shuffle": "Mescla",
|
||||
"sidebar": "Barra lateral",
|
||||
@@ -2058,7 +1966,6 @@
|
||||
"tags": "Etiquetes",
|
||||
"tap_to_run_job": "Toca per executar el treball",
|
||||
"template": "Plantilla",
|
||||
"text_recognition": "Reconeixement de text",
|
||||
"theme": "Tema",
|
||||
"theme_selection": "Selecció de tema",
|
||||
"theme_selection_description": "Activa automàticament el tema fosc o clar en funció de les preferències del sistema del navegador",
|
||||
@@ -2077,9 +1984,7 @@
|
||||
"theme_setting_three_stage_loading_title": "Activa la càrrega en tres etapes",
|
||||
"they_will_be_merged_together": "Es combinaran",
|
||||
"third_party_resources": "Recursos de tercers",
|
||||
"time": "Temps",
|
||||
"time_based_memories": "Records basats en el temps",
|
||||
"time_based_memories_duration": "Quants segons es mostrarà cada imatge.",
|
||||
"timeline": "Cronologia",
|
||||
"timezone": "Fus horari",
|
||||
"to_archive": "Arxivar",
|
||||
@@ -2091,7 +1996,6 @@
|
||||
"to_select": "per seleccionar",
|
||||
"to_trash": "Paperera",
|
||||
"toggle_settings": "Canvia configuració",
|
||||
"toggle_theme_description": "Commuta el tema",
|
||||
"total": "Total",
|
||||
"total_usage": "Ús total",
|
||||
"trash": "Paperera",
|
||||
@@ -2200,7 +2104,6 @@
|
||||
"view_album": "Veure l'àlbum",
|
||||
"view_all": "Veure tot",
|
||||
"view_all_users": "Mostra tot els usuaris",
|
||||
"view_asset_owners": "Veure els propietaris dels actius",
|
||||
"view_details": "Veure Detalls",
|
||||
"view_in_timeline": "Mostrar a la línia de temps",
|
||||
"view_link": "Veure enllaç",
|
||||
@@ -2217,14 +2120,11 @@
|
||||
"viewer_unstack": "Desapila",
|
||||
"visibility_changed": "La visibilitat ha canviat per {count, plural, one {# persona} other {# persones}}",
|
||||
"waiting": "Esperant",
|
||||
"waiting_count": "Esperant: {count}",
|
||||
"warning": "Avís",
|
||||
"week": "Setmana",
|
||||
"welcome": "Benvingut",
|
||||
"welcome_to_immich": "Benvingut a immich",
|
||||
"width": "Amplada",
|
||||
"wifi_name": "Nom Wi-Fi",
|
||||
"workflow": "Flux de treball",
|
||||
"wrong_pin_code": "Codi PIN incorrecte",
|
||||
"year": "Any",
|
||||
"years_ago": "Fa {years, plural, one {# any} other {# anys}}",
|
||||
|
||||
86
i18n/cs.json
86
i18n/cs.json
@@ -7,7 +7,6 @@
|
||||
"action_common_update": "Aktualizovat",
|
||||
"actions": "Akce",
|
||||
"active": "Aktivní",
|
||||
"active_count": "Aktivní: {count}",
|
||||
"activity": "Aktivita",
|
||||
"activity_changed": "Aktivita je {enabled, select, true {povolena} other {zakázána}}",
|
||||
"add": "Přidat",
|
||||
@@ -18,6 +17,7 @@
|
||||
"add_birthday": "Přidat datum narození",
|
||||
"add_endpoint": "Přidat koncový bod",
|
||||
"add_exclusion_pattern": "Přidat vzor vyloučení",
|
||||
"add_import_path": "Přidat cestu importu",
|
||||
"add_location": "Přidat polohu",
|
||||
"add_more_users": "Přidat další uživatele",
|
||||
"add_partner": "Přidat partnera",
|
||||
@@ -32,7 +32,6 @@
|
||||
"add_to_album_toggle": "Přepnout výběr pro {album}",
|
||||
"add_to_albums": "Přidat do alb",
|
||||
"add_to_albums_count": "Přidat do alb ({count})",
|
||||
"add_to_bottom_bar": "Přidat do",
|
||||
"add_to_shared_album": "Přidat do sdíleného alba",
|
||||
"add_upload_to_stack": "Přidat nahrané do zásobníku",
|
||||
"add_url": "Přidat URL",
|
||||
@@ -68,7 +67,6 @@
|
||||
"confirm_reprocess_all_faces": "Opravdu chcete znovu zpracovat všechny obličeje? Tím se vymažou i pojmenované osoby.",
|
||||
"confirm_user_password_reset": "Opravdu chcete obnovit heslo uživatele {user}?",
|
||||
"confirm_user_pin_code_reset": "Opravdu chcete resetovat PIN kód uživatele {user}?",
|
||||
"copy_config_to_clipboard_description": "Zkopírujte aktuální konfiguraci systému jako JSON objekt do schránky",
|
||||
"create_job": "Vytvořit úlohu",
|
||||
"cron_expression": "Výraz cron",
|
||||
"cron_expression_description": "Nastavte interval prohledávání pomocí cron formátu. Další informace naleznete např. v <link>Crontab Guru</link>",
|
||||
@@ -76,8 +74,7 @@
|
||||
"disable_login": "Zakázat přihlášení",
|
||||
"duplicate_detection_job_description": "Spuštění strojového učení na položkách za účelem detekce podobných obrázků. Spoléhá na Chytré vyhledávání",
|
||||
"exclusion_pattern_description": "Vzory vyloučení umožňují při prohledávání knihovny ignorovat soubory a složky. To je užitečné, pokud máte složky obsahující soubory, které nechcete importovat, například RAW soubory.",
|
||||
"export_config_as_json_description": "Stáhněte si aktuální konfiguraci systému jako JSON soubor",
|
||||
"external_libraries_page_description": "Stránka externí knihovny správce",
|
||||
"external_library_management": "Správa externích knihoven",
|
||||
"face_detection": "Detekce obličejů",
|
||||
"face_detection_description": "Detekce obličejů v obrázcích pomocí strojového učení. U videí se bere v úvahu pouze miniatura. „Obnovit“ znovu zpracuje všechny položky. „Resetovat“ navíc vymaže všechna aktuální data obličejů. „Chybějící“ zařadí do fronty položky, které ještě nebyly zpracovány. Zjištěné obličeje budou po dokončení funkce Rozpoznávání obličejů zařazeny do fronty a seskupeny do stávajících nebo nových osob.",
|
||||
"facial_recognition_job_description": "Seskupí nalezené obličeje do osob. Tento krok se spustí po dokončení detekce obličejů. „Resetovat“ znovu seskupí všechny obličeje. „Chybějící“ zpracuje obličeje, které nemají přiřazenou osobu.",
|
||||
@@ -105,28 +102,23 @@
|
||||
"image_thumbnail_description": "Malá miniatura s odstraněnými metadaty, který se používá při prohlížení skupin fotografií, jako je hlavní časová osa",
|
||||
"image_thumbnail_quality_description": "Kvalita miniatur od 1 do 100. Vyšší je lepší, ale vytváří větší soubory a může snížit odezvu aplikace.",
|
||||
"image_thumbnail_title": "Miniatury",
|
||||
"import_config_from_json_description": "Importujte konfiguraci systému nahráním konfiguračního JSON souboru",
|
||||
"job_concurrency": "Souběžnost úlohy {job}",
|
||||
"job_created": "Úloha vytvořena",
|
||||
"job_not_concurrency_safe": "Tato úloha není bezpečená pro souběh.",
|
||||
"job_settings": "Úlohy",
|
||||
"job_settings_description": "Správa souběžnosti úloh",
|
||||
"job_status": "Stav úloh",
|
||||
"jobs_delayed": "{jobCount, plural, one {# zpožděný} few {# zpožděné} other {# zpožděných}}",
|
||||
"jobs_failed": "{jobCount, plural, one {# neúspěšný} few {# neúspěšné} other {# neúspěšných}}",
|
||||
"jobs_over_time": "Úlohy v průběhu času",
|
||||
"library_created": "Vytvořena knihovna: {library}",
|
||||
"library_deleted": "Knihovna smazána",
|
||||
"library_details": "Podrobnosti o knihovně",
|
||||
"library_folder_description": "Zadejte složku, kterou chcete importovat. Tato složka, včetně podsložek, bude prohledána pro obrázky a videa.",
|
||||
"library_remove_exclusion_pattern_prompt": "Opravdu chcete odstranit tento vzor vyloučení?",
|
||||
"library_remove_folder_prompt": "Opravdu chcete odstranit tuto složku importu?",
|
||||
"library_import_path_description": "Zadejte složku, kterou chcete importovat. Tato složka bude prohledána včetně podsložek a budou v ní hledány obrázky a videa.",
|
||||
"library_scanning": "Pravidelné prohledávání",
|
||||
"library_scanning_description": "Nastavení pravidelného prohledávání knihovny",
|
||||
"library_scanning_enable_description": "Povolit pravidelné prohledávání knihovny",
|
||||
"library_settings": "Externí knihovna",
|
||||
"library_settings_description": "Správa nastavení externí knihovny",
|
||||
"library_tasks_description": "Vyhledávání nových nebo změněných položek v externích knihovnách",
|
||||
"library_updated": "Knihovna aktualizována",
|
||||
"library_watching_enable_description": "Sledovat změny souborů v externích knihovnách",
|
||||
"library_watching_settings": "Sledování knihovny [EXPERIMENTÁLNÍ]",
|
||||
"library_watching_settings_description": "Automatické sledování změněných souborů",
|
||||
@@ -181,12 +173,7 @@
|
||||
"machine_learning_smart_search_enabled": "Povolit chytré vyhledávání",
|
||||
"machine_learning_smart_search_enabled_description": "Pokud je vypnuto, obrázky nebudou kódovány pro inteligentní vyhledávání.",
|
||||
"machine_learning_url_description": "URL serveru strojového učení. Pokud je zadáno více URL adres, budou jednotlivé servery zkoušeny postupně, dokud jeden z nich neodpoví úspěšně, a to v pořadí od prvního k poslednímu. Servery, které neodpoví, budou dočasně ignorovány, dokud nebudou opět online.",
|
||||
"maintenance_settings": "Údržba",
|
||||
"maintenance_settings_description": "Přepnout Immich do režimu údržby.",
|
||||
"maintenance_start": "Zahájit režim údržby",
|
||||
"maintenance_start_error": "Nepodařilo se zahájit režim údržby.",
|
||||
"manage_concurrency": "Správa souběžnosti",
|
||||
"manage_concurrency_description": "Přejděte na stránku úloh a spravujte souběžnost úloh",
|
||||
"manage_log_settings": "Správa nastavení protokolu",
|
||||
"map_dark_style": "Tmavý motiv",
|
||||
"map_enable_description": "Povolit funkce mapy",
|
||||
@@ -276,14 +263,10 @@
|
||||
"password_settings_description": "Správa nastavení přihlašování pomocí hesla",
|
||||
"paths_validated_successfully": "Všechny cesty byly úspěšně ověřeny",
|
||||
"person_cleanup_job": "Promazání osob",
|
||||
"queue_details": "Podrobnosti o frontě",
|
||||
"queues": "Fronty úloh",
|
||||
"queues_page_description": "Stránka fronty úloh správce",
|
||||
"quota_size_gib": "Velikost kvóty (GiB)",
|
||||
"refreshing_all_libraries": "Obnovení všech knihoven",
|
||||
"registration": "Registrace správce",
|
||||
"registration_description": "Vzhledem k tomu, že jste prvním uživatelem v systému, budete přiřazen jako správce a budete zodpovědný za úkoly správy a další uživatelé budou vytvořeni vámi.",
|
||||
"remove_failed_jobs": "Odebrat neúspěšné úlohy",
|
||||
"require_password_change_on_login": "Požadovat, aby si uživatel při prvním přihlášení změnil heslo",
|
||||
"reset_settings_to_default": "Obnovení výchozího nastavení",
|
||||
"reset_settings_to_recent_saved": "Obnovit poslední uložené nastavení",
|
||||
@@ -296,10 +279,8 @@
|
||||
"server_public_users_description": "Všichni uživatelé (jméno a e-mail) jsou uvedeni při přidávání uživatele do sdílených alb. Pokud je tato funkce vypnuta, bude seznam uživatelů dostupný pouze uživatelům z řad správců.",
|
||||
"server_settings": "Server",
|
||||
"server_settings_description": "Správa nastavení serveru",
|
||||
"server_stats_page_description": "Stránka statistik administrátorského serveru",
|
||||
"server_welcome_message": "Uvítací zpráva",
|
||||
"server_welcome_message_description": "Zpráva, která se zobrazí na přihlašovací stránce.",
|
||||
"settings_page_description": "Stránka nastavení administrátora",
|
||||
"sidecar_job": "Postranní metadata",
|
||||
"sidecar_job_description": "Objevování nebo synchronizace sidecar metadat ze systému souborů",
|
||||
"slideshow_duration_description": "Počet sekund pro zobrazení každého obrázku",
|
||||
@@ -401,9 +382,9 @@
|
||||
"trash_number_of_days_description": "Počet dní, po které je třeba položku ponechat v koši, než bude trvale odstraněna",
|
||||
"trash_settings": "Koš",
|
||||
"trash_settings_description": "Správa nastavení koše",
|
||||
"unlink_all_oauth_accounts": "Odpojit všechny OAuth účty",
|
||||
"unlink_all_oauth_accounts": "Odpojit všechny účty OAuth",
|
||||
"unlink_all_oauth_accounts_description": "Nezapomeňte odpojit všechny OAuth účty před přechodem k novému poskytovateli.",
|
||||
"unlink_all_oauth_accounts_prompt": "Opravdu chcete odpojit všechny OAuth účty? Tím se resetuje OAuth ID pro každého uživatele a tento úkon nelze vrátit zpět.",
|
||||
"unlink_all_oauth_accounts_prompt": "Opravdu chcete odpojit všechny účty OAuth? Tím se resetuje ID OAuth pro každého uživatele a tento úkon nelze vrátit zpět.",
|
||||
"user_cleanup_job": "Promazání uživatelů",
|
||||
"user_delete_delay": "Účet a položky uživatele <b>{user}</b> budou trvale smazány za {delay, plural, one {# den} few {# dny} other {# dní}}.",
|
||||
"user_delete_delay_settings": "Odložení odstranění",
|
||||
@@ -419,7 +400,6 @@
|
||||
"user_settings": "Uživatelé",
|
||||
"user_settings_description": "Správa nastavení uživatelů",
|
||||
"user_successfully_removed": "Uživatel {email} byl úspěšně odstraněn.",
|
||||
"users_page_description": "Stránka správců",
|
||||
"version_check_enabled_description": "Povolit kontrolu verzí",
|
||||
"version_check_implications": "Kontrola verze je založena na pravidelné komunikaci s github.com",
|
||||
"version_check_settings": "Kontrola verze",
|
||||
@@ -450,7 +430,6 @@
|
||||
"age_months": "{months, plural, one {# měsíc} few {# měsíce} other {# měsíců}}",
|
||||
"age_year_months": "1 rok a {months, plural, one {# měsíc} few {# měsíce} other {# měsíců}}",
|
||||
"age_years": "{years, plural, one {# rok} few {# roky} other {# let}}",
|
||||
"album": "Album",
|
||||
"album_added": "Přidáno album",
|
||||
"album_added_notification_setting_description": "Dostávat e-mailové oznámení, když jste přidáni do sdíleného alba",
|
||||
"album_cover_updated": "Obal alba aktualizován",
|
||||
@@ -496,7 +475,6 @@
|
||||
"allow_edits": "Povolit úpravy",
|
||||
"allow_public_user_to_download": "Povolit veřejnosti stahovat",
|
||||
"allow_public_user_to_upload": "Povolit veřejnosti nahrávat",
|
||||
"allowed": "Povoleno",
|
||||
"alt_text_qr_code": "Obrázek QR kódu",
|
||||
"anti_clockwise": "Proti směru hodinových ručiček",
|
||||
"api_key": "API klíč",
|
||||
@@ -652,7 +630,6 @@
|
||||
"backup_options_page_title": "Nastavení záloh",
|
||||
"backup_setting_subtitle": "Správa nastavení zálohování na pozadí a na popředí",
|
||||
"backup_settings_subtitle": "Správa nastavení nahrávání",
|
||||
"backup_upload_details_page_more_details": "Klepněte pro více informací",
|
||||
"backward": "Pozpátku",
|
||||
"biometric_auth_enabled": "Biometrické ověřování je povoleno",
|
||||
"biometric_locked_out": "Jste vyloučeni z biometrického ověřování",
|
||||
@@ -719,7 +696,6 @@
|
||||
"check_corrupt_asset_backup_button": "Provést kontrolu",
|
||||
"check_corrupt_asset_backup_description": "Tuto kontrolu provádějte pouze přes Wi-Fi a po zálohování všech prostředků. Takto operace může trvat několik minut.",
|
||||
"check_logs": "Zkontrolujte protokoly",
|
||||
"checksum": "Kontrolní součet",
|
||||
"choose_matching_people_to_merge": "Zvolte odpovídající osoby ke sloučení",
|
||||
"city": "Město",
|
||||
"clear": "Vymazat",
|
||||
@@ -742,7 +718,6 @@
|
||||
"collapse_all": "Sbalit vše",
|
||||
"color": "Barva",
|
||||
"color_theme": "Barevný motiv",
|
||||
"command": "Příkaz",
|
||||
"comment_deleted": "Komentář odstraněn",
|
||||
"comment_options": "Možnosti komentáře",
|
||||
"comments_and_likes": "Komentáře a lajky",
|
||||
@@ -919,6 +894,8 @@
|
||||
"edit_description_prompt": "Vyberte nový popis:",
|
||||
"edit_exclusion_pattern": "Upravit vzor vyloučení",
|
||||
"edit_faces": "Upravit obličeje",
|
||||
"edit_import_path": "Upravit cestu importu",
|
||||
"edit_import_paths": "Úpravit importní cesty",
|
||||
"edit_key": "Upravit klíč",
|
||||
"edit_link": "Upravit odkaz",
|
||||
"edit_location": "Upravit polohu",
|
||||
@@ -990,8 +967,8 @@
|
||||
"failed_to_stack_assets": "Nepodařilo se seskupit položky",
|
||||
"failed_to_unstack_assets": "Nepodařilo se zrušit seskupení položek",
|
||||
"failed_to_update_notification_status": "Nepodařilo se aktualizovat stav oznámení",
|
||||
"import_path_already_exists": "Tato cesta importu již existuje.",
|
||||
"incorrect_email_or_password": "Nesprávný e-mail nebo heslo",
|
||||
"library_folder_already_exists": "Tato importní cesta již existuje.",
|
||||
"paths_validation_failed": "{paths, plural, one {# cesta neprošla} few {# cesty neprošly} other {# cest neprošlo}} kontrolou",
|
||||
"profile_picture_transparent_pixels": "Profilové obrázky nemohou mít průhledné pixely. Obrázek si prosím zvětšete nebo posuňte.",
|
||||
"quota_higher_than_disk_size": "Nastavili jste kvótu vyšší, než je velikost disku",
|
||||
@@ -1000,6 +977,7 @@
|
||||
"unable_to_add_assets_to_shared_link": "Nelze přidat položky do sdíleného odkazu",
|
||||
"unable_to_add_comment": "Nelze přidat komentář",
|
||||
"unable_to_add_exclusion_pattern": "Nelze přidat vzor vyloučení",
|
||||
"unable_to_add_import_path": "Nelze přidat cestu importu",
|
||||
"unable_to_add_partners": "Nelze přidat partnery",
|
||||
"unable_to_add_remove_archive": "Nelze {archived, select, true {odstranit položku z} other {přidat položku do}} archivu",
|
||||
"unable_to_add_remove_favorites": "Nelze {favorite, select, true {oblíbit položku} other {zrušit oblíbení položky}}",
|
||||
@@ -1022,10 +1000,12 @@
|
||||
"unable_to_delete_asset": "Nelze odstranit položku",
|
||||
"unable_to_delete_assets": "Chyba při odstraňování položek",
|
||||
"unable_to_delete_exclusion_pattern": "Nelze odstranit vzor vyloučení",
|
||||
"unable_to_delete_import_path": "Nelze odstranit cestu importu",
|
||||
"unable_to_delete_shared_link": "Nepodařilo se odstranit sdílený odkaz",
|
||||
"unable_to_delete_user": "Nelze odstranit uživatele",
|
||||
"unable_to_download_files": "Nelze stáhnout soubory",
|
||||
"unable_to_edit_exclusion_pattern": "Nelze upravit vzor vyloučení",
|
||||
"unable_to_edit_import_path": "Nelze upravit cestu importu",
|
||||
"unable_to_empty_trash": "Nelze vyprázdnit koš",
|
||||
"unable_to_enter_fullscreen": "Nelze přejít do režimu celé obrazovky",
|
||||
"unable_to_exit_fullscreen": "Nelze ukončit zobrazení na celou obrazovku",
|
||||
@@ -1076,7 +1056,6 @@
|
||||
"unable_to_update_user": "Nelze aktualizovat uživatele",
|
||||
"unable_to_upload_file": "Nepodařilo se nahrát soubor"
|
||||
},
|
||||
"exclusion_pattern": "Vzor vyloučení",
|
||||
"exif": "Exif",
|
||||
"exif_bottom_sheet_description": "Přidat popis...",
|
||||
"exif_bottom_sheet_description_error": "Chyba při aktualizaci popisu",
|
||||
@@ -1107,7 +1086,6 @@
|
||||
"external_network_sheet_info": "Pokud nejste v preferované síti Wi-Fi, aplikace se připojí k serveru prostřednictvím první z níže uvedených adres URL, které může dosáhnout, počínaje shora dolů",
|
||||
"face_unassigned": "Nepřiřazena",
|
||||
"failed": "Selhalo",
|
||||
"failed_count": "Selhalo: {count}",
|
||||
"failed_to_authenticate": "Ověření se nezdařilo",
|
||||
"failed_to_load_assets": "Nepodařilo se načíst položky",
|
||||
"failed_to_load_folder": "Nepodařilo se načíst složku",
|
||||
@@ -1137,7 +1115,6 @@
|
||||
"folders_feature_description": "Procházení zobrazení složek s fotografiemi a videi v souborovém systému",
|
||||
"forgot_pin_code_question": "Zapomněli jste PIN?",
|
||||
"forward": "Dopředu",
|
||||
"full_path": "Úplná cesta: {path}",
|
||||
"gcast_enabled": "Google Cast",
|
||||
"gcast_enabled_description": "Tato funkce načítá externí zdroje z Googlu, aby mohla fungovat.",
|
||||
"general": "Obecné",
|
||||
@@ -1168,14 +1145,12 @@
|
||||
"header_settings_header_name_input": "Název hlavičky",
|
||||
"header_settings_header_value_input": "Hodnota hlavičky",
|
||||
"headers_settings_tile_title": "Vlastní proxy hlavičky",
|
||||
"height": "Výška",
|
||||
"hi_user": "Ahoj {name} ({email})",
|
||||
"hide_all_people": "Skrýt všechny lidi",
|
||||
"hide_gallery": "Skrýt galerii",
|
||||
"hide_named_person": "Skrýt osobu {name}",
|
||||
"hide_password": "Skrýt heslo",
|
||||
"hide_person": "Skrýt osobu",
|
||||
"hide_text_recognition": "Skrýt rozpoznávání textu",
|
||||
"hide_unnamed_people": "Skrýt nejmenované lidi",
|
||||
"home_page_add_to_album_conflicts": "Přidáno {added} položek do alba {album}. {failed} položek je již v albu.",
|
||||
"home_page_add_to_album_err_local": "Zatím není možné přidat lokální média do alb, přeskakuji",
|
||||
@@ -1221,8 +1196,6 @@
|
||||
"import_path": "Cesta importu",
|
||||
"in_albums": "{count, plural, one {V # albu} few {Ve # albech} other {V # albech}}",
|
||||
"in_archive": "V archivu",
|
||||
"in_year": "V roce {year}",
|
||||
"in_year_selector": "V roce",
|
||||
"include_archived": "Včetně archivovaných",
|
||||
"include_shared_albums": "Včetně sdílených alb",
|
||||
"include_shared_partner_assets": "Včetně sdílených položek partnera",
|
||||
@@ -1259,7 +1232,6 @@
|
||||
"language_setting_description": "Vyberte upřednostňovaný jazyk",
|
||||
"large_files": "Velké soubory",
|
||||
"last": "Poslední",
|
||||
"last_months": "{count, plural, one {Poslední měsíc} few {Poslední # měsíce} other {Posledních # měsíců}}",
|
||||
"last_seen": "Naposledy viděno",
|
||||
"latest_version": "Nejnovější verze",
|
||||
"latitude": "Zeměpisná šířka",
|
||||
@@ -1269,8 +1241,6 @@
|
||||
"let_others_respond": "Nechte ostatní reagovat",
|
||||
"level": "Úroveň",
|
||||
"library": "Knihovna",
|
||||
"library_add_folder": "Přidat složku",
|
||||
"library_edit_folder": "Upravit složku",
|
||||
"library_options": "Možnosti knihovny",
|
||||
"library_page_device_albums": "Alba v zařízení",
|
||||
"library_page_new_album": "Nové album",
|
||||
@@ -1291,7 +1261,6 @@
|
||||
"local": "Místní",
|
||||
"local_asset_cast_failed": "Nelze odeslat položku, která není nahraná na serveru",
|
||||
"local_assets": "Místní položky",
|
||||
"local_id": "Místní ID",
|
||||
"local_media_summary": "Souhrn místních médií",
|
||||
"local_network": "Místní síť",
|
||||
"local_network_sheet_info": "Aplikace se při použití zadané sítě Wi-Fi připojí k serveru prostřednictvím tohoto URL",
|
||||
@@ -1343,17 +1312,8 @@
|
||||
"loop_videos_description": "Povolit automatickou smyčku videa v prohlížeči.",
|
||||
"main_branch_warning": "Používáte vývojovou verzi; důrazně doporučujeme používat verzi z vydání!",
|
||||
"main_menu": "Hlavní nabídka",
|
||||
"maintenance_description": "Immich byl přepnut do <link>režimu údržby</link>.",
|
||||
"maintenance_end": "Ukončit režim údržby",
|
||||
"maintenance_end_error": "Nepodařilo se ukončit režim údržby.",
|
||||
"maintenance_logged_in_as": "Aktuálně přihlášen jako {user}",
|
||||
"maintenance_title": "Dočasně nedostupné",
|
||||
"make": "Výrobce",
|
||||
"manage_geolocation": "Spravovat polohu",
|
||||
"manage_media_access_rationale": "Toto oprávnění je vyžadováno pro správné zacházení s přesunem položek do koše a jejich obnovováním z něj.",
|
||||
"manage_media_access_settings": "Otevřít nastavení",
|
||||
"manage_media_access_subtitle": "Povolte aplikaci Immich spravovat a přesouvat soubory médií.",
|
||||
"manage_media_access_title": "Přístup ke správě médií",
|
||||
"manage_shared_links": "Spravovat sdílené odkazy",
|
||||
"manage_sharing_with_partners": "Správa sdílení s partnery",
|
||||
"manage_the_app_settings": "Správa nastavení aplikace",
|
||||
@@ -1417,7 +1377,6 @@
|
||||
"more": "Více",
|
||||
"move": "Přesunout",
|
||||
"move_off_locked_folder": "Přesunout z uzamčené složky",
|
||||
"move_to": "Přesunout do",
|
||||
"move_to_lock_folder_action_prompt": "{count} přidaných do uzamčené složky",
|
||||
"move_to_locked_folder": "Přesunout do uzamčené složky",
|
||||
"move_to_locked_folder_confirmation": "Tyto fotky a videa budou odstraněny ze všech alb a bude je možné zobrazit pouze v uzamčené složce",
|
||||
@@ -1447,7 +1406,6 @@
|
||||
"new_pin_code": "Nový PIN kód",
|
||||
"new_pin_code_subtitle": "Poprvé přistupujete k uzamčené složce. Vytvořte si kód PIN pro bezpečný přístup na tuto stránku",
|
||||
"new_timeline": "Nová časová osa",
|
||||
"new_update": "Nová aktualizace",
|
||||
"new_user_created": "Vytvořen nový uživatel",
|
||||
"new_version_available": "NOVÁ VERZE K DISPOZICI",
|
||||
"newest_first": "Nejnovější první",
|
||||
@@ -1463,14 +1421,12 @@
|
||||
"no_cast_devices_found": "Nebyla nalezena žádná zařízení",
|
||||
"no_checksum_local": "Není k dispozici kontrolní součet - nelze načíst místní položky",
|
||||
"no_checksum_remote": "Není k dispozici kontrolní součet - nelze načíst vzdálenou položku",
|
||||
"no_devices": "Žádná autorizovaná zařízení",
|
||||
"no_duplicates_found": "Nebyly nalezeny žádné duplicity.",
|
||||
"no_exif_info_available": "Exif není k dispozici",
|
||||
"no_explore_results_message": "Nahrajte další fotografie a prozkoumejte svou sbírku.",
|
||||
"no_favorites_message": "Přidejte si oblíbené položky a rychle najděte své nejlepší obrázky a videa",
|
||||
"no_libraries_message": "Vytvořte si externí knihovnu pro zobrazení fotografií a videí",
|
||||
"no_local_assets_found": "Nebyly nalezeny žádné místní položky s tímto kontrolním součtem",
|
||||
"no_location_set": "Není nastavena poloha",
|
||||
"no_locked_photos_message": "Fotky a videa v uzamčené složce jsou skryté a při procházení nebo vyhledávání v knihovně se nezobrazují.",
|
||||
"no_name": "Bez jména",
|
||||
"no_notifications": "Žádná oznámení",
|
||||
@@ -1481,7 +1437,6 @@
|
||||
"no_results_description": "Zkuste použít synonymum nebo obecnější klíčové slovo",
|
||||
"no_shared_albums_message": "Vytvořte si album a sdílejte fotografie a videa s lidmi ve své síti",
|
||||
"no_uploads_in_progress": "Neprobíhá žádné nahrávání",
|
||||
"not_allowed": "Nepovoleno",
|
||||
"not_available": "Není k dispozici",
|
||||
"not_in_any_album": "Bez alba",
|
||||
"not_selected": "Není vybráno",
|
||||
@@ -1496,8 +1451,8 @@
|
||||
"notifications": "Oznámení",
|
||||
"notifications_setting_description": "Správa oznámení",
|
||||
"oauth": "OAuth",
|
||||
"obtainium_configurator": "Konfigurátor Obtainium",
|
||||
"obtainium_configurator_instructions": "Pomocí aplikace Obtainium nainstalujte a aktualizujte aplikaci pro Android přímo z vydání na GitHubu Immich. Vytvořte API klíč a vyberte variantu pro vytvoření konfiguračního odkazu pro Obtainium",
|
||||
"obtainium_configurator": "Obtainium konfigurátor",
|
||||
"obtainium_configurator_instructions": "Pomocí Obtainia nainstalujte a aktualizujte aplikaci pro Android přímo z vydání na Immich GitHubu. Vytvořte API klíč a vyberte variantu pro vytvoření konfiguračního odkazu Obtainia",
|
||||
"ocr": "OCR",
|
||||
"official_immich_resources": "Oficiální zdroje Immich",
|
||||
"offline": "Offline",
|
||||
@@ -1530,7 +1485,6 @@
|
||||
"other_variables": "Další proměnné",
|
||||
"owned": "Vlastní",
|
||||
"owner": "Vlastník",
|
||||
"page": "Stránka",
|
||||
"partner": "Partner",
|
||||
"partner_can_access": "{partner} má přístup",
|
||||
"partner_can_access_assets": "Všechny vaše fotky a videa kromě těch, které jsou v sekcích Archivováno a Smazáno",
|
||||
@@ -1593,8 +1547,6 @@
|
||||
"photos_count": "{count, plural, one {{count, number} fotka} few {{count, number} fotky} other {{count, number} fotek}}",
|
||||
"photos_from_previous_years": "Fotky z předchozích let",
|
||||
"pick_a_location": "Vyberte polohu",
|
||||
"pick_custom_range": "Vlastní rozsah",
|
||||
"pick_date_range": "Vyberte rozsah dat",
|
||||
"pin_code_changed_successfully": "PIN kód byl úspěšně změněn",
|
||||
"pin_code_reset_successfully": "PIN kód úspěšně resetován",
|
||||
"pin_code_setup_successfully": "PIN kód úspěšně nastaven",
|
||||
@@ -1862,8 +1814,6 @@
|
||||
"server_offline": "Server offline",
|
||||
"server_online": "Server online",
|
||||
"server_privacy": "Ochrana soukromí serveru",
|
||||
"server_restarting_description": "Tato stránka se za chvíli obnoví.",
|
||||
"server_restarting_title": "Server se restartuje",
|
||||
"server_stats": "Statistiky serveru",
|
||||
"server_update_available": "K dispozici je aktualizace serveru",
|
||||
"server_version": "Verze serveru",
|
||||
@@ -1987,7 +1937,6 @@
|
||||
"show_slideshow_transition": "Zobrazit přechod prezentace",
|
||||
"show_supporter_badge": "Odznak podporovatele",
|
||||
"show_supporter_badge_description": "Zobrazit odznak podporovatele",
|
||||
"show_text_recognition": "Zobrazit rozpoznávání textu",
|
||||
"show_text_search_menu": "Zobrazit nabídku pro vyhledávání textu",
|
||||
"shuffle": "Náhodný výběr",
|
||||
"sidebar": "Postranní panel",
|
||||
@@ -2058,7 +2007,6 @@
|
||||
"tags": "Značky",
|
||||
"tap_to_run_job": "Klepnutím spustíte úlohu",
|
||||
"template": "Šablona",
|
||||
"text_recognition": "Rozpoznávání textu",
|
||||
"theme": "Motiv",
|
||||
"theme_selection": "Výběr motivu",
|
||||
"theme_selection_description": "Automatické nastavení světlého nebo tmavého motivu podle systémových preferencí prohlížeče",
|
||||
@@ -2079,7 +2027,6 @@
|
||||
"third_party_resources": "Zdroje třetích stran",
|
||||
"time": "Čas",
|
||||
"time_based_memories": "Časové vzpomínky",
|
||||
"time_based_memories_duration": "Počet sekund k zobrazení každého obrázku.",
|
||||
"timeline": "Časová osa",
|
||||
"timezone": "Časové pásmo",
|
||||
"to_archive": "Archivovat",
|
||||
@@ -2091,7 +2038,6 @@
|
||||
"to_select": "vybrat",
|
||||
"to_trash": "Vyhodit",
|
||||
"toggle_settings": "Přepnout nastavení",
|
||||
"toggle_theme_description": "Přepnout motiv",
|
||||
"total": "Celkem",
|
||||
"total_usage": "Celkové využití",
|
||||
"trash": "Koš",
|
||||
@@ -2200,7 +2146,6 @@
|
||||
"view_album": "Zobrazit album",
|
||||
"view_all": "Zobrazit vše",
|
||||
"view_all_users": "Zobrazit všechny uživatele",
|
||||
"view_asset_owners": "Zobrazit vlastníky položek",
|
||||
"view_details": "Zobrazit podrobnosti",
|
||||
"view_in_timeline": "Zobrazit na časové ose",
|
||||
"view_link": "Zobrazit odkaz",
|
||||
@@ -2217,14 +2162,11 @@
|
||||
"viewer_unstack": "Zrušit zásobník",
|
||||
"visibility_changed": "Viditelnost změněna u {count, plural, one {# osoby} few {# osob} other {# lidí}}",
|
||||
"waiting": "Čekající",
|
||||
"waiting_count": "Čekající: {count}",
|
||||
"warning": "Upozornění",
|
||||
"week": "Týden",
|
||||
"welcome": "Vítejte",
|
||||
"welcome_to_immich": "Vítejte v Immichi",
|
||||
"width": "Šířka",
|
||||
"wifi_name": "Název Wi-Fi",
|
||||
"workflow": "Pracovní postup",
|
||||
"wrong_pin_code": "Chybný PIN kód",
|
||||
"year": "Rok",
|
||||
"years_ago": "Před {years, plural, one {rokem} other {# lety}}",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"add_birthday": "Ҫуралнӑ кун хушӑр",
|
||||
"add_endpoint": "Вӗҫӗмлӗ пӑнчӑ хушар",
|
||||
"add_exclusion_pattern": "Кӑларса пӑрахмалли йӗрке хуш",
|
||||
"add_import_path": "Импорт ҫулне хуш",
|
||||
"add_location": "Вырӑн хуш",
|
||||
"add_more_users": "Усӑҫсем ытларах хуш",
|
||||
"add_partner": "Мӑшӑр хуш",
|
||||
|
||||
110
i18n/da.json
110
i18n/da.json
@@ -17,6 +17,7 @@
|
||||
"add_birthday": "Tilføj en fødselsdag",
|
||||
"add_endpoint": "Tilføj endepunkt",
|
||||
"add_exclusion_pattern": "Tilføj udelukkelsesmønster",
|
||||
"add_import_path": "Tilføj importsti",
|
||||
"add_location": "Tilføj placering",
|
||||
"add_more_users": "Tilføj flere brugere",
|
||||
"add_partner": "Tilføj partner",
|
||||
@@ -31,7 +32,6 @@
|
||||
"add_to_album_toggle": "Skift selektion for {album}",
|
||||
"add_to_albums": "Tilføj til albummer",
|
||||
"add_to_albums_count": "Tilføj til albummer({count})",
|
||||
"add_to_bottom_bar": "Tilføj til",
|
||||
"add_to_shared_album": "Tilføj til delt album",
|
||||
"add_upload_to_stack": "Tilføj upload til stack",
|
||||
"add_url": "Tilføj URL",
|
||||
@@ -67,7 +67,6 @@
|
||||
"confirm_reprocess_all_faces": "Er du sikker på, at du vil genbehandle alle ansigter? Dette vil også rydde navngivne personer.",
|
||||
"confirm_user_password_reset": "Er du sikker på, at du vil nulstille {user}s adgangskode?",
|
||||
"confirm_user_pin_code_reset": "Er du sikker på at du vil nulstille {user}'s PIN kode?",
|
||||
"copy_config_to_clipboard_description": "Kopier den aktuelle systemkonfiguration som et JSON-objekt til udklipsholderen",
|
||||
"create_job": "Opret job",
|
||||
"cron_expression": "Cron formel",
|
||||
"cron_expression_description": "Indstil skannings intervallet i cron format. For mere information se: <link>Crontab Guru</link>",
|
||||
@@ -75,8 +74,7 @@
|
||||
"disable_login": "Deaktiver login",
|
||||
"duplicate_detection_job_description": "Kør maskinlæring på mediefiler for at opdage lignende billeder. Er afhængig af Smart Søgning",
|
||||
"exclusion_pattern_description": "Ekskluderingsmønstre lader dig ignorere filer og mapper, når du scanner dit bibliotek. Dette er nyttigt, hvis du har mapper, der indeholder filer, du ikke vil importere, såsom RAW-filer.",
|
||||
"export_config_as_json_description": "Download den aktuelle systemkonfiguration som en JSON-fil",
|
||||
"external_libraries_page_description": "Admin ekstern biblioteksside",
|
||||
"external_library_management": "Ekstern biblioteksstyring",
|
||||
"face_detection": "Ansigtsopdagelse",
|
||||
"face_detection_description": "Genkend ansigterne i mediefiler via maskinlæring. For videoer er det kun miniaturebilledet som tages hensyn til. \"Alle\" (gen-)behandler alle mediefiler. \"Mangler\" sætter mediefiler i kø, som ikke er blevet behandlet endnu. Opdagede ansigter vil blive sat i kø til Ansigtsgenkendelse efter Ansigtsopdagelse er færdig, hvilket grupperer dem til eksisterende eller nye personer.",
|
||||
"facial_recognition_job_description": "Grupper opdagede ansigter i personer. Dette trin kører efter Ansigtsopdagelse er færdig. \"Alle\" (gen-)klumper alle ansigter sammen. \"Mangler\" sætter ansigter i kø, som ikke har en person tildelt.",
|
||||
@@ -104,27 +102,23 @@
|
||||
"image_thumbnail_description": "Små miniaturer uden metadata, bruges når der ses samlinger eller den primære tidslinie",
|
||||
"image_thumbnail_quality_description": "Miniaturer kvaliteten indstilles fra 1 til 100. Nu højre, nu bedre kvalitet. Men giver større filer og påvirker programmets svartider.",
|
||||
"image_thumbnail_title": "Thumbnail-indstillinger",
|
||||
"import_config_from_json_description": "Importer systemkonfiguration ved at uploade en JSON-konfigurationsfil",
|
||||
"job_concurrency": "{job} samtidighed",
|
||||
"job_created": "opgaven er skabt",
|
||||
"job_not_concurrency_safe": "Denne opgave er ikke sikker at køre samtidigt med andre.",
|
||||
"job_settings": "Jobindstillinger",
|
||||
"job_settings_description": "Administrér samtidige opgaver",
|
||||
"job_status": "Opgave Status",
|
||||
"jobs_delayed": "{jobCount, plural, one {# forsinket} other {# forsinkede}}",
|
||||
"jobs_failed": "{jobCount, plural, one {# fejlet} other {# fejlede}}",
|
||||
"library_created": "Skabte bibliotek: {library}",
|
||||
"library_deleted": "Bibliotek slettet",
|
||||
"library_details": "Bibliotek detaljer",
|
||||
"library_folder_description": "Angiv en mappe, der skal importeres. Denne mappe, inklusive undermapper, scannes for billeder og videoer.",
|
||||
"library_remove_exclusion_pattern_prompt": "Er du sikker på, at du vil fjerne dette udelukkelsesmønster?",
|
||||
"library_remove_folder_prompt": "Er du sikker på, at du vil fjerne denne importmappe?",
|
||||
"library_import_path_description": "Angiv en mappe, der skal importeres. Denne mappe, inklusive undermapper, vil blive scannet for billeder og videoer.",
|
||||
"library_scanning": "Periodisk scanning",
|
||||
"library_scanning_description": "Konfigurer periodisk biblioteksscanning",
|
||||
"library_scanning_enable_description": "Aktiver periodisk biblioteksscanning",
|
||||
"library_settings": "Eksternt bibliotek",
|
||||
"library_settings_description": "Administrer eksterne biblioteksindstillinger",
|
||||
"library_tasks_description": "Scan eksterne biblioteker for nye og/eller ændrede mediefiler",
|
||||
"library_updated": "Opdateret bibliotek",
|
||||
"library_watching_enable_description": "Overvåg eksterne biblioteker for filændringer",
|
||||
"library_watching_settings": "Biblioteks overvågning [EKSPERIMENTEL]",
|
||||
"library_watching_settings_description": "Tjek automatisk for ændrede filer",
|
||||
@@ -179,12 +173,7 @@
|
||||
"machine_learning_smart_search_enabled": "Aktiver smart søgning",
|
||||
"machine_learning_smart_search_enabled_description": "Hvis deaktiveret, vil billeder ikke blive kodet til smart søgning.",
|
||||
"machine_learning_url_description": "URL’en for maskinlæringsserveren. Hvis mere end én URL angives, vil hver server blive forsøgt én ad gangen, indtil en svarer succesfuldt, i rækkefølge fra første til sidste. Servere, der ikke svarer, vil midlertidigt blive ignoreret, indtil de kommer online igen.",
|
||||
"maintenance_settings": "Vedligeholdelse",
|
||||
"maintenance_settings_description": "Sæt Immich i vedligeholdelsestilstand.",
|
||||
"maintenance_start": "Start vedligeholdelsestilstand",
|
||||
"maintenance_start_error": "Vedligeholdelsestilstand kunne ikke startes.",
|
||||
"manage_concurrency": "Administrer antallet af samtidige opgaver",
|
||||
"manage_concurrency_description": "Naviger til jobsiden for at administrere jobsamtidighed",
|
||||
"manage_log_settings": "Administrer logindstillinger",
|
||||
"map_dark_style": "Mørk tema",
|
||||
"map_enable_description": "Aktivér kortfunktioner",
|
||||
@@ -290,10 +279,8 @@
|
||||
"server_public_users_description": "Alle brugere (navn og e-mail) vises, når en bruger tilføjes til delte album. Når den er deaktiveret, vil brugerlisten kun være tilgængelig for administratorbrugere.",
|
||||
"server_settings": "Serverindstillinger",
|
||||
"server_settings_description": "Administrér serverindstillinger",
|
||||
"server_stats_page_description": "Admin server statistikside",
|
||||
"server_welcome_message": "Velkomstbesked",
|
||||
"server_welcome_message_description": "En besked som bliver vist på loginsiden.",
|
||||
"settings_page_description": "Admin-indstillinger side",
|
||||
"sidecar_job": "Medfølgende metadata",
|
||||
"sidecar_job_description": "Opdag eller synkroniser medfølgende metadata fra filsystemet",
|
||||
"slideshow_duration_description": "Antal sekunder at vise hvert billede",
|
||||
@@ -314,7 +301,7 @@
|
||||
"storage_template_settings_description": "Administrer mappestrukturen og filnavnet for den uploadede mediefil",
|
||||
"storage_template_user_label": "<code>{label}</code> er brugerens Lagringsmærkat",
|
||||
"system_settings": "Systemindstillinger",
|
||||
"tag_cleanup_job": "\"Tag\"-oprydning",
|
||||
"tag_cleanup_job": "\"Tag\" cleanup",
|
||||
"template_email_available_tags": "Du kan bruge følgende variabler i din skabelon: {tags}",
|
||||
"template_email_if_empty": "Hvis skabelonen er tom, vil standard-e-mailen blive brugt.",
|
||||
"template_email_invite_album": "Inviterings albumskabelon",
|
||||
@@ -390,11 +377,11 @@
|
||||
"transcoding_two_pass_encoding_setting_description": "Transkoder af to omgange for at producere bedre indkodede videoer. Når den maksimale bitrate er slået til (som det kræver for at det fungerer med H.264 og HEVC), bruger denne tilstand en bitrateinterval baseret på den maksimale birate og ignorerer CRF. For VP9, kan CRF bruges hvis den maksimale bitrate er slået fra.",
|
||||
"transcoding_video_codec": "Videocodec",
|
||||
"transcoding_video_codec_description": "VP9 har en højere effektivitet og webkompatibilitet, men indkodningen tager længere tid. HEVC har lignende ydelse, men har lavere webkompatibilitet og er hurtig at transkode, men giver meget større filer. AV1 er det mest effektive codec, men mangler understøttelse på ældre enheder.",
|
||||
"trash_enabled_description": "Aktivér \"Papirkurvs\"-funktioner",
|
||||
"trash_enabled_description": "Aktivér skraldefunktioner",
|
||||
"trash_number_of_days": "Antal dage",
|
||||
"trash_number_of_days_description": "Antal dage elementer i papirkurven skal beholdes inden de fjernes permanent",
|
||||
"trash_settings": "Papirkurvs-indstillinger",
|
||||
"trash_settings_description": "Administrér papirkurvs-indstillinger",
|
||||
"trash_number_of_days_description": "Antal dage aktiver i skraldespanden skal beholdes inden de fjernes permanent",
|
||||
"trash_settings": "Skraldeindstillinger",
|
||||
"trash_settings_description": "Administrér skraldeindstillinger",
|
||||
"unlink_all_oauth_accounts": "Ophæv link til alle OAuth konti",
|
||||
"unlink_all_oauth_accounts_description": "Husk at fjerne linket til alle OAuth konti før du migrerer til en ny udbyder.",
|
||||
"unlink_all_oauth_accounts_prompt": "Er du sikker på, at du vil ophæve link til alle OAuth konti? Dette vil nulstille OAuth ID for hver bruger og kan ikke fortrydes.",
|
||||
@@ -413,7 +400,6 @@
|
||||
"user_settings": "Brugerindstillinger",
|
||||
"user_settings_description": "Administrér brugerindstillinger",
|
||||
"user_successfully_removed": "Bruger {email} er blevet fjernet med succes.",
|
||||
"users_page_description": "Admin-brugere side",
|
||||
"version_check_enabled_description": "Aktivér versionstjek",
|
||||
"version_check_implications": "Funktionen til versionstjek er afhængig af periodisk kommunikation med github.com",
|
||||
"version_check_settings": "Versionstjek",
|
||||
@@ -444,7 +430,6 @@
|
||||
"age_months": "Alder {months, plural, one {# måned} other {# måneder}}",
|
||||
"age_year_months": "Alder 1 år, {months, plural, one {# måned} other {# måneder}}",
|
||||
"age_years": "{years, plural, other {Alder #}}",
|
||||
"album": "Album",
|
||||
"album_added": "Album tilføjet",
|
||||
"album_added_notification_setting_description": "Modtag en emailnotifikation når du bliver tilføjet til en delt album",
|
||||
"album_cover_updated": "Albumcover opdateret",
|
||||
@@ -490,7 +475,6 @@
|
||||
"allow_edits": "Tillad redigeringer",
|
||||
"allow_public_user_to_download": "Tillad offentlige brugere til at hente",
|
||||
"allow_public_user_to_upload": "Tillad offentlige brugere til at uploade",
|
||||
"allowed": "Tilladt",
|
||||
"alt_text_qr_code": "QR-kode billede",
|
||||
"anti_clockwise": "Mod uret",
|
||||
"api_key": "API-nøgle",
|
||||
@@ -538,7 +522,7 @@
|
||||
"asset_offline_description": "Denne eksterne mediefil kan ikke længere findes på drevet. Kontakt venligst din Immich-administrator for hjælp.",
|
||||
"asset_restored_successfully": "Elementet blev gendannet succesfuldt",
|
||||
"asset_skipped": "Sprunget over",
|
||||
"asset_skipped_in_trash": "I papirkurv",
|
||||
"asset_skipped_in_trash": "I skraldespand",
|
||||
"asset_trashed": "Objekt kasseret",
|
||||
"asset_troubleshoot": "Fejlsøg på objekt",
|
||||
"asset_uploaded": "Uploadet",
|
||||
@@ -734,7 +718,6 @@
|
||||
"collapse_all": "Klap alle sammen",
|
||||
"color": "Farve",
|
||||
"color_theme": "Farvetema",
|
||||
"command": "Kommando",
|
||||
"comment_deleted": "Kommentar slettet",
|
||||
"comment_options": "Kommentarindstillinger",
|
||||
"comments_and_likes": "Kommentarer og likes",
|
||||
@@ -911,6 +894,8 @@
|
||||
"edit_description_prompt": "Vælg venligst en ny beskrivelse:",
|
||||
"edit_exclusion_pattern": "Redigér udelukkelsesmønster",
|
||||
"edit_faces": "Redigér ansigter",
|
||||
"edit_import_path": "Redigér import-sti",
|
||||
"edit_import_paths": "Redigér import-stier",
|
||||
"edit_key": "Redigér nøgle",
|
||||
"edit_link": "Rediger link",
|
||||
"edit_location": "Rediger placering",
|
||||
@@ -982,8 +967,8 @@
|
||||
"failed_to_stack_assets": "Det lykkedes ikke at stable mediefiler",
|
||||
"failed_to_unstack_assets": "Det lykkedes ikke at fjerne gruperingen af mediefiler",
|
||||
"failed_to_update_notification_status": "Kunne ikke uploade notifikations status",
|
||||
"import_path_already_exists": "Denne importsti findes allerede.",
|
||||
"incorrect_email_or_password": "Forkert email eller kodeord",
|
||||
"library_folder_already_exists": "Denne import sti findes allerede.",
|
||||
"paths_validation_failed": "{paths, plural, one {# sti} other {# stier}} slog fejl ved validering",
|
||||
"profile_picture_transparent_pixels": "Profilbilleder kan ikke have gennemsigtige pixels. Zoom venligst ind og/eller flyt billedet.",
|
||||
"quota_higher_than_disk_size": "Du har sat en kvote der er større end disken",
|
||||
@@ -992,6 +977,7 @@
|
||||
"unable_to_add_assets_to_shared_link": "Kan ikke tilføje mediefiler til det delte link",
|
||||
"unable_to_add_comment": "Ikke i stand til at tilføje kommentar",
|
||||
"unable_to_add_exclusion_pattern": "Kunne ikke tilføje udelukkelsesmønster",
|
||||
"unable_to_add_import_path": "Kunne ikke tilføje importsti",
|
||||
"unable_to_add_partners": "Ikke i stand til at tilføje partnere",
|
||||
"unable_to_add_remove_archive": "Kan Ikke {archived, select, true {fjerne aktiv fra} other {tilføje aktiv til}} Arkiv",
|
||||
"unable_to_add_remove_favorites": "Kan ikke {favorite, select, true {tilføje aktiv til} other {fjerne aktiv fra}} favoritter",
|
||||
@@ -1014,11 +1000,13 @@
|
||||
"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",
|
||||
"unable_to_delete_user": "Ikke i stand til at slette bruger",
|
||||
"unable_to_download_files": "Kan ikke downloade filer",
|
||||
"unable_to_edit_exclusion_pattern": "Kunne ikke redigere udelukkelsesmønster",
|
||||
"unable_to_empty_trash": "Ikke i stand til at tømme papirkurv",
|
||||
"unable_to_edit_import_path": "Kunne ikke redigere importsti",
|
||||
"unable_to_empty_trash": "Ikke i stand til at tømme skraldespand",
|
||||
"unable_to_enter_fullscreen": "Kan ikke aktivere fuldskærmstilstand",
|
||||
"unable_to_exit_fullscreen": "Kan ikke forlade fuldskærmstilstand",
|
||||
"unable_to_get_comments_number": "Kan ikke få antallet af kommentarer",
|
||||
@@ -1043,7 +1031,7 @@
|
||||
"unable_to_reset_pin_code": "Kunne ikke nulstille din PIN kode",
|
||||
"unable_to_resolve_duplicate": "Kunne ikke opklare duplikat",
|
||||
"unable_to_restore_assets": "Kunne ikke gendanne medierfil",
|
||||
"unable_to_restore_trash": "Ikke i stand til at gendanne fra papirkurv",
|
||||
"unable_to_restore_trash": "Ikke i stand til at gendanne fra skraldespanden",
|
||||
"unable_to_restore_user": "Ikke i stand til at gendanne bruger",
|
||||
"unable_to_save_album": "Ikke i stand til at gemme album",
|
||||
"unable_to_save_api_key": "Kunne ikke gemme API-nøgle",
|
||||
@@ -1068,7 +1056,6 @@
|
||||
"unable_to_update_user": "Ikke i stand til at opdatere bruger",
|
||||
"unable_to_upload_file": "Filen kunne ikke uploades"
|
||||
},
|
||||
"exclusion_pattern": "Udelukkelsesmønster",
|
||||
"exif": "Exif",
|
||||
"exif_bottom_sheet_description": "Tilføj beskrivelse...",
|
||||
"exif_bottom_sheet_description_error": "Fejl ved opdatering af beskrivelsen",
|
||||
@@ -1128,7 +1115,6 @@
|
||||
"folders_feature_description": "Gennemse mappevisningen efter fotos og videoer på filsystemet",
|
||||
"forgot_pin_code_question": "Har du glemt PIN-koden?",
|
||||
"forward": "Fremad",
|
||||
"full_path": "Fuld sti: {path}",
|
||||
"gcast_enabled": "Google Cast",
|
||||
"gcast_enabled_description": "Denne funktion indlæser eksterne ressourcer fra Google for at virke.",
|
||||
"general": "Generel",
|
||||
@@ -1165,7 +1151,6 @@
|
||||
"hide_named_person": "Skjul person {name}",
|
||||
"hide_password": "Skjul adgangskode",
|
||||
"hide_person": "Skjul person",
|
||||
"hide_text_recognition": "Skjul tekstgenkendelse",
|
||||
"hide_unnamed_people": "Skjul unavngivne personer",
|
||||
"home_page_add_to_album_conflicts": "Tilføjede {added} elementer til album {album}. {failed} elementer er allerede i albummet.",
|
||||
"home_page_add_to_album_err_local": "Kan endnu ikke tilføje lokale elementer til album. Springer over",
|
||||
@@ -1211,8 +1196,6 @@
|
||||
"import_path": "Import-sti",
|
||||
"in_albums": "I {count, plural, one {# album} other {# albummer}}",
|
||||
"in_archive": "I arkiv",
|
||||
"in_year": "I {year}",
|
||||
"in_year_selector": "I",
|
||||
"include_archived": "Inkluder arkiveret",
|
||||
"include_shared_albums": "Inkludér delte albummer",
|
||||
"include_shared_partner_assets": "Inkludér delte partnermedier",
|
||||
@@ -1249,7 +1232,6 @@
|
||||
"language_setting_description": "Vælg dit foretrukne sprog",
|
||||
"large_files": "Store filer",
|
||||
"last": "Sidste",
|
||||
"last_months": "{count, plural, one {Sidste måned} other {Sidste # måneder}}",
|
||||
"last_seen": "Sidst set",
|
||||
"latest_version": "Seneste version",
|
||||
"latitude": "Breddegrad",
|
||||
@@ -1259,8 +1241,6 @@
|
||||
"let_others_respond": "Lad andre svare",
|
||||
"level": "Niveau",
|
||||
"library": "Bibliotek",
|
||||
"library_add_folder": "Tilføj mappe",
|
||||
"library_edit_folder": "Rediger mappe",
|
||||
"library_options": "Biblioteksindstillinger",
|
||||
"library_page_device_albums": "Albummer på enhed",
|
||||
"library_page_new_album": "Nyt album",
|
||||
@@ -1332,17 +1312,8 @@
|
||||
"loop_videos_description": "Aktivér for at genafspille videoer automatisk i detaljeret visning.",
|
||||
"main_branch_warning": "Du bruger en udviklingsversion; vi anbefaler kraftigt at bruge en udgivelsesversion!",
|
||||
"main_menu": "Hovedmenu",
|
||||
"maintenance_description": "Immich er blevet sat i <link>vedligeholdelsestilstand</link>.",
|
||||
"maintenance_end": "Afslut vedligeholdelsestilstand",
|
||||
"maintenance_end_error": "Vedligeholdelsestilstand kunne ikke afsluttes.",
|
||||
"maintenance_logged_in_as": "Aktuelt logget ind som {user}",
|
||||
"maintenance_title": "Midlertidigt Utilgængelig",
|
||||
"make": "Producent",
|
||||
"manage_geolocation": "Administrer placering",
|
||||
"manage_media_access_rationale": "Denne tilladelse er påkrævet for korrekt håndtering af flytning af elementer til papirkurven og gendannelse af dem fra den.",
|
||||
"manage_media_access_settings": "Åben instillinger",
|
||||
"manage_media_access_subtitle": "Tillad Immich appen at administrere og flytte mediefiler.",
|
||||
"manage_media_access_title": "Mediestyringsadgang",
|
||||
"manage_shared_links": "Håndter delte links",
|
||||
"manage_sharing_with_partners": "Administrér deling med partnere",
|
||||
"manage_the_app_settings": "Administrer appindstillinger",
|
||||
@@ -1406,13 +1377,12 @@
|
||||
"more": "Mere",
|
||||
"move": "Flyt",
|
||||
"move_off_locked_folder": "Flyt ud af låst mappe",
|
||||
"move_to": "Flyt til",
|
||||
"move_to_lock_folder_action_prompt": "{count} føjet til den låste mappe",
|
||||
"move_to_locked_folder": "Flyt til låst mappe",
|
||||
"move_to_locked_folder_confirmation": "Disse billeder og videoer vil blive fjernet fra alle albums, og vil kun være synlig fra den låste mappe",
|
||||
"moved_to_archive": "Flyttede {count, plural, one {# mediefil} other {# mediefiler}} til arkivet",
|
||||
"moved_to_library": "Flyttede {count, plural, one {# mediefil} other {# mediefiler}} til biblioteket",
|
||||
"moved_to_trash": "Flyttet til papirkurv",
|
||||
"moved_to_trash": "Flyttet til skraldespand",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "Kan ikke redigere datoen på skrivebeskyttet elementer. Springer over",
|
||||
"multiselect_grid_edit_gps_err_read_only": "Kan ikke redigere lokation af skrivebeskyttet elementer. Springer over",
|
||||
"mute_memories": "Dæmp minder",
|
||||
@@ -1436,7 +1406,6 @@
|
||||
"new_pin_code": "Ny PIN kode",
|
||||
"new_pin_code_subtitle": "Dette er første gang du tilgår den låste mappe. Lav en PIN kode for sikkert at tilgå denne side",
|
||||
"new_timeline": "Ny tidslinje",
|
||||
"new_update": "Ny opdatering",
|
||||
"new_user_created": "Ny bruger oprettet",
|
||||
"new_version_available": "NY VERSION TILGÆNGELIG",
|
||||
"newest_first": "Nyeste først",
|
||||
@@ -1452,14 +1421,12 @@
|
||||
"no_cast_devices_found": "Ingen Cast-enheder fundet",
|
||||
"no_checksum_local": "Ingen checksum tilgængelig – kan ikke hente lokale objekter",
|
||||
"no_checksum_remote": "Ingen checksum tilgængelig – kan ikke hente eksterne objekter",
|
||||
"no_devices": "Ingen godkendte enheder",
|
||||
"no_duplicates_found": "Ingen duplikater fundet.",
|
||||
"no_exif_info_available": "Ingen tilgængelig exif information",
|
||||
"no_explore_results_message": "Upload flere billeder for at udforske din samling.",
|
||||
"no_favorites_message": "Tilføj favoritter for hurtigt at finde dine bedst billeder og videoer",
|
||||
"no_libraries_message": "Opret et eksternt bibliotek for at se dine billeder og videoer",
|
||||
"no_local_assets_found": "Ingen lokale objekter fundet med denne checksum",
|
||||
"no_location_set": "Ingen placering sat",
|
||||
"no_locked_photos_message": "Billeder og videoer i den låste mappe er skjulte og vil ikke blive vist i dit bibliotek.",
|
||||
"no_name": "Intet navn",
|
||||
"no_notifications": "Ingen notifikationer",
|
||||
@@ -1470,7 +1437,6 @@
|
||||
"no_results_description": "Prøv et synonym eller et mere generelt søgeord",
|
||||
"no_shared_albums_message": "Opret et album for at dele billeder og videoer med personer i dit netværk",
|
||||
"no_uploads_in_progress": "Ingen upload i gang",
|
||||
"not_allowed": "Ikke tilladt",
|
||||
"not_available": "ikke tilgængelig",
|
||||
"not_in_any_album": "Ikke i noget album",
|
||||
"not_selected": "Ikke valgt",
|
||||
@@ -1519,7 +1485,6 @@
|
||||
"other_variables": "Andre variable",
|
||||
"owned": "Egne",
|
||||
"owner": "Ejer",
|
||||
"page": "Side",
|
||||
"partner": "Partnerpartner",
|
||||
"partner_can_access": "{partner} kan tilgå",
|
||||
"partner_can_access_assets": "Alle dine billeder og videoer, bortset fra dem i Arkivet og Slettet",
|
||||
@@ -1582,8 +1547,6 @@
|
||||
"photos_count": "{count, plural, one {{count, number} Billede} other {{count, number} Billeder}}",
|
||||
"photos_from_previous_years": "Billeder fra tidligere år",
|
||||
"pick_a_location": "Vælg et sted",
|
||||
"pick_custom_range": "Brugerdefineret periode",
|
||||
"pick_date_range": "Vælg et datointerval",
|
||||
"pin_code_changed_successfully": "Ændring af PIN kode vellykket",
|
||||
"pin_code_reset_successfully": "Nulstilling af PIN kode vellykket",
|
||||
"pin_code_setup_successfully": "Opsætning af PIN kode vellykket",
|
||||
@@ -1755,7 +1718,7 @@
|
||||
"save_to_gallery": "Gem til galleri",
|
||||
"saved": "Gemt",
|
||||
"saved_api_key": "Gemt API-nøgle",
|
||||
"saved_profile": "Gemt profil",
|
||||
"saved_profile": "Gemte profil",
|
||||
"saved_settings": "Gemte indstillinger",
|
||||
"say_something": "Skriv noget",
|
||||
"scaffold_body_error_occurred": "Der opstod en fejl",
|
||||
@@ -1771,7 +1734,7 @@
|
||||
"search_by_filename": "Søg efter filnavn eller filtypenavn",
|
||||
"search_by_filename_example": "dvs. IMG_1234.JPG eller PNG",
|
||||
"search_by_ocr": "Søg via OCR",
|
||||
"search_by_ocr_example": "Søg efter tekst i dine billeder",
|
||||
"search_by_ocr_example": "Latte",
|
||||
"search_camera_lens_model": "Søg objektiv model...",
|
||||
"search_camera_make": "Søg efter kameraproducent...",
|
||||
"search_camera_model": "Søg efter kameramodel...",
|
||||
@@ -1800,7 +1763,7 @@
|
||||
"search_options": "Søgemuligheder",
|
||||
"search_page_categories": "Kategorier",
|
||||
"search_page_motion_photos": "Bevægelsesbilleder",
|
||||
"search_page_no_objects": "Ingen elementinfomation er tilgængelig",
|
||||
"search_page_no_objects": "Ingen elementer er tilgængelige",
|
||||
"search_page_no_places": "Ingen placeringsinformation er tilgængelig",
|
||||
"search_page_screenshots": "Skærmbilleder",
|
||||
"search_page_search_photos_videos": "Søg i dine billeder og videoer",
|
||||
@@ -1851,8 +1814,6 @@
|
||||
"server_offline": "Server offline",
|
||||
"server_online": "Server online",
|
||||
"server_privacy": "Serverens privatliv",
|
||||
"server_restarting_description": "Denne side opdateres om et øjeblik.",
|
||||
"server_restarting_title": "Serveren genstarter",
|
||||
"server_stats": "Serverstatus",
|
||||
"server_update_available": "Serveropdatering er tilgængelig",
|
||||
"server_version": "Server version",
|
||||
@@ -1882,10 +1843,10 @@
|
||||
"setting_notifications_single_progress_title": "Vis detaljeret baggrundsuploadstatus",
|
||||
"setting_notifications_subtitle": "Tilpas dine notifikationspræferencer",
|
||||
"setting_notifications_total_progress_subtitle": "Samlet uploadstatus (færdige/samlet antal elementer)",
|
||||
"setting_notifications_total_progress_title": "Vis samlet baggrunds upload status",
|
||||
"setting_notifications_total_progress_title": "Vis samlet baggrundsuploadstatus",
|
||||
"setting_video_viewer_auto_play_subtitle": "Begynd automatisk at afspille videoer, når de åbnes",
|
||||
"setting_video_viewer_auto_play_title": "Automatisk afspilning af videoer",
|
||||
"setting_video_viewer_looping_title": "Genafspilning",
|
||||
"setting_video_viewer_looping_title": "Looper",
|
||||
"setting_video_viewer_original_video_subtitle": "Når der streames video fra serveren, afspil da den originale selv når en omkodet udgave er tilgængelig. Kan føre til buffering. Videoer, der er tilgængelige lokalt, afspilles i original kvalitet uanset denne indstilling.",
|
||||
"setting_video_viewer_original_video_title": "Tving original video",
|
||||
"settings": "Indstillinger",
|
||||
@@ -1941,7 +1902,7 @@
|
||||
"shared_link_info_chip_metadata": "EXIF",
|
||||
"shared_link_manage_links": "Håndter delte links",
|
||||
"shared_link_options": "Muligheder for delt link",
|
||||
"shared_link_password_description": "Kodeord krævet for at få adgang til dette delte link",
|
||||
"shared_link_password_description": "Kræv et kodeord for at få adgang til dette delte link",
|
||||
"shared_links": "Delte links",
|
||||
"shared_links_description": "Del billeder og videoer med et link",
|
||||
"shared_photos_and_videos_count": "{assetCount, plural, other {# delte billeder & videoer.}}",
|
||||
@@ -1974,9 +1935,8 @@
|
||||
"show_search_options": "Vis søgeindstillinger",
|
||||
"show_shared_links": "Vis delte links",
|
||||
"show_slideshow_transition": "Vis overgang til diasshow",
|
||||
"show_supporter_badge": "Supporter skilt",
|
||||
"show_supporter_badge_description": "Vis et supporter ikon",
|
||||
"show_text_recognition": "Vis tekstgenkendelse",
|
||||
"show_supporter_badge": "Supportermærke",
|
||||
"show_supporter_badge_description": "Vis et supportermærke",
|
||||
"show_text_search_menu": "Vis tekstsøgningsmenu",
|
||||
"shuffle": "Bland",
|
||||
"sidebar": "Sidebjælke",
|
||||
@@ -2011,7 +1971,7 @@
|
||||
"start_date_before_end_date": "Startdato skal ligge før slutdato",
|
||||
"state": "Stat",
|
||||
"status": "Status",
|
||||
"stop_casting": "Stop casting",
|
||||
"stop_casting": "Stop støbning",
|
||||
"stop_motion_photo": "Stopmotionbillede",
|
||||
"stop_photo_sharing": "Stop med at dele dine billeder?",
|
||||
"stop_photo_sharing_description": "{partner} vil ikke længere kunne tilgå dine billeder.",
|
||||
@@ -2047,7 +2007,6 @@
|
||||
"tags": "Tags",
|
||||
"tap_to_run_job": "Tryk for at køre jobbet",
|
||||
"template": "Skabelon",
|
||||
"text_recognition": "Tekst genkendelse",
|
||||
"theme": "Tema",
|
||||
"theme_selection": "Temavalg",
|
||||
"theme_selection_description": "Indstil automatisk temaet til lyst eller mørkt baseret på din browsers systempræference",
|
||||
@@ -2068,19 +2027,17 @@
|
||||
"third_party_resources": "Tredjepartsressourcer",
|
||||
"time": "Tid",
|
||||
"time_based_memories": "Tidsbaserede minder",
|
||||
"time_based_memories_duration": "Antal sekunder, hvert billede skal vises.",
|
||||
"timeline": "Tidslinje",
|
||||
"timezone": "Tidszone",
|
||||
"to_archive": "Arkivér",
|
||||
"to_change_password": "Skift adgangskode",
|
||||
"to_favorite": "Gør til favorit",
|
||||
"to_login": "Login",
|
||||
"to_multi_select": "for at vælge flere",
|
||||
"to_parent": "Gå et niveau op",
|
||||
"to_multi_select": "For at vælge flere",
|
||||
"to_parent": "Gå op",
|
||||
"to_select": "for at vælge",
|
||||
"to_trash": "Papirkurv",
|
||||
"toggle_settings": "Skift indstillinger",
|
||||
"toggle_theme_description": "Skift tema",
|
||||
"toggle_settings": "Slå indstillinger til eller fra",
|
||||
"total": "Total",
|
||||
"total_usage": "Samlet forbrug",
|
||||
"trash": "Papirkurv",
|
||||
@@ -2097,13 +2054,13 @@
|
||||
"trash_page_restore_all": "Gendan alt",
|
||||
"trash_page_select_assets_btn": "Vælg elementer",
|
||||
"trash_page_title": "Papirkurv ({count})",
|
||||
"trashed_items_will_be_permanently_deleted_after": "Mediefiler i papirkurven vil blive slettet permanent efter {days, plural, one {# dag} other {# dage}}.",
|
||||
"trashed_items_will_be_permanently_deleted_after": "Mediefiler i skraldespanden vil blive slettet permanent efter {days, plural, one {# dag} other {# dage}}.",
|
||||
"troubleshoot": "Fejlfinding",
|
||||
"type": "Type",
|
||||
"unable_to_change_pin_code": "Kunne ikke ændre PIN kode",
|
||||
"unable_to_check_version": "Kan ikke tjekke app- eller serverversion",
|
||||
"unable_to_setup_pin_code": "Kunne ikke sætte PIN kode",
|
||||
"unarchive": "Af Akivér",
|
||||
"unarchive": "Afakivér",
|
||||
"unarchive_action_prompt": "{count} slettet fra Arkiv",
|
||||
"unarchived_count": "{count, plural, other {Uarkiveret #}}",
|
||||
"undo": "Fortryd",
|
||||
@@ -2210,7 +2167,6 @@
|
||||
"welcome": "Velkommen",
|
||||
"welcome_to_immich": "Velkommen til Immich",
|
||||
"wifi_name": "Wi-Fi navn",
|
||||
"workflow": "Arbejdsproces",
|
||||
"wrong_pin_code": "Forkert PIN kode",
|
||||
"year": "År",
|
||||
"years_ago": "{years, plural, one {# år} other {# år}} siden",
|
||||
|
||||
109
i18n/de.json
109
i18n/de.json
@@ -7,7 +7,6 @@
|
||||
"action_common_update": "Aktualisieren",
|
||||
"actions": "Aktionen",
|
||||
"active": "Aktiv",
|
||||
"active_count": "Aktive:{count}",
|
||||
"activity": "Aktivität",
|
||||
"activity_changed": "Aktivität ist {enabled, select, true {aktiviert} other {deaktiviert}}",
|
||||
"add": "Hinzufügen",
|
||||
@@ -18,6 +17,7 @@
|
||||
"add_birthday": "Geburtsdatum hinzufügen",
|
||||
"add_endpoint": "Endpunkt hinzufügen",
|
||||
"add_exclusion_pattern": "Ausschlussmuster hinzufügen",
|
||||
"add_import_path": "Importpfad hinzufügen",
|
||||
"add_location": "Standort hinzufügen",
|
||||
"add_more_users": "Weitere Nutzer hinzufügen",
|
||||
"add_partner": "Partner hinzufügen",
|
||||
@@ -32,7 +32,6 @@
|
||||
"add_to_album_toggle": "Auswahl umschalten für {album}",
|
||||
"add_to_albums": "Zu Alben hinzufügen",
|
||||
"add_to_albums_count": "Zu Alben hinzufügen ({count})",
|
||||
"add_to_bottom_bar": "Hinzufügen zu",
|
||||
"add_to_shared_album": "Zu geteiltem Album hinzufügen",
|
||||
"add_upload_to_stack": "Upload zum Stapel hinzufügen",
|
||||
"add_url": "URL hinzufügen",
|
||||
@@ -68,7 +67,6 @@
|
||||
"confirm_reprocess_all_faces": "Bist du sicher, dass du alle Gesichter erneut verarbeiten möchtest? Dies löscht auch alle bereits benannten Personen.",
|
||||
"confirm_user_password_reset": "Bist du sicher, dass du das Passwort für {user} zurücksetzen möchtest?",
|
||||
"confirm_user_pin_code_reset": "Bist du sicher, dass du den PIN-Code von {user} zurücksetzen möchtest?",
|
||||
"copy_config_to_clipboard_description": "Kopieren Sie die aktuelle Systemkonfiguration als JSON-Objekt in die Zwischenablage",
|
||||
"create_job": "Aufgabe erstellen",
|
||||
"cron_expression": "Cron-Zeitangabe",
|
||||
"cron_expression_description": "Setze das Scanintervall im Cron-Format. Hilfe mit dem Format bietet dir dabei z. B. der <link>Crontab Guru</link>",
|
||||
@@ -76,11 +74,10 @@
|
||||
"disable_login": "Login deaktivieren",
|
||||
"duplicate_detection_job_description": "Diese Aufgabe führt das maschinelle Lernen für jede Datei aus, um Duplikate zu finden. Diese Aufgabe beruht auf der intelligenten Suche",
|
||||
"exclusion_pattern_description": "Mit Ausschlussmustern können Dateien und Ordner beim Scannen Ihrer Bibliothek ignoriert werden. Dies ist nützlich, wenn du Ordner hast, die Dateien enthalten, die du nicht importieren möchtest, wie z. B. RAW-Dateien.",
|
||||
"export_config_as_json_description": "Laden Sie die aktuelle Systemkonfiguration als JSON-Datei herunter",
|
||||
"external_libraries_page_description": "Externe Bibliotheksseite für Administratoren",
|
||||
"face_detection": "Gesichtserfassung",
|
||||
"face_detection_description": "Diese Aufgabe erfasst Gesichter in Dateien mittels maschinellen Lernens. Bei Videos wird nur die Miniaturansicht verwendet. „Aktualisieren“ verarbeitet alle Dateien neu. „Zurücksetzen“ setzt zusätzlich alle Gesichter zurück. „Fehlende“ stellt nur nicht verarbeitete Dateien in die Warteschlange. Erfasste Gesichter werden zur Gesichtsidentifizierung in die Warteschlange gestellt, um sie in bestehende oder neue Personen zu gruppieren.",
|
||||
"facial_recognition_job_description": "Diese Aufgabe gruppiert im Anschluss an die Gesichtserfassung die erfassten Gesichter zu Personen. „Zurücksetzen“ gruppiert alle Gesichter neu, während „Fehlende“ Gesichter ohne Zuordnung in die Warteschlange stellt.",
|
||||
"external_library_management": "Verwaltung externer Bibliotheken",
|
||||
"face_detection": "Gesichtserkennung",
|
||||
"face_detection_description": "Diese Aufgabe erkennt Gesichter in Dateien mittels maschinellen Lernens. Bei Videos wird nur die Miniaturansicht verwendet. „Aktualisieren“ verarbeitet alle Dateien neu. „Zurücksetzen“ setzt zusätzlich alle Gesichter zurück. „Fehlende“ stellt nur nicht verarbeitete Dateien in die Warteschlange. Erkannte Gesichter werden zur Gruppierung in bestehende oder neue Personen in die Warteschlange gestellt.",
|
||||
"facial_recognition_job_description": "Diese Aufgabe gruppiert im Anschluss an die Gesichtserkennung die erkannten Gesichter zu Personen. „Zurücksetzen“ gruppiert alle Gesichter neu, während „Fehlende“ Gesichter ohne Zuordnung in die Warteschlange stellt.",
|
||||
"failed_job_command": "Befehl {command} ist für Aufgabe {job} fehlgeschlagen",
|
||||
"force_delete_user_warning": "WARNUNG: Diese Aktion löscht sofort den Benutzer und all seine Dateien. Dies kann nicht rückgängig gemacht werden und die Dateien können nicht wiederhergestellt werden.",
|
||||
"image_format": "Format",
|
||||
@@ -105,28 +102,23 @@
|
||||
"image_thumbnail_description": "Kleine Miniaturansicht mit entfernten Metadaten, die bei der Anzeige von Sammlungen von Fotos wie der Zeitleiste verwendet wird",
|
||||
"image_thumbnail_quality_description": "Qualität der Miniaturansicht von 1-100. Höher ist besser, erzeugt aber größere Dateien und kann die Reaktionsfähigkeit der App beeinträchtigen.",
|
||||
"image_thumbnail_title": "Miniaturansicht-Einstellungen",
|
||||
"import_config_from_json_description": "Importieren Sie die Systemkonfiguration, indem Sie eine JSON-Konfigurationsdatei hochladen",
|
||||
"job_concurrency": "{job} (Anzahl gleichzeitiger Prozesse)",
|
||||
"job_created": "Aufgabe erstellt",
|
||||
"job_not_concurrency_safe": "Diese Aufgabe ist nicht parallelisierungssicher.",
|
||||
"job_settings": "Aufgabeneinstellungen",
|
||||
"job_settings_description": "Die gleichzeitige Ausführung von Aufgaben verwalten",
|
||||
"job_status": "Aufgabenstatus",
|
||||
"jobs_delayed": "{jobCount, plural, other {# verzögert}}",
|
||||
"jobs_failed": "{jobCount, plural, other {# fehlgeschlagen}}",
|
||||
"jobs_over_time": "Jobs im Laufe der Zeit",
|
||||
"library_created": "Bibliothek erstellt: {library}",
|
||||
"library_deleted": "Bibliothek gelöscht",
|
||||
"library_details": "Bibliotheksdetails",
|
||||
"library_folder_description": "Spezifiziere einen Ordner zum Importieren. Dieser Ordner wird einschließlich aller Unterordner nach Bildern und Videos durchsucht.",
|
||||
"library_remove_exclusion_pattern_prompt": "Bist du sicher, dass du dieses Ausschlussmuster entfernen möchtest?",
|
||||
"library_remove_folder_prompt": "Bist du sicher, dass du diesen Import-Ordner entfernen möchtest?",
|
||||
"library_import_path_description": "Gib einen Ordner für den Import an. Dieser Ordner, einschließlich der Unterordner, wird nach Bildern und Videos durchsucht.",
|
||||
"library_scanning": "Periodisches Scannen",
|
||||
"library_scanning_description": "Regelmäßiges Durchsuchen der Bibliothek einstellen",
|
||||
"library_scanning_enable_description": "Regelmäßiges Scannen der Bibliothek aktivieren",
|
||||
"library_settings": "Externe Bibliothek",
|
||||
"library_settings_description": "Einstellungen externer Bibliotheken verwalten",
|
||||
"library_tasks_description": "Überprüfe externe Bibliotheken auf neue und/oder veränderte Medien",
|
||||
"library_updated": "Aktualisierte Bibliothek",
|
||||
"library_watching_enable_description": "Überwache externe Bibliotheken auf Dateiänderungen",
|
||||
"library_watching_settings": "Überwache Bibliothek [EXPERIMENTELL]",
|
||||
"library_watching_settings_description": "Automatisch auf geänderte Dateien prüfen",
|
||||
@@ -134,7 +126,7 @@
|
||||
"logging_level_description": "Wenn aktiviert, welches Log-Level genutzt wird.",
|
||||
"logging_settings": "Protokollierung",
|
||||
"machine_learning_availability_checks": "Verfügbarkeitschecks",
|
||||
"machine_learning_availability_checks_description": "Erkenne und bevorzuge verfügbare Machine Learning Server",
|
||||
"machine_learning_availability_checks_description": "Erkenne und bevorzuge verfügbare Machine Learning Servers",
|
||||
"machine_learning_availability_checks_enabled": "Verfügbarkeitschecks einschalten",
|
||||
"machine_learning_availability_checks_interval": "Überprüfungsinterval",
|
||||
"machine_learning_availability_checks_interval_description": "Interval in Millisekunden zwischen Verfügbarkeitschecks",
|
||||
@@ -148,18 +140,18 @@
|
||||
"machine_learning_duplicate_detection_setting_description": "Verwendung von CLIP-Embeddings zum Erkennen möglicher Duplikate",
|
||||
"machine_learning_enabled": "Maschinelles Lernen aktivieren",
|
||||
"machine_learning_enabled_description": "Wenn diese Option deaktiviert ist, werden alle ML-Funktionen unabhängig von den unten aufgeführten Einstellungen deaktiviert.",
|
||||
"machine_learning_facial_recognition": "Gesichtserkennung",
|
||||
"machine_learning_facial_recognition_description": "Erfasse, identifiziere und gruppiere Gesichter in Bildern",
|
||||
"machine_learning_facial_recognition": "Gesichtsidentifizierung",
|
||||
"machine_learning_facial_recognition_description": "Erkenne, identifiziere und gruppiere Gesichter in Bildern",
|
||||
"machine_learning_facial_recognition_model": "Gesichtserkennungs-Modell",
|
||||
"machine_learning_facial_recognition_model_description": "Die Modelle sind in absteigender Reihenfolge ihrer Größe aufgeführt. Größere Modelle sind langsamer und verbrauchen mehr Speicher, liefern aber bessere Ergebnisse. Bitte beachte dabei, dass du die Gesichtserfassungsaufgabe für alle Bilder neu starten musst, wenn du ein Modell änderst.",
|
||||
"machine_learning_facial_recognition_model_description": "Die Modelle sind in absteigender Reihenfolge ihrer Größe aufgeführt. Größere Modelle sind langsamer und verbrauchen mehr Speicher, liefern aber bessere Ergebnisse. Bitte beachte dabei, dass du die Gesichtserkennungsaufgabe für alle Bilder neu starten musst, wenn du ein Modell änderst.",
|
||||
"machine_learning_facial_recognition_setting": "Gesichtserkennung aktivieren",
|
||||
"machine_learning_facial_recognition_setting_description": "Wenn diese Option deaktiviert ist, werden die Bilder nicht für die Gesichtserkennung kodiert und der Abschnitt „Personen“ auf der Seite „Erkunden“ wird nicht dargestellt.",
|
||||
"machine_learning_max_detection_distance": "Maximaler Erfassungsabstand",
|
||||
"machine_learning_max_detection_distance": "Maximaler Erkennungsabstand",
|
||||
"machine_learning_max_detection_distance_description": "Maximaler Unterschied zwischen zwei Bildern, um sie als Duplikate zu betrachten, im Bereich von 0,001-0,1. Bei höheren Werten werden mehr Duplikate erkannt, aber es kann zu falsch-positiven Ergebnissen kommen.",
|
||||
"machine_learning_max_recognition_distance": "Maximaler Erkennungsabstand",
|
||||
"machine_learning_max_recognition_distance_description": "Maximaler Abstand zwischen zwei Gesichtern, die als dieselbe Person angesehen werden, von 0-2. Ein niedrigerer Wert kann verhindern, dass zwei Personen als dieselbe Person eingestuft werden, während ein höherer Wert verhindern kann, dass ein und dieselbe Person als zwei verschiedene Personen eingestuft wird. Bitte beachte dabei, dass es einfacher ist, zwei Personen zu verschmelzen, als eine Person in zwei zu teilen, also wähle nach Möglichkeit einen niedrigeren Schwellenwert.",
|
||||
"machine_learning_min_detection_score": "Mindest-Erfassungswert",
|
||||
"machine_learning_min_detection_score_description": "Minimale Konfidenzrate für die Erfassung eines Gesichts von 0-1. Bei niedrigeren Werten werden mehr Gesichter erfasst, aber es kann zu falsch-positiven Ergebnissen kommen.",
|
||||
"machine_learning_min_detection_score": "Minimale Erkennungsrate",
|
||||
"machine_learning_min_detection_score_description": "Minimale Konfidenzrate für die Erkennung eines Gesichts von 0-1. Bei niedrigeren Werten werden mehr Gesichter erkannt, aber es kann zu falsch-positiven Ergebnissen kommen.",
|
||||
"machine_learning_min_recognized_faces": "Mindestens erkannte Gesichter",
|
||||
"machine_learning_min_recognized_faces_description": "Die Mindestanzahl von erkannten Gesichtern, damit eine Person erstellt werden kann. Eine Erhöhung dieses Wertes macht die Gesichtserkennung präziser, erhöht aber die Wahrscheinlichkeit, dass ein Gesicht nicht zu einer Person zugeordnet wird.",
|
||||
"machine_learning_ocr": "OCR",
|
||||
@@ -168,9 +160,9 @@
|
||||
"machine_learning_ocr_enabled_description": "Wenn deaktiviert, werden die Bilder nicht von der Texterkennung bearbeitet.",
|
||||
"machine_learning_ocr_max_resolution": "Maximale Auflösung",
|
||||
"machine_learning_ocr_max_resolution_description": "Vorschauen über dieser Auflösung werden unter Beibehaltung des Seitenverhältnisses verkleinert. Höhere Werte sind genauer, benötigen jedoch mehr Zeit für die Verarbeitung und verbrauchen mehr Speicher.",
|
||||
"machine_learning_ocr_min_detection_score": "Mindest-Erfassungswert",
|
||||
"machine_learning_ocr_min_detection_score": "Minimaler Erkennungswert",
|
||||
"machine_learning_ocr_min_detection_score_description": "Minimale Konfidenzrate für die Texterkennung von 0–1. Niedrigere Werte führen dazu, dass mehr Text erkannt wird, können jedoch zu falsch-positiven Ergebnissen führen.",
|
||||
"machine_learning_ocr_min_recognition_score": "Mindest-Erkennungswert",
|
||||
"machine_learning_ocr_min_recognition_score": "Minimale Erkennungsrate",
|
||||
"machine_learning_ocr_min_score_recognition_description": "Minimale Konfidenzrate für die Erkennung von erkanntem Text von 0–1. Niedrigere Werte führen dazu, dass mehr Text erkannt wird, können jedoch zu falsch-positiven Ergebnissen führen.",
|
||||
"machine_learning_ocr_model": "OCR Modell",
|
||||
"machine_learning_ocr_model_description": "Server Modelle sind genauer als mobile Modelle, brauchen aber länger zur Verarbeitung und brauchen mehr Speicher.",
|
||||
@@ -181,12 +173,7 @@
|
||||
"machine_learning_smart_search_enabled": "Intelligente Suche aktivieren",
|
||||
"machine_learning_smart_search_enabled_description": "Ist diese Option deaktiviert, werden die Bilder nicht für die intelligente Suche verwendet.",
|
||||
"machine_learning_url_description": "Die URL des Servers für maschinelles Lernen. Wenn mehr als eine URL angegeben wird, wird jeder Server einzeln ausprobiert, bis einer erfolgreich antwortet, und zwar in der Reihenfolge vom ersten bis zum letzten. Server die nicht antworten werden temporär ignoriert, bis sie wieder verfügbar sind.",
|
||||
"maintenance_settings": "Wartung",
|
||||
"maintenance_settings_description": "Immich in den Wartungsmodus versetzen.",
|
||||
"maintenance_start": "Wartungsmodus starten",
|
||||
"maintenance_start_error": "Wartungsmodus konnte nicht gestartet werden.",
|
||||
"manage_concurrency": "Gleichzeitige Ausführungen verwalten",
|
||||
"manage_concurrency_description": "Navigieren Sie zur Job-Seite, um die Job-Parallelität zu verwalten",
|
||||
"manage_log_settings": "Log-Einstellungen verwalten",
|
||||
"map_dark_style": "Dunkler Stil",
|
||||
"map_enable_description": "Kartenfunktionen aktivieren",
|
||||
@@ -211,7 +198,7 @@
|
||||
"metadata_settings_description": "Metadaten-Einstellungen verwalten",
|
||||
"migration_job": "Migration",
|
||||
"migration_job_description": "Diese Aufgabe migriert Miniaturansichten für Dateien und Gesichter in die neueste Ordnerstruktur",
|
||||
"nightly_tasks_cluster_faces_setting_description": "Gesichtsidentifizierung auf neu erfassten Gesichtern ausführen",
|
||||
"nightly_tasks_cluster_faces_setting_description": "Gesichtsidentifikation auf neu erkannten Gesichtern ausführen",
|
||||
"nightly_tasks_cluster_new_faces_setting": "Neue Gesichter gruppieren",
|
||||
"nightly_tasks_database_cleanup_setting": "Datenbankbereinigungs-Aufgaben",
|
||||
"nightly_tasks_database_cleanup_setting_description": "Alte, abgelaufene Daten aus der Datenbank bereinigen",
|
||||
@@ -276,14 +263,10 @@
|
||||
"password_settings_description": "Passwort-Anmeldeeinstellungen verwalten",
|
||||
"paths_validated_successfully": "Alle Pfade erfolgreich überprüft",
|
||||
"person_cleanup_job": "Personen aufräumen",
|
||||
"queue_details": "Warteschlangendetails",
|
||||
"queues": "Auftrags-Warteschlangen",
|
||||
"queues_page_description": "Auftragswarteschlangen-Adminseite",
|
||||
"quota_size_gib": "Kontingent (GiB)",
|
||||
"refreshing_all_libraries": "Alle Bibliotheken aktualisieren",
|
||||
"registration": "Admin-Registrierung",
|
||||
"registration_description": "Da du der erste Benutzer im System bist, wird dir die Rolle des Administrators zugewiesen, womit du für die Verwaltungsaufgaben verantwortlich bist. Weitere Benutzer werden von dir erstellt.",
|
||||
"remove_failed_jobs": "Entferne fehlgeschlagene Aufgaben",
|
||||
"require_password_change_on_login": "Benutzer muss das Passwort beim ersten Login ändern",
|
||||
"reset_settings_to_default": "Einstellungen auf Standard zurücksetzen",
|
||||
"reset_settings_to_recent_saved": "Einstellungen auf die zuletzt gespeicherten Einstellungen zurücksetzen",
|
||||
@@ -296,10 +279,8 @@
|
||||
"server_public_users_description": "Beim Hinzufügen eines Benutzers zu freigegebenen Alben werden alle Benutzer (Name und E-Mail) aufgelistet. Wenn diese Option deaktiviert ist, steht die Benutzerliste nur Administratoren zur Verfügung.",
|
||||
"server_settings": "Servereinstellungen",
|
||||
"server_settings_description": "Servereinstellungen verwalten",
|
||||
"server_stats_page_description": "Server Statistikseite für Administratoren",
|
||||
"server_welcome_message": "Willkommensnachricht",
|
||||
"server_welcome_message_description": "Eine Mitteilung, welche auf der Anmeldeseite angezeigt wird.",
|
||||
"settings_page_description": "Seite mit den Admin-Einstellungen",
|
||||
"sidecar_job": "Sidecar Metadaten",
|
||||
"sidecar_job_description": "Durch diese Aufgabe werden Filialdatei-Metadaten im Dateisystem entdeckt oder synchronisiert",
|
||||
"slideshow_duration_description": "Dauer der Anzeige jedes Bildes in Sekunden",
|
||||
@@ -418,8 +399,7 @@
|
||||
"user_restore_scheduled_removal": "Wiederherstellung des Benutzers - geplante Entfernung am {date, date, long}",
|
||||
"user_settings": "Benutzereinstellungen",
|
||||
"user_settings_description": "Benutzereinstellungen verwalten",
|
||||
"user_successfully_removed": "Der Benutzer {email} wurde erfolgreich entfernt.",
|
||||
"users_page_description": "Administrator-Benutzerseite",
|
||||
"user_successfully_removed": "Benutzer {email} wurde erfolgreich entfernt.",
|
||||
"version_check_enabled_description": "Versionsprüfung aktivieren",
|
||||
"version_check_implications": "Die Funktion zur Versionsprüfung basiert auf regelmäßiger Kommunikation mit GitHub.com",
|
||||
"version_check_settings": "Versionsprüfung",
|
||||
@@ -450,7 +430,6 @@
|
||||
"age_months": "Alter {months, plural, one {# Monat} other {# Monate}}",
|
||||
"age_year_months": "Alter 1 Jahr, {months, plural, one {# Monat} other {# Monate}}",
|
||||
"age_years": "Alter {years, plural, one {# Jahr} other {# Jahre}}",
|
||||
"album": "Album",
|
||||
"album_added": "Album hinzugefügt",
|
||||
"album_added_notification_setting_description": "Erhalte eine E-Mail-Benachrichtigung, wenn du zu einem freigegebenen Album hinzugefügt wurdest",
|
||||
"album_cover_updated": "Album-Cover aktualisiert",
|
||||
@@ -496,7 +475,6 @@
|
||||
"allow_edits": "Bearbeiten erlauben",
|
||||
"allow_public_user_to_download": "Erlaube öffentlichen Benutzern, herunterzuladen",
|
||||
"allow_public_user_to_upload": "Erlaube öffentlichen Benutzern, hochzuladen",
|
||||
"allowed": "Erlaubt",
|
||||
"alt_text_qr_code": "QR-Code Bild",
|
||||
"anti_clockwise": "Gegen den Uhrzeigersinn",
|
||||
"api_key": "API-Schlüssel",
|
||||
@@ -740,7 +718,6 @@
|
||||
"collapse_all": "Alle zusammenklappen",
|
||||
"color": "Farbe",
|
||||
"color_theme": "Farb-Theme",
|
||||
"command": "Befehl",
|
||||
"comment_deleted": "Kommentar gelöscht",
|
||||
"comment_options": "Kommentaroptionen",
|
||||
"comments_and_likes": "Kommentare & Likes",
|
||||
@@ -917,6 +894,8 @@
|
||||
"edit_description_prompt": "Bitte wähle eine neue Beschreibung:",
|
||||
"edit_exclusion_pattern": "Ausschlussmuster bearbeiten",
|
||||
"edit_faces": "Gesichter bearbeiten",
|
||||
"edit_import_path": "Importpfad bearbeiten",
|
||||
"edit_import_paths": "Importpfade bearbeiten",
|
||||
"edit_key": "Schlüssel bearbeiten",
|
||||
"edit_link": "Link bearbeiten",
|
||||
"edit_location": "Standort bearbeiten",
|
||||
@@ -988,8 +967,8 @@
|
||||
"failed_to_stack_assets": "Dateien konnten nicht gestapelt werden",
|
||||
"failed_to_unstack_assets": "Dateien konnten nicht entstapelt werden",
|
||||
"failed_to_update_notification_status": "Benachrichtigungsstatus aktualisieren fehlgeschlagen",
|
||||
"import_path_already_exists": "Dieser Importpfad existiert bereits.",
|
||||
"incorrect_email_or_password": "Ungültige E-Mail oder Passwort",
|
||||
"library_folder_already_exists": "Dieser Importpfad existiert bereits.",
|
||||
"paths_validation_failed": "{paths, plural, one {# Pfad konnte} other {# Pfade konnten}} nicht validiert werden",
|
||||
"profile_picture_transparent_pixels": "Profilbilder dürfen keine transparenten Pixel haben. Bitte zoome heran und/oder verschiebe das Bild.",
|
||||
"quota_higher_than_disk_size": "Dein festgelegtes Kontingent ist größer als der verfügbare Speicher",
|
||||
@@ -998,6 +977,7 @@
|
||||
"unable_to_add_assets_to_shared_link": "Datei konnte nicht zum geteilten Link hinzugefügt werden",
|
||||
"unable_to_add_comment": "Es kann kein Kommentar hinzufügt werden",
|
||||
"unable_to_add_exclusion_pattern": "Ausschlussmuster konnte nicht hinzugefügt werden",
|
||||
"unable_to_add_import_path": "Importpfad konnte nicht hinzugefügt werden",
|
||||
"unable_to_add_partners": "Es können keine Partner hinzufügt werden",
|
||||
"unable_to_add_remove_archive": "Datei konnte nicht {archived, select, true {aus dem Archiv entfernt} other {zum Archiv hinzugefügt}} werden",
|
||||
"unable_to_add_remove_favorites": "Datei konnte nicht {favorite, select, true {von den Favoriten entfernt} other {zu den Favoriten hinzugefügt}} werden",
|
||||
@@ -1020,10 +1000,12 @@
|
||||
"unable_to_delete_asset": "Datei konnte nicht gelöscht werden",
|
||||
"unable_to_delete_assets": "Fehler beim Löschen von Dateien",
|
||||
"unable_to_delete_exclusion_pattern": "Ausschlussmuster konnte nicht gelöscht werden",
|
||||
"unable_to_delete_import_path": "Importpfad konnte nicht gelöscht werden",
|
||||
"unable_to_delete_shared_link": "Geteilter Link kann nicht gelöscht werden",
|
||||
"unable_to_delete_user": "Nutzer konnte nicht gelöscht werden",
|
||||
"unable_to_download_files": "Dateien konnten nicht heruntergeladen werden",
|
||||
"unable_to_edit_exclusion_pattern": "Ausschlussmuster konnte nicht bearbeitet werden",
|
||||
"unable_to_edit_import_path": "Importpfad konnte nicht bearbeitet werden",
|
||||
"unable_to_empty_trash": "Papierkorb konnte nicht geleert werden",
|
||||
"unable_to_enter_fullscreen": "Vollbildmodus kann nicht aktiviert werden",
|
||||
"unable_to_exit_fullscreen": "Vollbildmodus kann nicht deaktiviert werden",
|
||||
@@ -1074,7 +1056,6 @@
|
||||
"unable_to_update_user": "Der Nutzer konnte nicht aktualisiert werden",
|
||||
"unable_to_upload_file": "Datei konnte nicht hochgeladen werden"
|
||||
},
|
||||
"exclusion_pattern": "Ausschlussmuster",
|
||||
"exif": "EXIF",
|
||||
"exif_bottom_sheet_description": "Beschreibung hinzufügen...",
|
||||
"exif_bottom_sheet_description_error": "Fehler bei der Aktualisierung der Beschreibung",
|
||||
@@ -1105,7 +1086,6 @@
|
||||
"external_network_sheet_info": "Wenn sich die App nicht im bevorzugten WLAN-Netzwerk befindet, verbindet sie sich mit dem Server über die erste der folgenden URLs, die sie erreichen kann (von oben nach unten)",
|
||||
"face_unassigned": "Nicht zugewiesen",
|
||||
"failed": "Fehlgeschlagen",
|
||||
"failed_count": "Fehlgeschlagen: {count}",
|
||||
"failed_to_authenticate": "Authentifizierung fehlgeschlagen",
|
||||
"failed_to_load_assets": "Laden der Assets fehlgeschlagen",
|
||||
"failed_to_load_folder": "Fehler beim Laden des Ordners",
|
||||
@@ -1135,7 +1115,6 @@
|
||||
"folders_feature_description": "Durchsuchen der Ordneransicht für Fotos und Videos im Dateisystem",
|
||||
"forgot_pin_code_question": "PIN-Code vergessen?",
|
||||
"forward": "Vorwärts",
|
||||
"full_path": "Vollständiger Pfad: {path}",
|
||||
"gcast_enabled": "Google Cast",
|
||||
"gcast_enabled_description": "Diese Funktion lädt externe Quellen von Google, um zu funktionieren.",
|
||||
"general": "Allgemein",
|
||||
@@ -1172,7 +1151,6 @@
|
||||
"hide_named_person": "Person {name} verbergen",
|
||||
"hide_password": "Passwort verbergen",
|
||||
"hide_person": "Person verbergen",
|
||||
"hide_text_recognition": "Texterkennung verbergen",
|
||||
"hide_unnamed_people": "Unbenannte Personen verbergen",
|
||||
"home_page_add_to_album_conflicts": "{added} Elemente zu {album} hinzugefügt. {failed} Elemente sind bereits vorhanden.",
|
||||
"home_page_add_to_album_err_local": "Es können lokale Elemente noch nicht zu Alben hinzugefügt werden, überspringen",
|
||||
@@ -1218,8 +1196,6 @@
|
||||
"import_path": "Importpfad",
|
||||
"in_albums": "In {count, plural, one {# Album} other {# Alben}}",
|
||||
"in_archive": "Im Archiv",
|
||||
"in_year": "Im Jahr {year}",
|
||||
"in_year_selector": "Im Jahr",
|
||||
"include_archived": "Archivierte Dateien einbeziehen",
|
||||
"include_shared_albums": "Freigegebene Alben einbeziehen",
|
||||
"include_shared_partner_assets": "Geteilte Partner-Dateien mit einbeziehen",
|
||||
@@ -1256,7 +1232,6 @@
|
||||
"language_setting_description": "Wähle deine bevorzugte Sprache",
|
||||
"large_files": "Große Dateien",
|
||||
"last": "Letzte",
|
||||
"last_months": "{count, plural, one {Letzter Monat} other {Letzte # Monate}}",
|
||||
"last_seen": "Zuletzt gesehen",
|
||||
"latest_version": "Aktuelle Version",
|
||||
"latitude": "Breitengrad",
|
||||
@@ -1266,8 +1241,6 @@
|
||||
"let_others_respond": "Antworten zulassen",
|
||||
"level": "Level",
|
||||
"library": "Bibliothek",
|
||||
"library_add_folder": "Ordner hinzufügen",
|
||||
"library_edit_folder": "Ordner bearbeiten",
|
||||
"library_options": "Bibliotheksoptionen",
|
||||
"library_page_device_albums": "Alben auf dem Gerät",
|
||||
"library_page_new_album": "Neues Album",
|
||||
@@ -1328,7 +1301,7 @@
|
||||
"login_form_server_empty": "Serveradresse eingeben.",
|
||||
"login_form_server_error": "Es Konnte sich nicht mit dem Server verbunden werden.",
|
||||
"login_has_been_disabled": "Die Anmeldung wurde deaktiviert.",
|
||||
"login_password_changed_error": "Fehler beim Ändern deines Passwortes",
|
||||
"login_password_changed_error": "Fehler beim Ändern deines Passwort",
|
||||
"login_password_changed_success": "Passwort erfolgreich geändert",
|
||||
"logout_all_device_confirmation": "Bist du sicher, dass du alle Geräte abmelden willst?",
|
||||
"logout_this_device_confirmation": "Bist du sicher, dass du dieses Gerät abmelden willst?",
|
||||
@@ -1339,17 +1312,8 @@
|
||||
"loop_videos_description": "Aktiviere diese Option, um eine automatische Videoschleife in der Detailansicht zu erstellen.",
|
||||
"main_branch_warning": "Du benutzt eine Entwicklungsversion. Wir empfehlen dringend, eine Release-Version zu verwenden!",
|
||||
"main_menu": "Hauptmenü",
|
||||
"maintenance_description": "Immich wurde in den <link>Wartungsmodus</link> versetzt.",
|
||||
"maintenance_end": "Wartungsmodus beenden",
|
||||
"maintenance_end_error": "Wartungsmodus konnte nicht beendet werden.",
|
||||
"maintenance_logged_in_as": "Aktuell angemeldet als {user}",
|
||||
"maintenance_title": "Vorrübergehend nicht verfügbar",
|
||||
"make": "Marke",
|
||||
"manage_geolocation": "Standort verwalten",
|
||||
"manage_media_access_rationale": "Diese Berechtigung wird benötigt, um Dateien ordnungsgemäß in den Papierkorb schieben und daraus wiederherstellen zu können.",
|
||||
"manage_media_access_settings": "Einstellungen öffnen",
|
||||
"manage_media_access_subtitle": "Erlaube Immich, Mediendateien zu verwalten und zu verschieben.",
|
||||
"manage_media_access_title": "Verwaltung von Mediendateien",
|
||||
"manage_shared_links": "Freigegebene Links verwalten",
|
||||
"manage_sharing_with_partners": "Gemeinsame Nutzung mit Partnern verwalten",
|
||||
"manage_the_app_settings": "App-Einstellungen verwalten",
|
||||
@@ -1397,7 +1361,7 @@
|
||||
"menu": "Menü",
|
||||
"merge": "Zusammenführen",
|
||||
"merge_people": "Personen zusammenführen",
|
||||
"merge_people_limit": "Du kannst maximal 5 Gesichter auf einmal zusammenführen",
|
||||
"merge_people_limit": "Du kannst nur bis zu 5 Gesichter auf einmal zusammenführen",
|
||||
"merge_people_prompt": "Willst du diese Personen zusammenführen? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"merge_people_successfully": "Personen erfolgreich zusammengeführt",
|
||||
"merged_people_count": "{count, plural, one {# Person} other {# Personen}} zusammengefügt",
|
||||
@@ -1413,7 +1377,6 @@
|
||||
"more": "Mehr",
|
||||
"move": "Verschieben",
|
||||
"move_off_locked_folder": "Aus dem gesperrten Ordner verschieben",
|
||||
"move_to": "Verschieben nach",
|
||||
"move_to_lock_folder_action_prompt": "{count} zum gesperrten Ordner hinzugefügt",
|
||||
"move_to_locked_folder": "In den gesperrten Ordner verschieben",
|
||||
"move_to_locked_folder_confirmation": "Diese Fotos und Videos werden aus allen Alben entfernt und können nur noch im gesperrten Ordner angezeigt werden",
|
||||
@@ -1443,7 +1406,6 @@
|
||||
"new_pin_code": "Neuer PIN-Code",
|
||||
"new_pin_code_subtitle": "Dies ist dein erster Zugriff auf den gesperrten Ordner. Erstelle einen PIN-Code für den sicheren Zugriff auf diese Seite",
|
||||
"new_timeline": "Neue Zeitleiste",
|
||||
"new_update": "Neues Update",
|
||||
"new_user_created": "Neuer Benutzer wurde erstellt",
|
||||
"new_version_available": "NEUE VERSION VERFÜGBAR",
|
||||
"newest_first": "Neueste zuerst",
|
||||
@@ -1459,14 +1421,12 @@
|
||||
"no_cast_devices_found": "Keine Geräte zum Übertragen gefunden",
|
||||
"no_checksum_local": "Prüfsumme nicht verfügbar - kann lokale Datei/en nicht laden",
|
||||
"no_checksum_remote": "Prüfsumme nicht verfügbar - kann entfernte Datei/en nicht laden",
|
||||
"no_devices": "Keine verwendeten Geräte",
|
||||
"no_duplicates_found": "Es wurden keine Duplikate gefunden.",
|
||||
"no_exif_info_available": "Keine EXIF-Informationen vorhanden",
|
||||
"no_explore_results_message": "Lade weitere Fotos hoch, um deine Sammlung zu erkunden.",
|
||||
"no_favorites_message": "Füge Favoriten hinzu, um deine besten Bilder und Videos schnell zu finden",
|
||||
"no_libraries_message": "Eine externe Bibliothek erstellen, um deine Fotos und Videos anzusehen",
|
||||
"no_local_assets_found": "Keine lokale Datei mit dieser Prüfsumme gefunden",
|
||||
"no_location_set": "Kein Standort festgelegt",
|
||||
"no_locked_photos_message": "Fotos und Videos im gesperrten Ordner sind versteckt und werden nicht angezeigt, wenn du deine Bibliothek durchsuchst.",
|
||||
"no_name": "Kein Name",
|
||||
"no_notifications": "Keine Benachrichtigungen",
|
||||
@@ -1477,7 +1437,6 @@
|
||||
"no_results_description": "Versuche es mit einem Synonym oder einem allgemeineren Stichwort",
|
||||
"no_shared_albums_message": "Erstelle ein Album, um Fotos und Videos mit Personen in deinem Netzwerk zu teilen",
|
||||
"no_uploads_in_progress": "Kein Upload in Bearbeitung",
|
||||
"not_allowed": "Nicht erlaubt",
|
||||
"not_available": "N/A",
|
||||
"not_in_any_album": "In keinem Album",
|
||||
"not_selected": "Nicht ausgewählt",
|
||||
@@ -1492,7 +1451,7 @@
|
||||
"notifications": "Benachrichtigungen",
|
||||
"notifications_setting_description": "Benachrichtigungen verwalten",
|
||||
"oauth": "OAuth",
|
||||
"obtainium_configurator": "Obtainium Konfigurator",
|
||||
"obtainium_configurator": "Obtainium Konfiguratior",
|
||||
"obtainium_configurator_instructions": "Du kannst Obtainium benutzen, um die App direkt aus den Github Releases zu installieren oder zu aktualisieren. Bitte erstelle dazu einen API-Schlüssel und wähle eine Variante aus um einen Obtainium-Konfigurationslink zu erstellen",
|
||||
"ocr": "OCR",
|
||||
"official_immich_resources": "Offizielle Immich Quellen",
|
||||
@@ -1526,7 +1485,6 @@
|
||||
"other_variables": "Sonstige Variablen",
|
||||
"owned": "Eigenes",
|
||||
"owner": "Besitzer",
|
||||
"page": "Seite",
|
||||
"partner": "Partner",
|
||||
"partner_can_access": "{partner} hat Zugriff",
|
||||
"partner_can_access_assets": "auf alle deine Fotos und Videos, außer die Archivierten und Gelöschten",
|
||||
@@ -1589,8 +1547,6 @@
|
||||
"photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}",
|
||||
"photos_from_previous_years": "Fotos von vorherigen Jahren",
|
||||
"pick_a_location": "Wähle einen Ort",
|
||||
"pick_custom_range": "Benutzerdefinierter Zeitraum",
|
||||
"pick_date_range": "Wähle einen Zeitraum",
|
||||
"pin_code_changed_successfully": "PIN-Code erfolgreich geändert",
|
||||
"pin_code_reset_successfully": "PIN-Code erfolgreich zurückgesetzt",
|
||||
"pin_code_setup_successfully": "PIN-Code erfolgreich festgelegt",
|
||||
@@ -1858,8 +1814,6 @@
|
||||
"server_offline": "Server offline",
|
||||
"server_online": "Server online",
|
||||
"server_privacy": "Privatsphäre auf dem Server",
|
||||
"server_restarting_description": "Diese Seite wird jeden Moment neu geladen.",
|
||||
"server_restarting_title": "Server wird neu gestartet",
|
||||
"server_stats": "Server-Statistiken",
|
||||
"server_update_available": "Server Update verfügbar",
|
||||
"server_version": "Server-Version",
|
||||
@@ -1983,7 +1937,6 @@
|
||||
"show_slideshow_transition": "Slideshow-Übergang anzeigen",
|
||||
"show_supporter_badge": "Unterstützerabzeichen",
|
||||
"show_supporter_badge_description": "Zeige Unterstützerabzeichen",
|
||||
"show_text_recognition": "Texterkennung anzeigen",
|
||||
"show_text_search_menu": "Zeige Menü für Textsuche",
|
||||
"shuffle": "Durchmischen",
|
||||
"sidebar": "Seitenleiste",
|
||||
@@ -2054,7 +2007,6 @@
|
||||
"tags": "Tags",
|
||||
"tap_to_run_job": "Tippen, um den Job zu starten",
|
||||
"template": "Vorlage",
|
||||
"text_recognition": "Texterkennung",
|
||||
"theme": "Theme",
|
||||
"theme_selection": "Themenauswahl",
|
||||
"theme_selection_description": "Automatische Einstellung des Themes auf Hell oder Dunkel, je nach Systemeinstellung des Browsers",
|
||||
@@ -2075,7 +2027,6 @@
|
||||
"third_party_resources": "Drittanbieter-Quellen",
|
||||
"time": "Zeit",
|
||||
"time_based_memories": "Zeitbasierte Erinnerungen",
|
||||
"time_based_memories_duration": "Anzahl der Sekunden, die jedes Bild angezeigt wird.",
|
||||
"timeline": "Zeitleiste",
|
||||
"timezone": "Zeitzone",
|
||||
"to_archive": "Archivieren",
|
||||
@@ -2087,7 +2038,6 @@
|
||||
"to_select": "zum Auswählen",
|
||||
"to_trash": "In den Papierkorb verschieben",
|
||||
"toggle_settings": "Einstellungen umschalten",
|
||||
"toggle_theme_description": "Design wechseln",
|
||||
"total": "Gesamt",
|
||||
"total_usage": "Gesamtnutzung",
|
||||
"trash": "Papierkorb",
|
||||
@@ -2196,7 +2146,6 @@
|
||||
"view_album": "Album anzeigen",
|
||||
"view_all": "Alles anzeigen",
|
||||
"view_all_users": "Alle Nutzer anzeigen",
|
||||
"view_asset_owners": "Dateibesitzer anzeigen",
|
||||
"view_details": "Details ansehen",
|
||||
"view_in_timeline": "In Zeitleiste anzeigen",
|
||||
"view_link": "Link anzeigen",
|
||||
@@ -2213,13 +2162,11 @@
|
||||
"viewer_unstack": "Stapel aufheben",
|
||||
"visibility_changed": "Sichtbarkeit für {count, plural, one {# Person} other {# Personen}} geändert",
|
||||
"waiting": "Wartend",
|
||||
"waiting_count": "In Warteschlage: {count}",
|
||||
"warning": "Warnung",
|
||||
"week": "Woche",
|
||||
"welcome": "Willkommen",
|
||||
"welcome_to_immich": "Willkommen bei Immich",
|
||||
"wifi_name": "WLAN-Name",
|
||||
"workflow": "Workflow",
|
||||
"wrong_pin_code": "PIN-Code falsch",
|
||||
"year": "Jahr",
|
||||
"years_ago": "Vor {years, plural, one {einem Jahr} other {# Jahren}}",
|
||||
@@ -2227,5 +2174,5 @@
|
||||
"you_dont_have_any_shared_links": "Du hast keine geteilten Links",
|
||||
"your_wifi_name": "Dein WLAN-Name",
|
||||
"zoom_image": "Bild vergrößern",
|
||||
"zoom_to_bounds": "Auf Grenzen zoomen"
|
||||
"zoom_to_bounds": "In die Grenzen zoomen"
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
133
i18n/el.json
133
i18n/el.json
@@ -7,17 +7,17 @@
|
||||
"action_common_update": "Ενημέρωση",
|
||||
"actions": "Ενέργειες",
|
||||
"active": "Ενεργά",
|
||||
"active_count": "Ενεργά: {count}",
|
||||
"activity": "Δραστηριότητα",
|
||||
"activity_changed": "Η δραστηριότητα είναι {enabled, select, true {ενεργοποιημένη} other {απενεργοποιημένη}}",
|
||||
"add": "Προσθήκη",
|
||||
"add_a_description": "Προσθήκη περιγραφής",
|
||||
"add_a_location": "Προσθήκη τοποθεσίας",
|
||||
"add_a_name": "Προσθήκη ενός ονόματος",
|
||||
"add_a_name": "Προσθήκη ονόματος",
|
||||
"add_a_title": "Προσθήκη τίτλου",
|
||||
"add_birthday": "Προσθήκη γενεθλίων",
|
||||
"add_endpoint": "Προσθήκη τελικού σημείου",
|
||||
"add_exclusion_pattern": "Προσθήκη μοτίβου αποκλεισμού",
|
||||
"add_import_path": "Προσθήκη μονοπατιού εισαγωγής",
|
||||
"add_location": "Προσθήκη τοποθεσίας",
|
||||
"add_more_users": "Προσθήκη επιπλέον χρηστών",
|
||||
"add_partner": "Προσθήκη συνεργάτη",
|
||||
@@ -32,7 +32,6 @@
|
||||
"add_to_album_toggle": "Εναλλαγή επιλογής για το {album}",
|
||||
"add_to_albums": "Προσθήκη στα άλμπουμ",
|
||||
"add_to_albums_count": "Προσθήκη στα άλμπουμ ({count})",
|
||||
"add_to_bottom_bar": "Προσθήκη σε",
|
||||
"add_to_shared_album": "Προσθήκη σε κοινόχρηστο άλμπουμ",
|
||||
"add_upload_to_stack": "Προσθήκη αρχείου στην ουρά",
|
||||
"add_url": "Προσθήκη Συνδέσμου",
|
||||
@@ -68,7 +67,6 @@
|
||||
"confirm_reprocess_all_faces": "Είστε βέβαιοι ότι θέλετε να επεξεργαστείτε ξανά όλα τα πρόσωπα; Αυτό θα εκκαθαρίσει ακόμα και τα άτομα στα οποία έχετε ήδη ορίσει το όνομα.",
|
||||
"confirm_user_password_reset": "Είστε βέβαιοι ότι θέλετε να επαναφέρετε τον κωδικό πρόσβασης του χρήστη {user};",
|
||||
"confirm_user_pin_code_reset": "Είστε βέβαιοι ότι θέλετε να επαναφέρετε τον κωδικό PIN του χρήστη {user};",
|
||||
"copy_config_to_clipboard_description": "Αντέγραψε την τρέχουσα διαμόρφωση συστήματος στο πρόχειρο, ως αντικείμενο JSON",
|
||||
"create_job": "Δημιουργία εργασίας",
|
||||
"cron_expression": "Σύνταξη Cron",
|
||||
"cron_expression_description": "Ορίστε το διάστημα σάρωσης χρησιμοποιώντας τη μορφή cron. Για περισσότερες πληροφορίες, ανατρέξτε π.χ. στο <link>Crontab Guru</link>",
|
||||
@@ -76,8 +74,7 @@
|
||||
"disable_login": "Απενεργοποίηση σύνδεσης",
|
||||
"duplicate_detection_job_description": "Εκτελέστε μηχανική μάθηση σε στοιχεία για να εντοπίσετε παρόμοιες εικόνες. Βασίζεται στην Έξυπνη Αναζήτηση",
|
||||
"exclusion_pattern_description": "Τα μοτίβα αποκλεισμού σας επιτρέπουν να αγνοείται αρχεία και φακέλους κατά τη σάρωση της βιβλιοθήκης σας. Αυτό είναι χρήσιμο εάν εχετε φακέλους που περιέχουν αρχεία που δεν θέλετε να εισάγετε, όπως αρχεία RAW.",
|
||||
"export_config_as_json_description": "Κατέβασε την τρέχουσα διαμόρφωση συστήματος ως αρχείο JSON",
|
||||
"external_libraries_page_description": "Σελίδα εξωτερικής βιβλιοθήκης διαχειριστή",
|
||||
"external_library_management": "Διαχείριση Εξωτερικών Βιβλιοθηκών",
|
||||
"face_detection": "Ανίχνευση προσώπου",
|
||||
"face_detection_description": "Ανιχνεύστε τα πρόσωπα σε στοιχεία χρησιμοποιώντας μηχανική μάθηση. Για βίντεο, λαμβάνεται υπόψη μόνο η μικρογραφία. Η επιλογή \"Ανανέωση\" επεξεργάζεται εκ νέου όλα τα στοιχεία. Η επιλογή \"Επαναφορά\", επιπλέον εκκαθαρίζει όλα τα δεδομένα προσώπου. Η επιλογή \"Ελλείποντα\" προσθέτει στην ουρά στοιχεία που δεν έχουν υποστεί ακόμη επεξεργασία. Τα πρόσωπα που έχουν εντοπιστεί θα μπουν στην ουρά για την Αναγνώριση Προσώπου μετά την ολοκλήρωση της Ανίχνευσης Προσώπου, ομαδοποιώντας τα σε υπάρχοντα ή νέα άτομα.",
|
||||
"facial_recognition_job_description": "Ομαδοποιήστε ανιχνευμένα πρόσωπα σε άτομα. Αυτό το βήμα εκτελείται αφού ολοκληρωθεί η Ανίχνευση Προσώπου. Η επιλογή \"Επαναφορά\" ομαδοποιεί εκ νέου όλα τα πρόσωπα. Η επιλογή \"Ελλείποντα\" βάζει στην ουρά για ομαδοποίηση πρόσωπα που δεν έχουν αντιστοιχηθεί σε κάποιο άτομο.",
|
||||
@@ -105,30 +102,25 @@
|
||||
"image_thumbnail_description": "Μικρογραφία εικόνας χωρίς μεταδεδομένα, που χρησιμοποιείται όταν προβάλλονται ομάδες φωτογραφιών όπως το κύριο χρονολόγιο",
|
||||
"image_thumbnail_quality_description": "Ποιότητα μικρογραφίας από 1 έως 100. Υψηλότερες τιμές είναι καλύτερες, αλλά παράγουν μεγαλύτερα αρχεία που μπορεί να μειώσουν την ταχύτητα απόκρισης της εφαρμογής.",
|
||||
"image_thumbnail_title": "Ρυθμίσεις Μικρογραφίας",
|
||||
"import_config_from_json_description": "Εισαγωγή διαμόρφωσης συστήματος με ανέβασμα αρχείου διαμόρφωσης JSON",
|
||||
"job_concurrency": "Ταυτόχρονη εκτέλεση {job}",
|
||||
"job_created": "Εργασία δημιουργήθηκε",
|
||||
"job_not_concurrency_safe": "Αυτή η εργασία δεν είναι ασφαλής για ταυτόχρονη εκτέλεση.",
|
||||
"job_settings": "Ρυθμίσεις Εργασίας",
|
||||
"job_settings_description": "Διαχείριση ταυτόχρονης εκτέλεσης εργασίας",
|
||||
"job_status": "Κατάσταση Εργασίας",
|
||||
"jobs_delayed": "{jobCount, plural, one {# καθυστέρησε} other {# καθυστέρησαν}}",
|
||||
"jobs_failed": "{jobCount, plural, one {# απέτυχε} other {# απέτυχαν}}",
|
||||
"jobs_over_time": "Εργασίες με την πάροδο του χρόνου",
|
||||
"library_created": "Δημιουργήθηκε η βιβλιοθήκη: {library}",
|
||||
"library_deleted": "Η βιβλιοθήκη διαγράφηκε",
|
||||
"library_details": "Λεπτομέρειες βιβλιοθήκης",
|
||||
"library_folder_description": "Καθόρισε έναν φάκελο για εισαγωγή. Ο φάκελος αυτός, μαζί με τους υποφακέλους του, θα σαρωθεί για εικόνες και βίντεο.",
|
||||
"library_remove_exclusion_pattern_prompt": "Είσαι σίγουρος ότι θέλεις να αφαιρέσεις αυτό το μοτίβο εξαίρεσης;",
|
||||
"library_remove_folder_prompt": "Είσαι σίγουρος ότι θέλεις να αφαιρέσεις αυτόν τον φάκελο εισαγωγής;",
|
||||
"library_import_path_description": "Καθορίστε έναν φάκελο για εισαγωγή. Αυτός ο φάκελος, συμπεριλαμβανομένων των υποφακέλων του, θα σαρωθεί για εικόνες και βίντεο.",
|
||||
"library_scanning": "Περιοδική Σάρωση",
|
||||
"library_scanning_description": "Ρύθμιση περιοδικής σάρωσης βιβλιοθήκης",
|
||||
"library_scanning_enable_description": "Ενεργοποίηση περιοδικής σάρωσης βιβλιοθήκης",
|
||||
"library_settings": "Εξωτερική Βιβλιοθήκη",
|
||||
"library_settings_description": "Διαχείριση ρυθμίσεων εξωτερικής βιβλιοθήκης",
|
||||
"library_tasks_description": "Σάρωση εξωτερικών βιβλιοθηκών για νέα ή/και αλλαγμένα στοιχεία",
|
||||
"library_updated": "Ενημερωμένη βιβλιοθήκη",
|
||||
"library_watching_enable_description": "Παρακολούθηση εξωτερικών βιβλιοθηκών για τροποποιήσεις αρχείων",
|
||||
"library_watching_settings": "Παρακολούθηση βιβλιοθήκης [ΠΕΙΡΑΜΑΤΙΚΟ]",
|
||||
"library_watching_settings": "Παρακολούθηση βιβλιοθήκης (ΠΕΙΡΑΜΑΤΙΚΟ)",
|
||||
"library_watching_settings_description": "Αυτόματη παρακολούθηση για τροποποιημένα αρχεία",
|
||||
"logging_enable_description": "Ενεργοποίηση καταγραφής συμβάντων",
|
||||
"logging_level_description": "Το επίπεδο καταγραφής συμβάντων που θα εφαρμοστεί, όταν αυτή είναι ενεργοποιημένη.",
|
||||
@@ -162,18 +154,6 @@
|
||||
"machine_learning_min_detection_score_description": "Ελάχιστο σκορ εμπιστοσύνης για ανίχνευση προσώπου από 0-1. Οι χαμηλότερες τιμές θα εντοπίσουν περισσότερα πρόσωπα, αλλά μπορεί να οδηγήσουν σε ψευδώς θετικά αποτελέσματα.",
|
||||
"machine_learning_min_recognized_faces": "Ελάχιστα αναγνωρισμένα πρόσωπα",
|
||||
"machine_learning_min_recognized_faces_description": "Ο ελάχιστος αριθμός αναγνωρισμένων προσώπων για ένα άτομο που θα δημιουργηθεί. Η αύξηση αυτή καθιστά την Αναγνώριση Προσώπου πιο ακριβή με το κόστος να αυξηθεί η πιθανότητα να μην εκχωρηθεί ένα πρόσωπο σε ένα άτομο.",
|
||||
"machine_learning_ocr": "Οπτική Αναγνώριση Κειμένου (OCR)",
|
||||
"machine_learning_ocr_description": "Χρησιμοποείστε τη μηχανική μάθηση για την αναγνώριση κειμένου σε εικόνες",
|
||||
"machine_learning_ocr_enabled": "Ενεργοποίηση OCR",
|
||||
"machine_learning_ocr_enabled_description": "Αν είναι απενεργοποιημένο, οι εικόνες δεν θα υποβληθούν σε αναγνώριση κειμένου.",
|
||||
"machine_learning_ocr_max_resolution": "Μέγιστη ανάλυση",
|
||||
"machine_learning_ocr_max_resolution_description": "Οι προεπισκοπήσεις πάνω από αυτήν την ανάλυση θα αλλάξουν μέγεθος διατηρώντας τις αναλογίες. Μεγαλύτερες τιμές είναι πιο ακριβείς, αλλά χρειάζονται περισσότερο χρόνο επεξεργασίας και μνήμη.",
|
||||
"machine_learning_ocr_min_detection_score": "Ελάχιστος βαθμός ανίχνευσης",
|
||||
"machine_learning_ocr_min_detection_score_description": "Ελάχιστος βαθμός εμπιστοσύνης για την ανίχνευση κειμένου από 0 έως 1. Χαμηλότερες τιμές θα ανιχνεύουν περισσότερο κείμενο, αλλά μπορεί να οδηγήσουν σε ψευδώς θετικά αποτελέσματα.",
|
||||
"machine_learning_ocr_min_recognition_score": "Ελάχιστος βαθμός αναγνώρισης",
|
||||
"machine_learning_ocr_min_score_recognition_description": "Ελάχιστος βαθμός εμπιστοσύνης για την αναγνώριση ανιχνευμένου κειμένου από 0 έως 1. Χαμηλότερες τιμές θα αναγνωρίζουν περισσότερο κείμενο, αλλά μπορεί να οδηγήσουν σε ψευδώς θετικά αποτελέσματα.",
|
||||
"machine_learning_ocr_model": "Μοντέλο OCR",
|
||||
"machine_learning_ocr_model_description": "Τα μοντέλα διακομιστή είναι πιο ακριβή από τα μοντέλα των κινητών, αλλά χρειάζονται περισσότερο χρόνο επεξεργασίας και χρησιμοποιούν περισσότερη μνήμη.",
|
||||
"machine_learning_settings": "Ρυθμίσεις Μηχανικής Μάθησης",
|
||||
"machine_learning_settings_description": "Διαχειριστείτε τις λειτουργίες και τις ρυθμίσεις μηχανικής μάθησης",
|
||||
"machine_learning_smart_search": "Έξυπνη Αναζήτηση",
|
||||
@@ -181,12 +161,7 @@
|
||||
"machine_learning_smart_search_enabled": "Ενεργοποίηση έξυπνης αναζήτησης",
|
||||
"machine_learning_smart_search_enabled_description": "Αν απενεργοποιηθεί, οι εικόνες δεν θα κωδικοποιούνται για έξυπνη αναζήτηση.",
|
||||
"machine_learning_url_description": "Η διεύθυνση URL του διακομιστή μηχανικής μάθησης. Αν δοθούν περισσότερες από μία διευθύνσεις URL, κάθε διακομιστής θα δοκιμάζεται διαδοχικά μέχρι να ανταποκριθεί ένας με επιτυχία, με τη σειρά από την πρώτη έως την τελευταία. Οι διακομιστές που δεν ανταποκρίνονται θα αγνοούνται προσωρινά μέχρι να επανέλθουν σε λειτουργία.",
|
||||
"maintenance_settings": "Συντήρηση",
|
||||
"maintenance_settings_description": "Θέστε το Immich σε λειτουργία συντήρησης.",
|
||||
"maintenance_start": "Έναρξη λειτουργίας συντήρησης",
|
||||
"maintenance_start_error": "Αποτυχία έναρξης λειτουργίας συντήρησης.",
|
||||
"manage_concurrency": "Διαχείριση ταυτόχρονη εκτέλεσης",
|
||||
"manage_concurrency_description": "Μεταβείτε στη σελίδα εργασιών για να διαχειριστείτε την ταυτόχρονη εκτέλεση εργασιών",
|
||||
"manage_log_settings": "Διαχείριση ρυθμίσεων αρχείου καταγραφής",
|
||||
"map_dark_style": "Σκούρο Θέμα",
|
||||
"map_enable_description": "Ενεργοποίηση λειτουργιών χάρτη",
|
||||
@@ -236,7 +211,6 @@
|
||||
"notification_email_ignore_certificate_errors_description": "Παράβλεψη σφαλμάτων επικύρωσης της πιστοποίησης TLS (δεν προτείνεται)",
|
||||
"notification_email_password_description": "Κωδικός για την αυθεντικοποίηση με τον server του email",
|
||||
"notification_email_port_description": "Θύρα του email server (πχ 25, 465, ή 587)",
|
||||
"notification_email_secure": "SMTPS (ασφαλές πρωτόκολλο αποστολής email)",
|
||||
"notification_email_secure_description": "Χρήση SMTPS (SMTP over TLS)",
|
||||
"notification_email_sent_test_email_button": "Αποστολή test email και αποθήκευση",
|
||||
"notification_email_setting_description": "Ρυθμίσεις για την αποστολή ειδοποιήσεων μέσω email",
|
||||
@@ -270,20 +244,15 @@
|
||||
"oauth_storage_quota_default_description": "Ποσοστό σε GiB που θα χρησιμοποιηθεί όταν δεν ορίζεται από τη δηλωμένη τιμή.",
|
||||
"oauth_timeout": "Χρονικό όριο Αιτήματος",
|
||||
"oauth_timeout_description": "Χρονικό όριο Αιτήματος σε milliseconds",
|
||||
"ocr_job_description": "Χρήση μηχανικής μάθησης για αναγνώριση κειμένου σε εικόνες",
|
||||
"password_enable_description": "Σύνδεση με ηλεκτρονικό ταχυδρομείο",
|
||||
"password_settings": "Σύνδεση με κωδικό",
|
||||
"password_settings_description": "Διαχείριση ρυθμίσεων σύνδεσης μέσω κωδικού πρόσβασης",
|
||||
"paths_validated_successfully": "Όλες οι διαδρομές επικυρώθηκαν επιτυχώς",
|
||||
"person_cleanup_job": "Καθαρισμός ατόμου",
|
||||
"queue_details": "Λεπτομέρειες ουράς",
|
||||
"queues": "Ουρές εργασιών",
|
||||
"queues_page_description": "Σελίδα ουρών εργασιών διαχειριστή",
|
||||
"quota_size_gib": "Μέγεθος ορίου (GiB)",
|
||||
"refreshing_all_libraries": "Επαναφόρτωση όλων των βιβλιοθηκών",
|
||||
"registration": "Εγγραφή Διαχειριστή",
|
||||
"registration_description": "Δεδομένου ότι είστε ο πρώτος χρήστης στο σύστημα, θα ανατεθείτε ως Διαχειριστής και θα είστε υπεύθυνος για τις διαχειριστικές εργασίες, ενώ οι επιπλέον χρήστες θα δημιουργούνται από εσάς.",
|
||||
"remove_failed_jobs": "Αφαίρεση αποτυχημένων εργασιών",
|
||||
"require_password_change_on_login": "Απαιτείται από τον χρήστη να αλλάξει τον κωδικό πρόσβασης κατά την πρώτη σύνδεση",
|
||||
"reset_settings_to_default": "Επαναφορά προεπιλεγμένων ρυθμίσεων",
|
||||
"reset_settings_to_recent_saved": "Επαναφορά ρυθμίσεων στις πρόσφατα αποθηκευμένες ρυθμίσεις",
|
||||
@@ -296,10 +265,8 @@
|
||||
"server_public_users_description": "Όλοι οι χρήστες (όνομα και email) εμφανίζονται κατά την προσθήκη ενός χρήστη σε κοινόχρηστα άλμπουμ. Όταν αυτή η επιλογή είναι απενεργοποιημένη, η λίστα χρηστών θα είναι διαθέσιμη μόνο στους διαχειριστές.",
|
||||
"server_settings": "Ρυθμίσεις Διακομιστή",
|
||||
"server_settings_description": "Διαχείριση ρυθμίσεων διακομιστή",
|
||||
"server_stats_page_description": "Σελίδα στατιστικών διακομιστή διαχειριστή",
|
||||
"server_welcome_message": "Μήνυμα καλωσορίσματος",
|
||||
"server_welcome_message_description": "Το μήνυμα που θα εμφανίζεται στη σελίδα σύνδεσης.",
|
||||
"settings_page_description": "Σελίδα ρυθμίσεων διαχειριστή",
|
||||
"sidecar_job": "Μεταδεδομένα συνοδευτικού αρχείου",
|
||||
"sidecar_job_description": "Ανακάλυψη ή συγχρονισμός των μεταδεδομένων του συνοδευτικού αρχείου από το σύστημα αρχείων",
|
||||
"slideshow_duration_description": "Αριθμός δευτερολέπτων για την εμφάνιση κάθε εικόνας",
|
||||
@@ -418,8 +385,7 @@
|
||||
"user_restore_scheduled_removal": "Αποκατάσταση χρήστη - προγραμματισμένη διαγραφή στις {date, date, long}",
|
||||
"user_settings": "Ρυθμίσεις χρήστη",
|
||||
"user_settings_description": "Διαχείριση ρυθμίσεων χρήστη",
|
||||
"user_successfully_removed": "Ο χρήστης {email} αφαιρέθηκε με επιτυχία.",
|
||||
"users_page_description": "Σελίδα χρηστών διαχειριστή",
|
||||
"user_successfully_removed": "Ο χρήστης {email} έχει αφαιρεθεί με επιτυχία.",
|
||||
"version_check_enabled_description": "Ενεργοποίηση ελέγχου έκδοσης",
|
||||
"version_check_implications": "Η λειτουργία ελέγχου έκδοσης, εξαρτάται από την περιοδική επικοινωνία με το github.com",
|
||||
"version_check_settings": "Έλεγχος Έκδοσης",
|
||||
@@ -437,11 +403,11 @@
|
||||
"advanced_settings_prefer_remote_subtitle": "Μερικές συσκευές αργούν πολύ να φορτώσουν μικρογραφίες από τοπικά αρχεία. Ενεργοποιήστε αυτήν τη ρύθμιση για να φορτώνονται αντί αυτού απομακρυσμένες εικόνες.",
|
||||
"advanced_settings_prefer_remote_title": "Προτίμηση απομακρυσμένων εικόνων",
|
||||
"advanced_settings_proxy_headers_subtitle": "Καθορισμός κεφαλίδων διακομιστή μεσολάβησης που το Immich πρέπει να στέλνει με κάθε αίτημα δικτύου",
|
||||
"advanced_settings_proxy_headers_title": "Προσαρμοσμένοι proxy headers [ΠΕΙΡΑΜΑΤΙΚΟ]",
|
||||
"advanced_settings_proxy_headers_title": "Κεφαλίδες διακομιστή μεσολάβησης",
|
||||
"advanced_settings_readonly_mode_subtitle": "Ενεργοποιεί τη λειτουργία μόνο-για-ανάγνωση, όπου οι φωτογραφίες μπορούν μόνο να προβληθούν. Ενέργειες όπως επιλογή πολλών εικόνων, κοινή χρήση, αποστολή (casting) και διαγραφή είναι απενεργοποιημένες. Η ενεργοποίηση/απενεργοποίηση της λειτουργίας μόνο-για-ανάγνωση γίνεται μέσω της εικόνας του χρήστη από την κεντρική οθόνη",
|
||||
"advanced_settings_readonly_mode_title": "Μόνο-για-ανάγνωση",
|
||||
"advanced_settings_readonly_mode_title": "Λειτουργία μόνο-για-ανάγνωση",
|
||||
"advanced_settings_self_signed_ssl_subtitle": "Παρακάμπτει τον έλεγχο πιστοποιητικού SSL του διακομιστή. Απαραίτητο για αυτο-υπογεγραμμένα πιστοποιητικά.",
|
||||
"advanced_settings_self_signed_ssl_title": "Να επιτρέπονται αυτο-υπογεγραμμένα πιστοποιητικά SSL [ΠΕΙΡΑΜΑΤΙΚΟ]",
|
||||
"advanced_settings_self_signed_ssl_title": "Να επιτρέπονται αυτο-υπογεγραμμένα πιστοποιητικά SSL",
|
||||
"advanced_settings_sync_remote_deletions_subtitle": "Αυτόματη διαγραφή ή επαναφορά ενός περιουσιακού στοιχείου σε αυτή τη συσκευή, όταν η ενέργεια αυτή πραγματοποιείται στο διαδίκτυο",
|
||||
"advanced_settings_sync_remote_deletions_title": "Συγχρονισμός απομακρυσμένων διαγραφών [ΠΕΙΡΑΜΑΤΙΚΟ]",
|
||||
"advanced_settings_tile_subtitle": "Ρυθμίσεις προχωρημένου χρήστη",
|
||||
@@ -450,7 +416,6 @@
|
||||
"age_months": "Η ηλικία {months, plural, one {# μήνας} other {# μήνες}}",
|
||||
"age_year_months": "Ηλικία, 1 έτος, {months, plural, one {# μήνας} other {# μήνες}}",
|
||||
"age_years": "{years, plural, other {Ηλικία #}}",
|
||||
"album": "Λεύκωμα",
|
||||
"album_added": "Το άλμπουμ, προστέθηκε",
|
||||
"album_added_notification_setting_description": "Λάβετε ειδοποίηση μέσω email όταν προστεθείτε σε ένα κοινόχρηστο άλμπουμ",
|
||||
"album_cover_updated": "Το εξώφυλλο του άλμπουμ, ενημερώθηκε",
|
||||
@@ -496,7 +461,6 @@
|
||||
"allow_edits": "Επιτρέψτε τις τροποποιήσεις",
|
||||
"allow_public_user_to_download": "Επιτρέψτε σε δημόσιο χρήστη να κατεβάσει",
|
||||
"allow_public_user_to_upload": "Επιτρέψτε στον δημόσιο χρήστη να ανεβάσει",
|
||||
"allowed": "Επιτρεπόμενο",
|
||||
"alt_text_qr_code": "Εικόνα κωδικού QR",
|
||||
"anti_clockwise": "Αντίθετα με τη φορά του ρολογιού",
|
||||
"api_key": "Κλειδί API",
|
||||
@@ -509,8 +473,6 @@
|
||||
"app_bar_signout_dialog_title": "Αποσύνδεση",
|
||||
"app_download_links": "Σύνδεσμοι Λήψης Εφαρμογής",
|
||||
"app_settings": "Ρυθμίσεις εφαρμογής",
|
||||
"app_stores": "Καταστήματα εφαρμογών",
|
||||
"app_update_available": "Υπάρχει διαθέσιμη ενημέρωση εφαρμογής",
|
||||
"appears_in": "Εμφανίζεται σε",
|
||||
"apply_count": "Εφαρμογή ({count, number})",
|
||||
"archive": "Αρχείο",
|
||||
@@ -594,7 +556,6 @@
|
||||
"backup_albums_sync": "Συγχρονισμός αντιγράφων ασφαλείας άλμπουμ",
|
||||
"backup_all": "Όλα",
|
||||
"backup_background_service_backup_failed_message": "Αποτυχία δημιουργίας αντιγράφων ασφαλείας. Επανάληψη…",
|
||||
"backup_background_service_complete_notification": "Ολοκλήρωση αντιγράφου ασφαλείας στοιχείων",
|
||||
"backup_background_service_connection_failed_message": "Αποτυχία σύνδεσης με το διακομιστή. Επανάληψη…",
|
||||
"backup_background_service_current_upload_notification": "Μεταφόρτωση {filename}",
|
||||
"backup_background_service_default_notification": "Έλεγχος για νέα στοιχεία…",
|
||||
@@ -704,8 +665,6 @@
|
||||
"change_password_description": "Αυτή είναι ή η πρώτη φορά που συνδέεστε στο σύστημα ή έχει γίνει αίτημα για αλλαγή του κωδικού σας. Παρακαλώ εισάγετε τον νέο κωδικό, παρακάτω.",
|
||||
"change_password_form_confirm_password": "Επιβεβαίωση Κωδικού",
|
||||
"change_password_form_description": "Γεια σας {name},\n\nΕίτε είναι η πρώτη φορά που συνδέεστε στο σύστημα είτε έχει γίνει αίτηση για αλλαγή του κωδικού σας. Παρακαλώ εισάγετε τον νέο κωδικό.",
|
||||
"change_password_form_log_out": "Αποσύνδεση από όλες τις άλλες συσκευές",
|
||||
"change_password_form_log_out_description": "Συνιστάται η αποσύνδεση από όλες τις άλλες συσκευές",
|
||||
"change_password_form_new_password": "Νέος Κωδικός",
|
||||
"change_password_form_password_mismatch": "Οι κωδικοί δεν ταιριάζουν",
|
||||
"change_password_form_reenter_new_password": "Επανεισαγωγή Νέου Κωδικού",
|
||||
@@ -732,15 +691,14 @@
|
||||
"client_cert_import_success_msg": "Το πιστοποιητικό πελάτη εισάγεται",
|
||||
"client_cert_invalid_msg": "Μη έγκυρο αρχείο πιστοποιητικού ή λάθος κωδικός πρόσβασης",
|
||||
"client_cert_remove_msg": "Το πιστοποιητικό πελάτη καταργήθηκε",
|
||||
"client_cert_subtitle": "Υποστηρίζει μόνο τη μορφή PKCS12 (.p12, .pfx). Η εισαγωγή/αφαίρεση πιστοποιητικού είναι διαθέσιμη μόνο πριν από τη σύνδεση",
|
||||
"client_cert_title": "Πιστοποιητικό SSL πελάτη [ΠΕΙΡΑΜΑΤΙΚΟ]",
|
||||
"client_cert_subtitle": "Υποστηρίζει μόνο τη μορφή PKCS12 (.p12, .pfx). Η Εισαγωγή/Αφαίρεση πιστοποιητικού είναι διαθέσιμη μόνο πριν από τη σύνδεση",
|
||||
"client_cert_title": "Πιστοποιητικό πελάτη SSL",
|
||||
"clockwise": "Δεξιόστροφα",
|
||||
"close": "Κλείσιμο",
|
||||
"collapse": "Σύμπτυξη",
|
||||
"collapse_all": "Σύμπτυξη όλων",
|
||||
"color": "Χρώμα",
|
||||
"color_theme": "Χρώμα θέματος",
|
||||
"command": "Εντολή",
|
||||
"comment_deleted": "Το σχόλιο διαγράφηκε",
|
||||
"comment_options": "Επιλογές σχολίου",
|
||||
"comments_and_likes": "Σχόλια & αντιδράσεις (likes)",
|
||||
@@ -784,7 +742,6 @@
|
||||
"create": "Δημιουργία",
|
||||
"create_album": "Δημιουργία άλμπουμ",
|
||||
"create_album_page_untitled": "Χωρίς τίτλο",
|
||||
"create_api_key": "Δημιουργία κλειδιού API",
|
||||
"create_library": "Δημιουργία Βιβλιοθήκης",
|
||||
"create_link": "Δημιουργία συνδέσμου",
|
||||
"create_link_to_share": "Δημιουργία συνδέσμου για διαμοιρασμό",
|
||||
@@ -814,7 +771,6 @@
|
||||
"daily_title_text_date_year": "Ε, MMM dd, yyyy",
|
||||
"dark": "Σκούρο",
|
||||
"dark_theme": "Εναλλαγή σκοτεινής εμφάνισης",
|
||||
"date": "Ημερομηνία",
|
||||
"date_after": "Ημερομηνία μετά",
|
||||
"date_and_time": "Ημερομηνία και ώρα",
|
||||
"date_before": "Ημερομηνία πριν",
|
||||
@@ -917,6 +873,8 @@
|
||||
"edit_description_prompt": "Παρακαλώ επιλέξτε νέα περιγραφή:",
|
||||
"edit_exclusion_pattern": "Επεξεργασία μοτίβου αποκλεισμού",
|
||||
"edit_faces": "Επεξεργασία προσώπων",
|
||||
"edit_import_path": "Επεξεργασία διαδρομής εισαγωγής",
|
||||
"edit_import_paths": "Επεξεργασία Διαδρομών Εισαγωγής",
|
||||
"edit_key": "Επεξεργασία κλειδιού",
|
||||
"edit_link": "Επεξεργασία συνδέσμου",
|
||||
"edit_location": "Επεξεργασία τοποθεσίας",
|
||||
@@ -988,8 +946,8 @@
|
||||
"failed_to_stack_assets": "Αποτυχία στην συμπίεση των στοιχείων",
|
||||
"failed_to_unstack_assets": "Αποτυχία στην αποσυμπίεση των στοιχείων",
|
||||
"failed_to_update_notification_status": "Αποτυχία ενημέρωσης της κατάστασης ειδοποίησης",
|
||||
"import_path_already_exists": "Αυτή η διαδρομή εισαγωγής υπάρχει ήδη.",
|
||||
"incorrect_email_or_password": "Λανθασμένο email ή κωδικός πρόσβασης",
|
||||
"library_folder_already_exists": "Η διαδρομή εισαγωγής υπάρχει ήδη.",
|
||||
"paths_validation_failed": "{paths, plural, one {# διαδρομή} other {# διαδρομές}} απέτυχαν κατά την επικύρωση",
|
||||
"profile_picture_transparent_pixels": "Οι εικόνες προφίλ δεν μπορούν να έχουν διαφανή εικονοστοιχεία. Παρακαλώ μεγεθύνετε ή/και μετακινήστε την εικόνα.",
|
||||
"quota_higher_than_disk_size": "Έχετε ορίσει ένα όριο, μεγαλύτερο από το μέγεθος του δίσκου",
|
||||
@@ -998,6 +956,7 @@
|
||||
"unable_to_add_assets_to_shared_link": "Αδυναμία προσθήκης στοιχείου στον κοινόχρηστο σύνδεσμο",
|
||||
"unable_to_add_comment": "Αδυναμία προσθήκης σχολίου",
|
||||
"unable_to_add_exclusion_pattern": "Αδυναμία προσθήκης μοτίβου αποκλεισμού",
|
||||
"unable_to_add_import_path": "Αδυναμία προσθήκης διαδρομής εισαγωγής",
|
||||
"unable_to_add_partners": "Αδυναμία προσθήκης συνεργατών",
|
||||
"unable_to_add_remove_archive": "Αδυναμία {archived, select, true {αφαίρεσης του στοιχείου από το} other {προσθήκης του στοιχείου στο}} αρχείο",
|
||||
"unable_to_add_remove_favorites": "Αδυναμία {favorite, select, true {προσθήκης του στοιχείου στα} other {αφαίρεσης του στοιχείου από τα}} αγαπημένα",
|
||||
@@ -1020,10 +979,12 @@
|
||||
"unable_to_delete_asset": "Αδυναμία διαγραφής στοιχείου",
|
||||
"unable_to_delete_assets": "Σφάλμα κατα τη διαγραφή στοιχείων",
|
||||
"unable_to_delete_exclusion_pattern": "Αδυναμία διαγραφής μοτίβου αποκλεισμού",
|
||||
"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_empty_trash": "Αδυναμία αδειάσματος του κάδου απορριμμάτων",
|
||||
"unable_to_enter_fullscreen": "Αδυναμία μετάβασης σε πλήρη οθόνη",
|
||||
"unable_to_exit_fullscreen": "Αδυναμία εξόδου από πλήρη οθόνη",
|
||||
@@ -1074,7 +1035,6 @@
|
||||
"unable_to_update_user": "Αδυναμία ενημέρωσης του χρήστη",
|
||||
"unable_to_upload_file": "Αδυναμία μεταφόρτωσης αρχείου"
|
||||
},
|
||||
"exclusion_pattern": "Μοτίβο αποκλεισμού",
|
||||
"exif": "Μεταδεδομένα Exif",
|
||||
"exif_bottom_sheet_description": "Προσθήκη Περιγραφής...",
|
||||
"exif_bottom_sheet_description_error": "Σφάλμα κατά την ενημέρωση της περιγραφής",
|
||||
@@ -1105,7 +1065,6 @@
|
||||
"external_network_sheet_info": "Όταν δεν είστε συνδεδεμένοι στο προτιμώμενο δίκτυο Wi-Fi, η εφαρμογή θα συνδεθεί με τον διακομιστή μέσω του πρώτου από τα παρακάτω URLs που μπορεί να βρει διαθέσιμο, ξεκινώντας από το πάνω προς το κάτω",
|
||||
"face_unassigned": "Μη ανατεθειμένο",
|
||||
"failed": "Απέτυχε",
|
||||
"failed_count": "Αποτυχημένα: {count}",
|
||||
"failed_to_authenticate": "Αποτυχία ταυτοποίησης",
|
||||
"failed_to_load_assets": "Αποτυχία φόρτωσης στοιχείων",
|
||||
"failed_to_load_folder": "Αποτυχία φόρτωσης φακέλου",
|
||||
@@ -1120,7 +1079,6 @@
|
||||
"features_setting_description": "Διαχειριστείτε τα χαρακτηριστικά της εφαρμογής",
|
||||
"file_name": "Όνομα αρχείου",
|
||||
"file_name_or_extension": "Όνομα αρχείου ή επέκταση",
|
||||
"file_size": "Μέγεθος αρχείου",
|
||||
"filename": "Ονομασία αρχείου",
|
||||
"filetype": "Τύπος αρχείου",
|
||||
"filter": "Φίλτρο",
|
||||
@@ -1135,7 +1093,6 @@
|
||||
"folders_feature_description": "Περιήγηση στην προβολή φακέλου για τις φωτογραφίες και τα βίντεο στο σύστημα αρχείων",
|
||||
"forgot_pin_code_question": "Ξεχάσατε το PIN;",
|
||||
"forward": "Προς τα εμπρός",
|
||||
"full_path": "Πλήρης διαδρομή: {path}",
|
||||
"gcast_enabled": "Μετάδοση περιεχομένου Google Cast",
|
||||
"gcast_enabled_description": "Αυτό το χαρακτηριστικό φορτώνει εξωτερικούς πόρους από τη Google για να λειτουργήσει.",
|
||||
"general": "Γενικά",
|
||||
@@ -1161,7 +1118,7 @@
|
||||
"hash_asset": "Κατακερματισμός στοιχείου",
|
||||
"hashed_assets": "Κατακερματισμένα στοιχεία",
|
||||
"hashing": "Κατακερματισμός",
|
||||
"header_settings_add_header_tip": "Προσθήκη header",
|
||||
"header_settings_add_header_tip": "Προσθήκη Κεφαλίδας",
|
||||
"header_settings_field_validator_msg": "Η τιμή δεν μπορεί να είναι κενή",
|
||||
"header_settings_header_name_input": "Όνομα κεφαλίδας",
|
||||
"header_settings_header_value_input": "Τιμή κεφαλίδας",
|
||||
@@ -1172,7 +1129,6 @@
|
||||
"hide_named_person": "Απόκρυψη του ατόμου {name}",
|
||||
"hide_password": "Απόκρυψη κωδικού πρόσβασης",
|
||||
"hide_person": "Απόκρυψη ατόμου",
|
||||
"hide_text_recognition": "Απόκρυψη αναγνώρισης κειμένου",
|
||||
"hide_unnamed_people": "Απόκρυψη ατόμων χωρίς όνομα",
|
||||
"home_page_add_to_album_conflicts": "Προστέθηκαν {added} στοιχεία στο άλμπουμ {album}. {failed} στοιχεία υπάρχουν ήδη στο άλμπουμ.",
|
||||
"home_page_add_to_album_err_local": "Δεν είναι ακόμη δυνατή η προσθήκη τοπικών στοιχείων σε άλμπουμ, παράβλεψη",
|
||||
@@ -1218,8 +1174,6 @@
|
||||
"import_path": "Εισαγωγή διαδρομής",
|
||||
"in_albums": "Μέσα σε {count, plural, one {# άλμπουμ} other {# άλμπουμ}}",
|
||||
"in_archive": "Μέσα στα αρχειοθετημένα",
|
||||
"in_year": "Σε {year}",
|
||||
"in_year_selector": "Σε",
|
||||
"include_archived": "Συμπερίληψη αρχειοθετημένων",
|
||||
"include_shared_albums": "Συμπερίληψη διαμοιρασμένων άλμπουμ",
|
||||
"include_shared_partner_assets": "Συμπερίληψη των στοιχείων των συνεργατών που έχουν κοινοποιηθεί",
|
||||
@@ -1256,7 +1210,6 @@
|
||||
"language_setting_description": "Επιλέξτε τη γλώσσα που προτιμάτε",
|
||||
"large_files": "Μεγάλα Αρχεία",
|
||||
"last": "Τελευταία",
|
||||
"last_months": "{count, plural, one {Τον προηγούμενο μήνα} other {Τους τελευταίους # μήνες}}",
|
||||
"last_seen": "Τελευταία προβολή",
|
||||
"latest_version": "Τελευταία Έκδοση",
|
||||
"latitude": "Γεωγραφικό πλάτος",
|
||||
@@ -1266,8 +1219,6 @@
|
||||
"let_others_respond": "Επέτρεψε σε άλλους να απαντήσουν",
|
||||
"level": "Επίπεδο",
|
||||
"library": "Βιβλιοθήκη",
|
||||
"library_add_folder": "Προσθήκη φακέλου",
|
||||
"library_edit_folder": "Επεξεργασία φακέλου",
|
||||
"library_options": "Επιλογές βιβλιοθήκης",
|
||||
"library_page_device_albums": "Άλμπουμ στη Συσκευή",
|
||||
"library_page_new_album": "Νέο άλμπουμ",
|
||||
@@ -1291,7 +1242,6 @@
|
||||
"local_media_summary": "Περίληψη τοπικών πολυμέσων",
|
||||
"local_network": "Τοπικό δίκτυο",
|
||||
"local_network_sheet_info": "Η εφαρμογή θα συνδεθεί με τον διακομιστή μέσω αυτού του URL όταν χρησιμοποιείται το καθορισμένο δίκτυο Wi-Fi",
|
||||
"location": "Τοποθεσία",
|
||||
"location_permission": "Άδεια τοποθεσίας",
|
||||
"location_permission_content": "Για να χρησιμοποιηθεί η λειτουργία αυτόματης εναλλαγής, το Immich χρειάζεται άδεια για την ακριβή τοποθεσία της συσκευής ώστε να μπορεί να διαβάζει το όνομα του τρέχοντος δικτύου Wi-Fi",
|
||||
"location_picker_choose_on_map": "Επιλέξτε στο χάρτη",
|
||||
@@ -1339,17 +1289,8 @@
|
||||
"loop_videos_description": "Ενεργοποιήστε την αυτόματη επανάληψη ενός βίντεο στο πρόγραμμα προβολής λεπτομερειών.",
|
||||
"main_branch_warning": "Χρησιμοποιείτε μια έκδοση σε ανάπτυξη· συνιστούμε ανεπιφύλακτα τη χρήση μιας τελικής έκδοσης!",
|
||||
"main_menu": "Κύριο μενού",
|
||||
"maintenance_description": "Το Immich έχει τεθεί σε <link>λειτουργία συντήρησης</link>.",
|
||||
"maintenance_end": "Τερματισμός λειτουργίας συντήρησης",
|
||||
"maintenance_end_error": "Αποτυχία τερματισμού της λειτουργίας συντήρησης.",
|
||||
"maintenance_logged_in_as": "Αυτήν τη στιγμή είστε συνδεδεμένος ως {user}",
|
||||
"maintenance_title": "Προσωρινά μη διαθέσιμο",
|
||||
"make": "Κατασκευαστής",
|
||||
"manage_geolocation": "Διαχείριση τοποθεσίας",
|
||||
"manage_media_access_rationale": "Αυτή η άδεια απαιτείται για τη σωστή μετακίνηση των στοιχείων στον κάδο και την επαναφορά τους από αυτόν.",
|
||||
"manage_media_access_settings": "Άνοιγμα ρυθμίσεων",
|
||||
"manage_media_access_subtitle": "Επίτρεψτε στην εφαρμογή Immich να διαχειρίζεται και να μετακινεί αρχεία πολυμέσων.",
|
||||
"manage_media_access_title": "Πρόσβαση Διαχείρισης Πολυμέσων",
|
||||
"manage_shared_links": "Διαχείριση κοινόχρηστων συνδέσμων",
|
||||
"manage_sharing_with_partners": "Διαχειριστείτε την κοινή χρήση με συνεργάτες",
|
||||
"manage_the_app_settings": "Διαχειριστείτε τις ρυθμίσεις της εφαρμογής",
|
||||
@@ -1406,14 +1347,13 @@
|
||||
"minutes": "Λεπτά",
|
||||
"missing": "Όσα Λείπουν",
|
||||
"mobile_app": "Εφαρμογή για κινητά",
|
||||
"mobile_app_download_onboarding_note": "Κατέβασε την συνοδευτική εφαρμογή για κινητά χρησιμοποιώντας τις παρακάτω επιλογές",
|
||||
"mobile_app_download_onboarding_note": "Μπορείτε να αποκτήσετε ξανά πρόσβαση σε αυτές τις επιλογές από τη σελίδα Βοηθήματα.",
|
||||
"model": "Μοντέλο",
|
||||
"month": "Μήνας",
|
||||
"monthly_title_text_date_format": "ΜΜΜΜ y",
|
||||
"more": "Περισσότερα",
|
||||
"move": "Μετακίνηση",
|
||||
"move_off_locked_folder": "Μετακίνηση έξω από τον κλειδωμένο φάκελο",
|
||||
"move_to": "Μετακίνηση σε",
|
||||
"move_to_lock_folder_action_prompt": "Προστέθηκαν {count} στον κλειδωμένο φάκελο",
|
||||
"move_to_locked_folder": "Μετακίνηση σε κλειδωμένο φάκελο",
|
||||
"move_to_locked_folder_confirmation": "Αυτές οι φωτογραφίες και τα βίντεο θα αφαιρεθούν από όλα τα άλμπουμ και θα μπορούν να προβληθούν μόνο από τον κλειδωμένο φάκελο",
|
||||
@@ -1443,7 +1383,6 @@
|
||||
"new_pin_code": "Νέος κωδικός PIN",
|
||||
"new_pin_code_subtitle": "Αυτή είναι η πρώτη φορά που αποκτάτε πρόσβαση στον κλειδωμένο φάκελο. Δημιουργήστε έναν κωδικό PIN για ασφαλή πρόσβαση σε αυτή τη σελίδα",
|
||||
"new_timeline": "Νέο Χρονολόγιο",
|
||||
"new_update": "Νέα ενημέρωση",
|
||||
"new_user_created": "Ο νέος χρήστης δημιουργήθηκε",
|
||||
"new_version_available": "ΔΙΑΘΕΣΙΜΗ ΝΕΑ ΕΚΔΟΣΗ",
|
||||
"newest_first": "Τα νεότερα πρώτα",
|
||||
@@ -1459,14 +1398,12 @@
|
||||
"no_cast_devices_found": "Δε βρέθηκαν συσκευές μετάδοσης",
|
||||
"no_checksum_local": "Δεν υπάρχει διαθέσιμο checksum για έλεγχο ακεραιότητας – δεν μπορούν να ανακτηθούν τα τοπικά στοιχεία",
|
||||
"no_checksum_remote": "Δεν υπάρχει διαθέσιμο checksum για έλεγχο ακεραιότητας – δεν μπορούν να ανακτηθούν τα απομακρυσμένα στοιχεία",
|
||||
"no_devices": "Δεν υπάρχουν εξουσιοδοτημένες συσκευές",
|
||||
"no_duplicates_found": "Δεν βρέθηκαν διπλότυπα.",
|
||||
"no_exif_info_available": "Καμία πληροφορία exif διαθέσιμη",
|
||||
"no_explore_results_message": "Ανεβάστε περισσότερες φωτογραφίες για να περιηγηθείτε στη συλλογή σας.",
|
||||
"no_favorites_message": "Προσθέστε αγαπημένα για να βρείτε γρήγορα τις καλύτερες φωτογραφίες και τα βίντεό σας",
|
||||
"no_libraries_message": "Δημιουργήστε μια εξωτερική βιβλιοθήκη για να προβάλετε τις φωτογραφίες και τα βίντεό σας",
|
||||
"no_local_assets_found": "Δεν βρέθηκαν τοπικά στοιχεία με αυτό το checksum",
|
||||
"no_location_set": "Η τοποθεσία δεν έχει οριστεί",
|
||||
"no_locked_photos_message": "Οι φωτογραφίες και τα βίντεο στον κλειδωμένο φάκελο, είναι κρυμμένες και δεν θα εμφανίζονται κατά την περιήγηση ή την αναζήτηση στη βιβλιοθήκη σας.",
|
||||
"no_name": "Χωρίς Όνομα",
|
||||
"no_notifications": "Καμία ειδοποίηση",
|
||||
@@ -1477,7 +1414,6 @@
|
||||
"no_results_description": "Δοκιμάστε ένα συνώνυμο ή πιο γενική λέξη-κλειδί",
|
||||
"no_shared_albums_message": "Δημιουργήστε ένα άλμπουμ για να μοιράζεστε φωτογραφίες και βίντεο με άτομα στο δίκτυό σας",
|
||||
"no_uploads_in_progress": "Καμία μεταφόρτωση σε εξέλιξη",
|
||||
"not_allowed": "Δεν επιτρέπεται",
|
||||
"not_available": "Μ/Δ (Μη Διαθέσιμο)",
|
||||
"not_in_any_album": "Σε κανένα άλμπουμ",
|
||||
"not_selected": "Δεν επιλέχθηκε",
|
||||
@@ -1493,8 +1429,7 @@
|
||||
"notifications_setting_description": "Διαχείριση ειδοποιήσεων",
|
||||
"oauth": "OAuth",
|
||||
"obtainium_configurator": "Ρυθμιστής Obtainium",
|
||||
"obtainium_configurator_instructions": "Χρησιμοποίησε το Obtainium για να εγκαταστήσεις και να ενημερώσεις απευθείας την εφαρμογή Android από τα κυκλοφορίες του Immich στο GitHub. Δημιούργησε ένα API key και επέλεξε μια παραλλαγή για να δημιουργήσεις το σύνδεσμο ρύθμισης του Obtainium",
|
||||
"ocr": "OCR",
|
||||
"obtainium_configurator_instructions": "Δημιουργήστε ένα κλειδί API και επιλέξτε μια παραλλαγή για να δημιουργήσετε τον σύνδεσμο σας ρύθμισης Obtainium.",
|
||||
"official_immich_resources": "Επίσημοι Πόροι του Immich",
|
||||
"offline": "Εκτός σύνδεσης",
|
||||
"offset": "Μετατόπιση",
|
||||
@@ -1526,7 +1461,6 @@
|
||||
"other_variables": "Άλλες μεταβλητές",
|
||||
"owned": "Δικά μου",
|
||||
"owner": "Κάτοχος",
|
||||
"page": "Σελίδα",
|
||||
"partner": "Συνεργάτης",
|
||||
"partner_can_access": "Ο χρήστης {partner} έχει πρόσβαση",
|
||||
"partner_can_access_assets": "Όλες οι φωτογραφίες και τα βίντεό σας εκτός από αυτά που βρίσκονται στο Αρχείο και τα Διεγραμμένα",
|
||||
@@ -1589,22 +1523,17 @@
|
||||
"photos_count": "{count, plural, one {{count, number} Φωτογραφία} other {{count, number} Φωτογραφίες}}",
|
||||
"photos_from_previous_years": "Φωτογραφίες προηγούμενων ετών",
|
||||
"pick_a_location": "Επιλέξτε μια τοποθεσία",
|
||||
"pick_custom_range": "Προσαρμοσμένο εύρος",
|
||||
"pick_date_range": "Επιλέξτε εύρος ημερομηνιών",
|
||||
"pin_code_changed_successfully": "Επιτυχής αλλαγή κωδικού PIN",
|
||||
"pin_code_reset_successfully": "Επιτυχής επαναφορά κωδικού PIN",
|
||||
"pin_code_setup_successfully": "Επιτυχής ρύθμιση κωδικού PIN",
|
||||
"pin_verification": "Επιβεβαίωση κωδικού PIN",
|
||||
"place": "Μέρος",
|
||||
"place": "Τοποθεσία",
|
||||
"places": "Τοποθεσίες",
|
||||
"places_count": "{count, plural, one {{count} Τοποθεσία} other {{count} Τοποθεσίες}}",
|
||||
"play": "Αναπαραγωγή",
|
||||
"play_memories": "Αναπαραγωγή αναμνήσεων",
|
||||
"play_motion_photo": "Αναπαραγωγή Κινούμενης Φωτογραφίας",
|
||||
"play_or_pause_video": "Αναπαραγωγή ή παύση βίντεο",
|
||||
"play_original_video": "Αναπαραγωγή αρχικού βίντεο",
|
||||
"play_original_video_setting_description": "Προτίμησε την αναπαραγωγή των αρχικών βίντεο αντί των μετακωδικοποιημένων. Αν το αρχικό αρχείο δεν είναι συμβατό, ίσως να μην αναπαραχθεί σωστά.",
|
||||
"play_transcoded_video": "Αναπαραγωγή μετακωδικοποιημένου βίντεο",
|
||||
"please_auth_to_access": "Παρακαλώ πιστοποιηθείτε για να αποκτήσετε πρόσβαση",
|
||||
"port": "Θύρα",
|
||||
"preferences_settings_subtitle": "Διαχειριστείτε τις προτιμήσεις της εφαρμογής",
|
||||
@@ -1741,7 +1670,6 @@
|
||||
"reset_sqlite_confirmation": "Είσαι σίγουρος ότι θέλεις να επαναφέρεις τη βάση δεδομένων SQLite; Θα χρειαστεί να κάνεις αποσύνδεση και επανασύνδεση για να επανασυγχρονίσεις τα δεδομένα",
|
||||
"reset_sqlite_success": "Η επαναφορά της SQLite βάσης δεδομένων ολοκληρώθηκε με επιτυχία",
|
||||
"reset_to_default": "Επαναφορά στις προεπιλογές",
|
||||
"resolution": "Ανάλυση",
|
||||
"resolve_duplicates": "Επίλυση διπλοτύπων",
|
||||
"resolved_all_duplicates": "Επιλύθηκαν όλα τα διπλότυπα",
|
||||
"restore": "Ανάκτηση",
|
||||
@@ -1760,7 +1688,6 @@
|
||||
"running": "Σε λειτουργία",
|
||||
"save": "Αποθήκευση",
|
||||
"save_to_gallery": "Αποθήκευση στη συλλογή",
|
||||
"saved": "Αποθηκευμένο",
|
||||
"saved_api_key": "Αποθηκευμένο API key",
|
||||
"saved_profile": "Αποθηκευμένο προφίλ",
|
||||
"saved_settings": "Αποθηκευμένες ρυθμίσεις",
|
||||
@@ -1777,9 +1704,6 @@
|
||||
"search_by_description_example": "Ημερήσια πεζοπορία στο Πάπιγκο",
|
||||
"search_by_filename": "Αναζήτηση βάσει ονόματος αρχείου ή επέκτασης αρχείου",
|
||||
"search_by_filename_example": "π.χ. IMG_1234.JPG ή PNG",
|
||||
"search_by_ocr": "Αναζήτηση κατά OCR",
|
||||
"search_by_ocr_example": "Λάτε",
|
||||
"search_camera_lens_model": "Αναζήτηση μοντέλου φακού...",
|
||||
"search_camera_make": "Αναζήτηση κατασκευαστή κάμερας...",
|
||||
"search_camera_model": "Αναζήτηση μοντέλου κάμερας...",
|
||||
"search_city": "Αναζήτηση πόλης...",
|
||||
@@ -1796,7 +1720,6 @@
|
||||
"search_filter_location_title": "Επιλέξτε τοποθεσία",
|
||||
"search_filter_media_type": "Τύπος Μέσου",
|
||||
"search_filter_media_type_title": "Επιλέξτε τύπο μέσου",
|
||||
"search_filter_ocr": "Αναζήτηση κατά OCR",
|
||||
"search_filter_people_title": "Επιλέξτε άτομα",
|
||||
"search_for": "Αναζήτηση για",
|
||||
"search_for_existing_person": "Αναζήτηση υπάρχοντος ατόμου",
|
||||
@@ -1858,10 +1781,7 @@
|
||||
"server_offline": "Διακομιστής Εκτός Σύνδεσης",
|
||||
"server_online": "Διακομιστής Σε Σύνδεση",
|
||||
"server_privacy": "Απόρρητο Διακομιστή",
|
||||
"server_restarting_description": "Αυτή η σελίδα θα ανανεωθεί σε λίγο.",
|
||||
"server_restarting_title": "Ο διακομιστής επανεκκινεί",
|
||||
"server_stats": "Στατιστικά Διακομιστή",
|
||||
"server_update_available": "Υπάρχει διαθέσιμη ενημέρωση διακομιστή",
|
||||
"server_version": "Έκδοση Διακομιστή",
|
||||
"set": "Ορισμός",
|
||||
"set_as_album_cover": "Ορισμός ως εξώφυλλο άλμπουμ",
|
||||
@@ -1983,7 +1903,6 @@
|
||||
"show_slideshow_transition": "Εμφάνιση μετάβασης παρουσίασης",
|
||||
"show_supporter_badge": "Σήμα υποστηρικτή",
|
||||
"show_supporter_badge_description": "Εμφάνιση σήματος υποστηρικτή",
|
||||
"show_text_recognition": "Εμφάνιση αναγνώρισης κειμένου",
|
||||
"show_text_search_menu": "Εμφάνιση μενού αναζήτησης κειμένου",
|
||||
"shuffle": "Ανάμειξη",
|
||||
"sidebar": "Πλαϊνή μπάρα",
|
||||
@@ -2054,7 +1973,6 @@
|
||||
"tags": "Ετικέτες",
|
||||
"tap_to_run_job": "Πατήστε για να ξεκινήσει η εργασία",
|
||||
"template": "Πρότυπο",
|
||||
"text_recognition": "Αναγνώριση κειμένου",
|
||||
"theme": "Θέμα",
|
||||
"theme_selection": "Επιλογή θέματος",
|
||||
"theme_selection_description": "Ρυθμίστε αυτόματα το θέμα σε ανοιχτό ή σκούρο με βάση τις προτιμήσεις συστήματος του προγράμματος περιήγησής σας",
|
||||
@@ -2073,9 +1991,7 @@
|
||||
"theme_setting_three_stage_loading_title": "Ενεργοποιήστε τη φόρτωση τριών σταδίων",
|
||||
"they_will_be_merged_together": "Θα συγχωνευθούν μαζί",
|
||||
"third_party_resources": "Πόροι τρίτων",
|
||||
"time": "Χρόνος",
|
||||
"time_based_memories": "Μνήμες βασισμένες στο χρόνο",
|
||||
"time_based_memories_duration": "Αριθμός δευτερολέπτων εμφάνισης κάθε εικόνας.",
|
||||
"timeline": "Χρονολόγιο",
|
||||
"timezone": "Ζώνη ώρας",
|
||||
"to_archive": "Αρχειοθέτηση",
|
||||
@@ -2087,7 +2003,6 @@
|
||||
"to_select": "για επιλογή",
|
||||
"to_trash": "Κάδος απορριμμάτων",
|
||||
"toggle_settings": "Εναλλαγή ρυθμίσεων",
|
||||
"toggle_theme_description": "Εναλλαγή θέματος",
|
||||
"total": "Σύνολο",
|
||||
"total_usage": "Συνολικη χρηση",
|
||||
"trash": "Κάδος απορριμμάτων",
|
||||
@@ -2108,7 +2023,6 @@
|
||||
"troubleshoot": "Επίλυση προβλημάτων",
|
||||
"type": "Τύπος",
|
||||
"unable_to_change_pin_code": "Αδυναμία αλλαγής κωδικού PIN",
|
||||
"unable_to_check_version": "Αδυναμία ελέγχου έκδοσης εφαρμογής ή έκδοσης διακομιστή",
|
||||
"unable_to_setup_pin_code": "Αδυναμία ρύθμισης κωδικού PIN",
|
||||
"unarchive": "Αναίρεση αρχειοθέτησης",
|
||||
"unarchive_action_prompt": "{count} αφαιρέθηκαν από το Αρχείο",
|
||||
@@ -2196,7 +2110,6 @@
|
||||
"view_album": "Προβολή Άλμπουμ",
|
||||
"view_all": "Προβολή Όλων",
|
||||
"view_all_users": "Προβολή όλων των χρηστών",
|
||||
"view_asset_owners": "Δείτε τους ιδιοκτήτες των στοιχείων",
|
||||
"view_details": "Προβολή Λεπτομερειών",
|
||||
"view_in_timeline": "Προβολή στο χρονοδιάγραμμα",
|
||||
"view_link": "Προβολή σύνδεσμου",
|
||||
@@ -2213,13 +2126,11 @@
|
||||
"viewer_unstack": "Αποστοίβαξε",
|
||||
"visibility_changed": "Η ορατότητα άλλαξε για {count, plural, one {# άτομο} other {# άτομα}}",
|
||||
"waiting": "Στοιχεία σε αναμονή",
|
||||
"waiting_count": "Σε αναμονή: {count}",
|
||||
"warning": "Προειδοποίηση",
|
||||
"week": "Εβδομάδα",
|
||||
"welcome": "Καλωσορίσατε",
|
||||
"welcome_to_immich": "Καλωσορίσατε στο Ιmmich",
|
||||
"wifi_name": "Όνομα Wi-Fi",
|
||||
"workflow": "Ροή εργασίας",
|
||||
"wrong_pin_code": "Λάθος κωδικός PIN",
|
||||
"year": "Έτος",
|
||||
"years_ago": "πριν από {years, plural, one {# χρόνο} other {# χρόνια}}",
|
||||
|
||||
64
i18n/en.json
64
i18n/en.json
@@ -7,7 +7,6 @@
|
||||
"action_common_update": "Update",
|
||||
"actions": "Actions",
|
||||
"active": "Active",
|
||||
"active_count": "Active: {count}",
|
||||
"activity": "Activity",
|
||||
"activity_changed": "Activity is {enabled, select, true {enabled} other {disabled}}",
|
||||
"add": "Add",
|
||||
@@ -18,6 +17,7 @@
|
||||
"add_birthday": "Add a birthday",
|
||||
"add_endpoint": "Add endpoint",
|
||||
"add_exclusion_pattern": "Add exclusion pattern",
|
||||
"add_import_path": "Add import path",
|
||||
"add_location": "Add location",
|
||||
"add_more_users": "Add more users",
|
||||
"add_partner": "Add partner",
|
||||
@@ -32,7 +32,6 @@
|
||||
"add_to_album_toggle": "Toggle selection for {album}",
|
||||
"add_to_albums": "Add to albums",
|
||||
"add_to_albums_count": "Add to albums ({count})",
|
||||
"add_to_bottom_bar": "Add to",
|
||||
"add_to_shared_album": "Add to shared album",
|
||||
"add_upload_to_stack": "Add upload to stack",
|
||||
"add_url": "Add URL",
|
||||
@@ -68,7 +67,6 @@
|
||||
"confirm_reprocess_all_faces": "Are you sure you want to reprocess all faces? This will also clear named people.",
|
||||
"confirm_user_password_reset": "Are you sure you want to reset {user}'s password?",
|
||||
"confirm_user_pin_code_reset": "Are you sure you want to reset {user}'s PIN code?",
|
||||
"copy_config_to_clipboard_description": "Copy the current system config as a JSON object to the clipboard",
|
||||
"create_job": "Create job",
|
||||
"cron_expression": "Cron expression",
|
||||
"cron_expression_description": "Set the scanning interval using the cron format. For more information please refer to e.g. <link>Crontab Guru</link>",
|
||||
@@ -76,8 +74,7 @@
|
||||
"disable_login": "Disable login",
|
||||
"duplicate_detection_job_description": "Run machine learning on assets to detect similar images. Relies on Smart Search",
|
||||
"exclusion_pattern_description": "Exclusion patterns lets you ignore files and folders when scanning your library. This is useful if you have folders that contain files you don't want to import, such as RAW files.",
|
||||
"export_config_as_json_description": "Download the current system config as a JSON file",
|
||||
"external_libraries_page_description": "Admin external library page",
|
||||
"external_library_management": "External Library Management",
|
||||
"face_detection": "Face detection",
|
||||
"face_detection_description": "Detect the faces in assets using machine learning. For videos, only the thumbnail is considered. \"Refresh\" (re-)processes all assets. \"Reset\" additionally clears all current face data. \"Missing\" queues assets that haven't been processed yet. Detected faces will be queued for Facial Recognition after Face Detection is complete, grouping them into existing or new people.",
|
||||
"facial_recognition_job_description": "Group detected faces into people. This step runs after Face Detection is complete. \"Reset\" (re-)clusters all faces. \"Missing\" queues faces that don't have a person assigned.",
|
||||
@@ -105,28 +102,23 @@
|
||||
"image_thumbnail_description": "Small thumbnail with stripped metadata, used when viewing groups of photos like the main timeline",
|
||||
"image_thumbnail_quality_description": "Thumbnail quality from 1-100. Higher is better, but produces larger files and can reduce app responsiveness.",
|
||||
"image_thumbnail_title": "Thumbnail Settings",
|
||||
"import_config_from_json_description": "Import system config by uploading a JSON config file",
|
||||
"job_concurrency": "{job} concurrency",
|
||||
"job_created": "Job created",
|
||||
"job_not_concurrency_safe": "This job is not concurrency-safe.",
|
||||
"job_settings": "Job Settings",
|
||||
"job_settings_description": "Manage job concurrency",
|
||||
"job_status": "Job Status",
|
||||
"jobs_delayed": "{jobCount, plural, other {# delayed}}",
|
||||
"jobs_failed": "{jobCount, plural, other {# failed}}",
|
||||
"jobs_over_time": "Jobs over time",
|
||||
"library_created": "Created library: {library}",
|
||||
"library_deleted": "Library deleted",
|
||||
"library_details": "Library details",
|
||||
"library_folder_description": "Specify a folder to import. This folder, including subfolders, will be scanned for images and videos.",
|
||||
"library_remove_exclusion_pattern_prompt": "Are you sure you want to remove this exclusion pattern?",
|
||||
"library_remove_folder_prompt": "Are you sure you want to remove this import folder?",
|
||||
"library_import_path_description": "Specify a folder to import. This folder, including subfolders, will be scanned for images and videos.",
|
||||
"library_scanning": "Periodic Scanning",
|
||||
"library_scanning_description": "Configure periodic library scanning",
|
||||
"library_scanning_enable_description": "Enable periodic library scanning",
|
||||
"library_settings": "External Library",
|
||||
"library_settings_description": "Manage external library settings",
|
||||
"library_tasks_description": "Scan external libraries for new and/or changed assets",
|
||||
"library_updated": "Updated library",
|
||||
"library_watching_enable_description": "Watch external libraries for file changes",
|
||||
"library_watching_settings": "Library watching [EXPERIMENTAL]",
|
||||
"library_watching_settings_description": "Automatically watch for changed files",
|
||||
@@ -181,12 +173,7 @@
|
||||
"machine_learning_smart_search_enabled": "Enable smart search",
|
||||
"machine_learning_smart_search_enabled_description": "If disabled, images will not be encoded for smart search.",
|
||||
"machine_learning_url_description": "The URL of the machine learning server. If more than one URL is provided, each server will be attempted one-at-a-time until one responds successfully, in order from first to last. Servers that don't respond will be temporarily ignored until they come back online.",
|
||||
"maintenance_settings": "Maintenance",
|
||||
"maintenance_settings_description": "Put Immich into maintenance mode.",
|
||||
"maintenance_start": "Start maintenance mode",
|
||||
"maintenance_start_error": "Failed to start maintenance mode.",
|
||||
"manage_concurrency": "Manage Concurrency",
|
||||
"manage_concurrency_description": "Navigate to the jobs page to manage job concurrency",
|
||||
"manage_log_settings": "Manage log settings",
|
||||
"map_dark_style": "Dark style",
|
||||
"map_enable_description": "Enable map features",
|
||||
@@ -276,14 +263,10 @@
|
||||
"password_settings_description": "Manage password login settings",
|
||||
"paths_validated_successfully": "All paths validated successfully",
|
||||
"person_cleanup_job": "Person cleanup",
|
||||
"queue_details": "Queue Details",
|
||||
"queues": "Job Queues",
|
||||
"queues_page_description": "Admin job queues page",
|
||||
"quota_size_gib": "Quota Size (GiB)",
|
||||
"refreshing_all_libraries": "Refreshing all libraries",
|
||||
"registration": "Admin Registration",
|
||||
"registration_description": "Since you are the first user on the system, you will be assigned as the Admin and are responsible for administrative tasks, and additional users will be created by you.",
|
||||
"remove_failed_jobs": "Remove failed jobs",
|
||||
"require_password_change_on_login": "Require user to change password on first login",
|
||||
"reset_settings_to_default": "Reset settings to default",
|
||||
"reset_settings_to_recent_saved": "Reset settings to the recent saved settings",
|
||||
@@ -296,10 +279,8 @@
|
||||
"server_public_users_description": "All users (name and email) are listed when adding a user to shared albums. When disabled, the user list will only be available to admin users.",
|
||||
"server_settings": "Server Settings",
|
||||
"server_settings_description": "Manage server settings",
|
||||
"server_stats_page_description": "Admin server statistics page",
|
||||
"server_welcome_message": "Welcome message",
|
||||
"server_welcome_message_description": "A message that is displayed on the login page.",
|
||||
"settings_page_description": "Admin settings page",
|
||||
"sidecar_job": "Sidecar metadata",
|
||||
"sidecar_job_description": "Discover or synchronize sidecar metadata from the filesystem",
|
||||
"slideshow_duration_description": "Number of seconds to display each image",
|
||||
@@ -419,7 +400,6 @@
|
||||
"user_settings": "User Settings",
|
||||
"user_settings_description": "Manage user settings",
|
||||
"user_successfully_removed": "User {email} has been successfully removed.",
|
||||
"users_page_description": "Admin users page",
|
||||
"version_check_enabled_description": "Enable version check",
|
||||
"version_check_implications": "The version check feature relies on periodic communication with github.com",
|
||||
"version_check_settings": "Version Check",
|
||||
@@ -450,7 +430,6 @@
|
||||
"age_months": "Age {months, plural, one {# month} other {# months}}",
|
||||
"age_year_months": "Age 1 year, {months, plural, one {# month} other {# months}}",
|
||||
"age_years": "{years, plural, other {Age #}}",
|
||||
"album": "Album",
|
||||
"album_added": "Album added",
|
||||
"album_added_notification_setting_description": "Receive an email notification when you are added to a shared album",
|
||||
"album_cover_updated": "Album cover updated",
|
||||
@@ -652,7 +631,6 @@
|
||||
"backup_options_page_title": "Backup options",
|
||||
"backup_setting_subtitle": "Manage background and foreground upload settings",
|
||||
"backup_settings_subtitle": "Manage upload settings",
|
||||
"backup_upload_details_page_more_details": "Tap for more details",
|
||||
"backward": "Backward",
|
||||
"biometric_auth_enabled": "Biometric authentication enabled",
|
||||
"biometric_locked_out": "You are locked out of biometric authentication",
|
||||
@@ -719,7 +697,6 @@
|
||||
"check_corrupt_asset_backup_button": "Perform check",
|
||||
"check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.",
|
||||
"check_logs": "Check Logs",
|
||||
"checksum": "Checksum",
|
||||
"choose_matching_people_to_merge": "Choose matching people to merge",
|
||||
"city": "City",
|
||||
"clear": "Clear",
|
||||
@@ -742,7 +719,6 @@
|
||||
"collapse_all": "Collapse all",
|
||||
"color": "Color",
|
||||
"color_theme": "Color theme",
|
||||
"command": "Command",
|
||||
"comment_deleted": "Comment deleted",
|
||||
"comment_options": "Comment options",
|
||||
"comments_and_likes": "Comments & likes",
|
||||
@@ -919,6 +895,8 @@
|
||||
"edit_description_prompt": "Please select a new description:",
|
||||
"edit_exclusion_pattern": "Edit exclusion pattern",
|
||||
"edit_faces": "Edit faces",
|
||||
"edit_import_path": "Edit import path",
|
||||
"edit_import_paths": "Edit Import Paths",
|
||||
"edit_key": "Edit key",
|
||||
"edit_link": "Edit link",
|
||||
"edit_location": "Edit location",
|
||||
@@ -990,8 +968,8 @@
|
||||
"failed_to_stack_assets": "Failed to stack assets",
|
||||
"failed_to_unstack_assets": "Failed to un-stack assets",
|
||||
"failed_to_update_notification_status": "Failed to update notification status",
|
||||
"import_path_already_exists": "This import path already exists.",
|
||||
"incorrect_email_or_password": "Incorrect email or password",
|
||||
"library_folder_already_exists": "This import path already exists.",
|
||||
"paths_validation_failed": "{paths, plural, one {# path} other {# paths}} failed validation",
|
||||
"profile_picture_transparent_pixels": "Profile pictures cannot have transparent pixels. Please zoom in and/or move the image.",
|
||||
"quota_higher_than_disk_size": "You set a quota higher than the disk size",
|
||||
@@ -1000,6 +978,7 @@
|
||||
"unable_to_add_assets_to_shared_link": "Unable to add assets to shared link",
|
||||
"unable_to_add_comment": "Unable to add comment",
|
||||
"unable_to_add_exclusion_pattern": "Unable to add exclusion pattern",
|
||||
"unable_to_add_import_path": "Unable to add import path",
|
||||
"unable_to_add_partners": "Unable to add partners",
|
||||
"unable_to_add_remove_archive": "Unable to {archived, select, true {remove asset from} other {add asset to}} archive",
|
||||
"unable_to_add_remove_favorites": "Unable to {favorite, select, true {add asset to} other {remove asset from}} favorites",
|
||||
@@ -1022,10 +1001,12 @@
|
||||
"unable_to_delete_asset": "Unable to delete asset",
|
||||
"unable_to_delete_assets": "Error deleting assets",
|
||||
"unable_to_delete_exclusion_pattern": "Unable to delete exclusion pattern",
|
||||
"unable_to_delete_import_path": "Unable to delete import path",
|
||||
"unable_to_delete_shared_link": "Unable to delete shared link",
|
||||
"unable_to_delete_user": "Unable to delete user",
|
||||
"unable_to_download_files": "Unable to download files",
|
||||
"unable_to_edit_exclusion_pattern": "Unable to edit exclusion pattern",
|
||||
"unable_to_edit_import_path": "Unable to edit import path",
|
||||
"unable_to_empty_trash": "Unable to empty trash",
|
||||
"unable_to_enter_fullscreen": "Unable to enter fullscreen",
|
||||
"unable_to_exit_fullscreen": "Unable to exit fullscreen",
|
||||
@@ -1076,7 +1057,6 @@
|
||||
"unable_to_update_user": "Unable to update user",
|
||||
"unable_to_upload_file": "Unable to upload file"
|
||||
},
|
||||
"exclusion_pattern": "Exclusion pattern",
|
||||
"exif": "Exif",
|
||||
"exif_bottom_sheet_description": "Add Description...",
|
||||
"exif_bottom_sheet_description_error": "Error updating description",
|
||||
@@ -1107,7 +1087,6 @@
|
||||
"external_network_sheet_info": "When not on the preferred Wi-Fi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom",
|
||||
"face_unassigned": "Unassigned",
|
||||
"failed": "Failed",
|
||||
"failed_count": "Failed: {count}",
|
||||
"failed_to_authenticate": "Failed to authenticate",
|
||||
"failed_to_load_assets": "Failed to load assets",
|
||||
"failed_to_load_folder": "Failed to load folder",
|
||||
@@ -1137,7 +1116,6 @@
|
||||
"folders_feature_description": "Browsing the folder view for the photos and videos on the file system",
|
||||
"forgot_pin_code_question": "Forgot your PIN?",
|
||||
"forward": "Forward",
|
||||
"full_path": "Full path: {path}",
|
||||
"gcast_enabled": "Google Cast",
|
||||
"gcast_enabled_description": "This feature loads external resources from Google in order to work.",
|
||||
"general": "General",
|
||||
@@ -1168,14 +1146,12 @@
|
||||
"header_settings_header_name_input": "Header name",
|
||||
"header_settings_header_value_input": "Header value",
|
||||
"headers_settings_tile_title": "Custom proxy headers",
|
||||
"height": "Height",
|
||||
"hi_user": "Hi {name} ({email})",
|
||||
"hide_all_people": "Hide all people",
|
||||
"hide_gallery": "Hide gallery",
|
||||
"hide_named_person": "Hide person {name}",
|
||||
"hide_password": "Hide password",
|
||||
"hide_person": "Hide person",
|
||||
"hide_text_recognition": "Hide text recognition",
|
||||
"hide_unnamed_people": "Hide unnamed people",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
@@ -1269,8 +1245,6 @@
|
||||
"let_others_respond": "Let others respond",
|
||||
"level": "Level",
|
||||
"library": "Library",
|
||||
"library_add_folder": "Add folder",
|
||||
"library_edit_folder": "Edit folder",
|
||||
"library_options": "Library options",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_new_album": "New album",
|
||||
@@ -1291,7 +1265,6 @@
|
||||
"local": "Local",
|
||||
"local_asset_cast_failed": "Unable to cast an asset that is not uploaded to the server",
|
||||
"local_assets": "Local Assets",
|
||||
"local_id": "Local ID",
|
||||
"local_media_summary": "Local Media Summary",
|
||||
"local_network": "Local network",
|
||||
"local_network_sheet_info": "The app will connect to the server through this URL when using the specified Wi-Fi network",
|
||||
@@ -1343,11 +1316,6 @@
|
||||
"loop_videos_description": "Enable to automatically loop a video in the detail viewer.",
|
||||
"main_branch_warning": "You're using a development version; we strongly recommend using a release version!",
|
||||
"main_menu": "Main menu",
|
||||
"maintenance_description": "Immich has been put into <link>maintenance mode</link>.",
|
||||
"maintenance_end": "End maintenance mode",
|
||||
"maintenance_end_error": "Failed to end maintenance mode.",
|
||||
"maintenance_logged_in_as": "Currently logged in as {user}",
|
||||
"maintenance_title": "Temporarily Unavailable",
|
||||
"make": "Make",
|
||||
"manage_geolocation": "Manage location",
|
||||
"manage_media_access_rationale": "This permission is required for proper handling of moving assets to the trash and restoring them from it.",
|
||||
@@ -1417,7 +1385,6 @@
|
||||
"more": "More",
|
||||
"move": "Move",
|
||||
"move_off_locked_folder": "Move out of locked folder",
|
||||
"move_to": "Move to",
|
||||
"move_to_lock_folder_action_prompt": "{count} added to the locked folder",
|
||||
"move_to_locked_folder": "Move to locked folder",
|
||||
"move_to_locked_folder_confirmation": "These photos and video will be removed from all albums, and only viewable from the locked folder",
|
||||
@@ -1470,7 +1437,6 @@
|
||||
"no_favorites_message": "Add favorites to quickly find your best pictures and videos",
|
||||
"no_libraries_message": "Create an external library to view your photos and videos",
|
||||
"no_local_assets_found": "No local assets found with this checksum",
|
||||
"no_location_set": "No location set",
|
||||
"no_locked_photos_message": "Photos and videos in the locked folder are hidden and won't show up as you browse or search your library.",
|
||||
"no_name": "No Name",
|
||||
"no_notifications": "No notifications",
|
||||
@@ -1530,7 +1496,6 @@
|
||||
"other_variables": "Other variables",
|
||||
"owned": "Owned",
|
||||
"owner": "Owner",
|
||||
"page": "Page",
|
||||
"partner": "Partner",
|
||||
"partner_can_access": "{partner} can access",
|
||||
"partner_can_access_assets": "All your photos and videos except those in Archived and Deleted",
|
||||
@@ -1862,8 +1827,6 @@
|
||||
"server_offline": "Server Offline",
|
||||
"server_online": "Server Online",
|
||||
"server_privacy": "Server Privacy",
|
||||
"server_restarting_description": "This page will refresh momentarily.",
|
||||
"server_restarting_title": "Server is restarting",
|
||||
"server_stats": "Server Stats",
|
||||
"server_update_available": "Server update is available",
|
||||
"server_version": "Server Version",
|
||||
@@ -1987,7 +1950,6 @@
|
||||
"show_slideshow_transition": "Show slideshow transition",
|
||||
"show_supporter_badge": "Supporter badge",
|
||||
"show_supporter_badge_description": "Show a supporter badge",
|
||||
"show_text_recognition": "Show text recognition",
|
||||
"show_text_search_menu": "Show text search menu",
|
||||
"shuffle": "Shuffle",
|
||||
"sidebar": "Sidebar",
|
||||
@@ -2058,7 +2020,6 @@
|
||||
"tags": "Tags",
|
||||
"tap_to_run_job": "Tap to run job",
|
||||
"template": "Template",
|
||||
"text_recognition": "Text recognition",
|
||||
"theme": "Theme",
|
||||
"theme_selection": "Theme selection",
|
||||
"theme_selection_description": "Automatically set the theme to light or dark based on your browser's system preference",
|
||||
@@ -2091,7 +2052,6 @@
|
||||
"to_select": "to select",
|
||||
"to_trash": "Trash",
|
||||
"toggle_settings": "Toggle settings",
|
||||
"toggle_theme_description": "Toggle theme",
|
||||
"total": "Total",
|
||||
"total_usage": "Total usage",
|
||||
"trash": "Trash",
|
||||
@@ -2200,7 +2160,6 @@
|
||||
"view_album": "View Album",
|
||||
"view_all": "View All",
|
||||
"view_all_users": "View all users",
|
||||
"view_asset_owners": "View asset owners",
|
||||
"view_details": "View Details",
|
||||
"view_in_timeline": "View in timeline",
|
||||
"view_link": "View link",
|
||||
@@ -2217,14 +2176,11 @@
|
||||
"viewer_unstack": "Un-Stack",
|
||||
"visibility_changed": "Visibility changed for {count, plural, one {# person} other {# people}}",
|
||||
"waiting": "Waiting",
|
||||
"waiting_count": "Waiting: {count}",
|
||||
"warning": "Warning",
|
||||
"week": "Week",
|
||||
"welcome": "Welcome",
|
||||
"welcome_to_immich": "Welcome to Immich",
|
||||
"width": "Width",
|
||||
"wifi_name": "Wi-Fi Name",
|
||||
"workflow": "Workflow",
|
||||
"wrong_pin_code": "Wrong PIN code",
|
||||
"year": "Year",
|
||||
"years_ago": "{years, plural, one {# year} other {# years}} ago",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
250
i18n/es.json
250
i18n/es.json
@@ -7,40 +7,39 @@
|
||||
"action_common_update": "Actualizar",
|
||||
"actions": "Acciones",
|
||||
"active": "Activo",
|
||||
"active_count": "Activo: {count}",
|
||||
"activity": "Actividad",
|
||||
"activity_changed": "La actividad está {enabled, select, true {habilitada} other {deshabilitada}}",
|
||||
"add": "Añadir",
|
||||
"add_a_description": "Añadir descripción",
|
||||
"add_a_location": "Añadir una ubicación",
|
||||
"add_a_name": "Añadir un nombre",
|
||||
"add_a_title": "Añadir título",
|
||||
"add_birthday": "Añadir un cumpleaños",
|
||||
"add_endpoint": "Añadir punto final",
|
||||
"add_exclusion_pattern": "Añadir patrón de exclusión",
|
||||
"add_location": "Añadir ubicación",
|
||||
"add_more_users": "Añadir más usuarios",
|
||||
"add_partner": "Añadir miembro",
|
||||
"add_path": "Añadir ruta",
|
||||
"add_photos": "Añadir fotos",
|
||||
"add_tag": "Añadir etiqueta",
|
||||
"add_to": "Añadir a…",
|
||||
"add_to_album": "Añadir al álbum",
|
||||
"add_to_album_bottom_sheet_added": "Añadido a {album}",
|
||||
"add": "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_birthday": "Agregar un cumpleaños",
|
||||
"add_endpoint": "Agregar endpoint",
|
||||
"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 compañero",
|
||||
"add_path": "Agregar ruta",
|
||||
"add_photos": "Agregar fotos",
|
||||
"add_tag": "Agregar etiqueta",
|
||||
"add_to": "Agregar a…",
|
||||
"add_to_album": "Incluir en álbum",
|
||||
"add_to_album_bottom_sheet_added": "Agregado a {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Ya se encuentra en {album}",
|
||||
"add_to_album_bottom_sheet_some_local_assets": "Algunos recursos locales no se pudieron añadir al álbum",
|
||||
"add_to_album_toggle": "Alternar selección para el {album}",
|
||||
"add_to_albums": "Añadir a álbumes",
|
||||
"add_to_albums_count": "Añadir a {count} álbumes",
|
||||
"add_to_bottom_bar": "Añadir a",
|
||||
"add_to_shared_album": "Añadir al álbum compartido",
|
||||
"add_upload_to_stack": "Añadir subida a la cola",
|
||||
"add_url": "Añadir URL",
|
||||
"added_to_archive": "Añadido al archivo",
|
||||
"added_to_favorites": "Añadido a favoritos",
|
||||
"added_to_favorites_count": "Añadido {count, number} a favoritos",
|
||||
"add_to_albums": "Incluir en álbumes",
|
||||
"add_to_albums_count": "Incluir en {count} álbumes",
|
||||
"add_to_shared_album": "Incluir en álbum compartido",
|
||||
"add_upload_to_stack": "Añadir archivo y apilar",
|
||||
"add_url": "Agregar URL",
|
||||
"added_to_archive": "Agregado al 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). Ejemplos: para ignorar todos los archivos en cualquier directorio llamado \"Raw\", utiliza \"**/Raw/**\". Para ignorar todos los archivos que terminan en \".tif\", utiliza \"**/*.tif\". Para ignorar una ruta absoluta, utiliza \"/carpeta/a/ignorar/**\".",
|
||||
"add_exclusion_pattern_description": "Agrega patrones de exclusión. Puedes utilizar los caracteres *, ** y ? (globbing). Ejemplos: para ignorar todos los archivos en cualquier directorio llamado \"Raw\", utiliza \"**/Raw/**\". Para ignorar todos los archivos que terminan en \".tif\", utiliza \"**/*.tif\". Para ignorar una ruta absoluta, utiliza \"/carpeta/a/ignorar/**\".",
|
||||
"admin_user": "Usuario administrador",
|
||||
"asset_offline_description": "Este recurso externo de la biblioteca ya no se encuentra en el disco y se ha movido a la papelera. Si el archivo se movió dentro de la biblioteca, comprueba la línea temporal para el nuevo recurso correspondiente. Para restaurar este recurso, asegúrate de que Immich puede acceder a la siguiente ruta de archivo y escanear la biblioteca.",
|
||||
"authentication_settings": "Parámetros de autenticación",
|
||||
@@ -68,7 +67,6 @@
|
||||
"confirm_reprocess_all_faces": "¿Estás seguro de que deseas reprocesar todas las caras? Esto borrará a todas las personas que nombraste.",
|
||||
"confirm_user_password_reset": "¿Estás seguro de que quieres restablecer la contraseña de {user}?",
|
||||
"confirm_user_pin_code_reset": "¿Seguro que quieres restablecer el PIN de {user}?",
|
||||
"copy_config_to_clipboard_description": "Copiar la configuración actual del sistema como un objeto JSON al",
|
||||
"create_job": "Crear trabajo",
|
||||
"cron_expression": "Expresión CRON",
|
||||
"cron_expression_description": "Establece el intervalo de escaneo utilizando el formato CRON. Para más información puedes consultar, por ejemplo, <link> Crontab Guru</link>",
|
||||
@@ -76,8 +74,7 @@
|
||||
"disable_login": "Deshabilitar inicio de sesión",
|
||||
"duplicate_detection_job_description": "Lanza el aprendizaje automático para detectar imágenes similares. Necesita tener activado \"Búsqueda Inteligente\"",
|
||||
"exclusion_pattern_description": "Los patrones de exclusión te permiten ignorar archivos y carpetas al escanear tu biblioteca. Es útil si tienes carpetas que contienen archivos que no deseas importar, por ejemplo archivos RAW.",
|
||||
"export_config_as_json_description": "Descargar la configuración actual del sistema como un archivo JSON",
|
||||
"external_libraries_page_description": "Página de biblioteca externa del administrador",
|
||||
"external_library_management": "Gestión de bibliotecas externas",
|
||||
"face_detection": "Detección de caras",
|
||||
"face_detection_description": "Detecta las caras en los elementos mediante aprendizaje automático. En el caso de los vídeos, solo se tiene en cuenta la miniatura. \"Actualizar\" (re)procesará todos los elementos. \"Restablecer\" borra además todos los datos de caras actuales. \"Faltante\" pone en cola los elementos que aún no se han procesado. Las caras detectadas se pondrán en cola para el reconocimiento facial una vez finalizada la detección, agrupándolos en personas existentes o nuevas.",
|
||||
"facial_recognition_job_description": "Agrupa las caras detectadas en personas. Este paso se realiza después de completar la detección de caras. \"Restablecer\" (re)agrupa todas las caras. \"Faltante\" pone en cola las caras que no tienen una persona asignada.",
|
||||
@@ -105,28 +102,23 @@
|
||||
"image_thumbnail_description": "Miniatura pequeña con metadatos eliminados. Se utiliza al visualizar grupos de fotos como la línea temporal principal",
|
||||
"image_thumbnail_quality_description": "Calidad de miniatura de 1 a 100. Es mejor cuanto más alto es el valor pero genera archivos más grandes y puede reducir la capacidad de respuesta de la aplicación.",
|
||||
"image_thumbnail_title": "Ajustes de las miniaturas",
|
||||
"import_config_from_json_description": "Importar la configuración del sistema subiendo un archivo JSON de configuración",
|
||||
"job_concurrency": "{job}: Procesos simultáneos",
|
||||
"job_created": "Tarea creada",
|
||||
"job_not_concurrency_safe": "Esta tarea no es segura para la simultaneidad.",
|
||||
"job_settings": "Configuración de tareas",
|
||||
"job_settings_description": "Administrar tareas simultáneas",
|
||||
"job_status": "Estado de la tarea",
|
||||
"jobs_delayed": "{jobCount, plural, one {# retrasado} other {# retrasados}}",
|
||||
"jobs_failed": "{jobCount, plural, one {# fallido} other {# fallidos}}",
|
||||
"jobs_over_time": "Trabajos a lo largo del tiempo",
|
||||
"library_created": "La biblioteca ha sido creada: {library}",
|
||||
"library_deleted": "Biblioteca eliminada",
|
||||
"library_details": "Detalles de la biblioteca",
|
||||
"library_folder_description": "Especifica una carpeta para importar. Esta carpeta, incluidas sus subcarpetas, se analizará en busca de imágenes y vídeos.",
|
||||
"library_remove_exclusion_pattern_prompt": "¿Estás seguro de que quieres eliminar este patrón de exclusión?",
|
||||
"library_remove_folder_prompt": "¿Estás seguro de que quieres eliminar esta carpeta de importación?",
|
||||
"library_import_path_description": "Indica una carpeta para importar. Esta carpeta y sus subcarpetas serán escaneadas en busca de elementos multimedia.",
|
||||
"library_scanning": "Escaneo periódico",
|
||||
"library_scanning_description": "Configurar el escaneo periódico de la biblioteca",
|
||||
"library_scanning_enable_description": "Activar el escaneo periódico de la biblioteca",
|
||||
"library_settings": "Biblioteca externa",
|
||||
"library_settings_description": "Administrar configuración biblioteca externa",
|
||||
"library_tasks_description": "Buscar elementos nuevos o modificados en bibliotecas externas",
|
||||
"library_updated": "Biblioteca actualizada",
|
||||
"library_watching_enable_description": "Vigilar las bibliotecas externas para detectar cambios en los archivos",
|
||||
"library_watching_settings": "Vigilancia de la biblioteca [EXPERIMENTAL]",
|
||||
"library_watching_settings_description": "Vigilar automaticamente en busca de archivos modificados",
|
||||
@@ -181,12 +173,7 @@
|
||||
"machine_learning_smart_search_enabled": "Habilitar búsqueda inteligente",
|
||||
"machine_learning_smart_search_enabled_description": "Al desactivarlo las imágenes no se procesarán para usar la búsqueda inteligente.",
|
||||
"machine_learning_url_description": "La URL del servidor de aprendizaje automático. Si se proporciona más de una URL se intentará acceder a cada servidor sucesivamente hasta que uno responda correctamente en el orden especificado. Los servidores que no respondan serán ignorados temporalmente hasta que vuelvan a estar en línea.",
|
||||
"maintenance_settings": "Mantenimiento",
|
||||
"maintenance_settings_description": "Poner Immich en modo de mantenimiento.",
|
||||
"maintenance_start": "Iniciar el modo de mantenimiento",
|
||||
"maintenance_start_error": "Error al iniciar el modo de mantenimiento.",
|
||||
"manage_concurrency": "Ajustes de concurrencia",
|
||||
"manage_concurrency_description": "Navegar a la página de trabajos para administrar la concurrencia de trabajos",
|
||||
"manage_log_settings": "Administrar la configuración de los registros",
|
||||
"map_dark_style": "Estilo oscuro",
|
||||
"map_enable_description": "Habilitar características del mapa",
|
||||
@@ -225,8 +212,8 @@
|
||||
"nightly_tasks_start_time_setting_description": "El tiempo cuando el servidor comienza a ejecutar las tareas nocturnas",
|
||||
"nightly_tasks_sync_quota_usage_setting": "Uso de la cuota de sincronización",
|
||||
"nightly_tasks_sync_quota_usage_setting_description": "Actualizar la cuota de almacenamiento del usuario, según el uso actual",
|
||||
"no_paths_added": "No se han añadido rutas",
|
||||
"no_pattern_added": "No se agregó ningún patrón",
|
||||
"no_paths_added": "No se han agregado rutas",
|
||||
"no_pattern_added": "No se han agregado patrones",
|
||||
"note_apply_storage_label_previous_assets": "Nota: Para aplicar la Etiqueta de Almacenamiento a los elementos previamente subidos, ejecuta la",
|
||||
"note_cannot_be_changed_later": "NOTA: ¡No se puede cambiar posteriormente!",
|
||||
"notification_email_from_address": "Desde",
|
||||
@@ -276,14 +263,10 @@
|
||||
"password_settings_description": "Administrar la configuración de inicio de sesión con contraseña",
|
||||
"paths_validated_successfully": "Todas las carpetas se han validado satisfactoriamente",
|
||||
"person_cleanup_job": "Limpieza de personas",
|
||||
"queue_details": "Detalles de cola",
|
||||
"queues": "Colas de trabajo",
|
||||
"queues_page_description": "Página de administración de colas de trabajo",
|
||||
"quota_size_gib": "Tamaño de la cuota (GiB)",
|
||||
"refreshing_all_libraries": "Actualizar todas las bibliotecas",
|
||||
"registration": "Registrar administrador",
|
||||
"registration_description": "Dado que eres el primer usuario del sistema, se te designará como administrador, tendrás a tu cargo las tareas administrativas y deberás crear los demás usuarios.",
|
||||
"remove_failed_jobs": "Eliminar trabajos fallidos",
|
||||
"require_password_change_on_login": "Requerir que el usuario cambie la contraseña en el primer inicio de sesión",
|
||||
"reset_settings_to_default": "Restablecer la configuración predeterminada",
|
||||
"reset_settings_to_recent_saved": "Restablecer la configuración a la configuración guardada recientemente",
|
||||
@@ -293,13 +276,11 @@
|
||||
"server_external_domain_settings": "Dominio externo",
|
||||
"server_external_domain_settings_description": "Dominio para enlaces públicos compartidos, incluidos http(s)://",
|
||||
"server_public_users": "Usuarios públicos",
|
||||
"server_public_users_description": "Cuando se añade un usuario a los álbumes compartidos, todos los usuarios aparecen en una lista con su nombre y su correo electrónico. Si deshabilita esta opción, solo los administradores podrán ver la lista de usuarios.",
|
||||
"server_public_users_description": "Cuando se agrega un usuario a los álbumes compartidos, todos los usuarios aparecen en una lista con su nombre y su correo electrónico. Si deshabilita esta opción, solo los administradores podrán ver la lista de usuarios.",
|
||||
"server_settings": "Configuración del servidor",
|
||||
"server_settings_description": "Administrar la configuración del servidor",
|
||||
"server_stats_page_description": "Página de estadísticas de administrador del servidor",
|
||||
"server_welcome_message": "Mensaje de bienvenida",
|
||||
"server_welcome_message_description": "Mensaje para mostrar en la página de inicio de sesión.",
|
||||
"settings_page_description": "Página de ajustes de administrador",
|
||||
"sidecar_job": "Metadatos de archivos sidecar",
|
||||
"sidecar_job_description": "Descubrir o sincronizar metadatos sidecar desde el sistema de archivos",
|
||||
"slideshow_duration_description": "Número de segundos para mostrar cada imagen",
|
||||
@@ -418,8 +399,7 @@
|
||||
"user_restore_scheduled_removal": "Restaurar el usuario - eliminación programada el {date, date, long}",
|
||||
"user_settings": "Ajustes de usuario",
|
||||
"user_settings_description": "Administrar la configuración del usuario",
|
||||
"user_successfully_removed": "El usuario {email} ha sido eliminado con éxito.",
|
||||
"users_page_description": "Página de usuarios administradores",
|
||||
"user_successfully_removed": "El usuario {email} ha sido eliminado exitosamente.",
|
||||
"version_check_enabled_description": "Activar la comprobación de la versión",
|
||||
"version_check_implications": "La función de comprobación de versiones depende de la comunicación periódica con github.com",
|
||||
"version_check_settings": "Verificar Versión",
|
||||
@@ -450,9 +430,8 @@
|
||||
"age_months": "Tiempo {months, plural, one {# mes} other {# meses}}",
|
||||
"age_year_months": "1 año, {months, plural, one {# mes} other {# meses}}",
|
||||
"age_years": "Edad {years, plural, one {# año} other {# años}}",
|
||||
"album": "Álbum",
|
||||
"album_added": "Álbum añadido",
|
||||
"album_added_notification_setting_description": "Reciba una notificación por correo electrónico cuando lo añadan a un álbum compartido",
|
||||
"album_added": "Álbum agregado",
|
||||
"album_added_notification_setting_description": "Reciba una notificación por correo electrónico cuando lo agreguen a un álbum compartido",
|
||||
"album_cover_updated": "Portada del álbum actualizada",
|
||||
"album_delete_confirmation": "¿Estás seguro de que deseas eliminar el álbum {album}?",
|
||||
"album_delete_confirmation_description": "Si este álbum se comparte, otros usuarios ya no podrán acceder a él.",
|
||||
@@ -480,8 +459,8 @@
|
||||
"album_viewer_appbar_share_err_title": "Error al cambiar el título del álbum",
|
||||
"album_viewer_appbar_share_leave": "Abandonar álbum",
|
||||
"album_viewer_appbar_share_to": "Compartir Con",
|
||||
"album_viewer_page_share_add_users": "Añadir usuarios",
|
||||
"album_with_link_access": "Permitir que cualquiera que tenga el enlace vea las fotos y las personas en este álbum.",
|
||||
"album_viewer_page_share_add_users": "Agregar usuarios",
|
||||
"album_with_link_access": "Permitir que cualquiera que tenga el enlace vea las fotos y las personas del álbum.",
|
||||
"albums": "Álbumes",
|
||||
"albums_count": "{count, plural, one {{count, number} álbum} other {{count, number} álbumes}}",
|
||||
"albums_default_sort_order": "Ordenación por defecto de los álbumes",
|
||||
@@ -496,7 +475,6 @@
|
||||
"allow_edits": "Permitir edición",
|
||||
"allow_public_user_to_download": "Permitir descargas a los usuarios públicos",
|
||||
"allow_public_user_to_upload": "Permitir a los usuarios públicos subir fotos",
|
||||
"allowed": "Permitido",
|
||||
"alt_text_qr_code": "Código QR",
|
||||
"anti_clockwise": "En sentido antihorario",
|
||||
"api_key": "Clave API",
|
||||
@@ -509,25 +487,25 @@
|
||||
"app_bar_signout_dialog_title": "Cerrar sesión",
|
||||
"app_download_links": "Enlaces de Descarga de la Aplicación",
|
||||
"app_settings": "Ajustes de la aplicacion",
|
||||
"app_stores": "Tiendas de Aplicaciones",
|
||||
"app_stores": "App Stores",
|
||||
"app_update_available": "Actualización de aplicación está disponible",
|
||||
"appears_in": "Aparece en",
|
||||
"apply_count": "Aplicar ({count, number})",
|
||||
"archive": "Archivo",
|
||||
"archive_action_prompt": "{count} añadido(s) al archivo",
|
||||
"archive_action_prompt": "{count} agregado(s) al archivo",
|
||||
"archive_or_unarchive_photo": "Archivar o restaurar foto",
|
||||
"archive_page_no_archived_assets": "No se encontraron elementos archivados",
|
||||
"archive_page_title": "Archivo ({count})",
|
||||
"archive_size": "Tamaño de archivo comprimido",
|
||||
"archive_size": "Tamaño del archivo",
|
||||
"archive_size_description": "Configure el tamaño del archivo para descargas (en GB)",
|
||||
"archived": "Archivado",
|
||||
"archived_count": "{count, plural, one {# archivado} other {# archivados}}",
|
||||
"are_these_the_same_person": "¿Son la misma persona?",
|
||||
"are_you_sure_to_do_this": "¿Estás seguro de que quieres hacer esto?",
|
||||
"asset_action_delete_err_read_only": "No se puede borrar archivo(s) de solo lectura, omitiendo",
|
||||
"asset_action_share_err_offline": "No se pudo obtener archivo(s) sin conexión, omitiendo",
|
||||
"asset_added_to_album": "Añadido al álbum",
|
||||
"asset_adding_to_album": "Añadiendo al álbum…",
|
||||
"are_you_sure_to_do_this": "¿Estas seguro de que quieres hacer esto?",
|
||||
"asset_action_delete_err_read_only": "No se pueden borrar el archivo(s) de solo lectura, omitiendo",
|
||||
"asset_action_share_err_offline": "No se pudo obtener el archivo(s) sin conexión, omitiendo",
|
||||
"asset_added_to_album": "Agregado al álbum",
|
||||
"asset_adding_to_album": "Agregando al álbum…",
|
||||
"asset_description_updated": "La descripción del elemento ha sido actualizada",
|
||||
"asset_filename_is_offline": "El archivo {filename} está offline",
|
||||
"asset_has_unassigned_faces": "El archivo no tiene rostros asignados",
|
||||
@@ -552,11 +530,11 @@
|
||||
"asset_viewer_settings_subtitle": "Administra las configuracioens de tu visor de fotos",
|
||||
"asset_viewer_settings_title": "Visor de Archivos",
|
||||
"assets": "elementos",
|
||||
"assets_added_count": "{count, plural, one {# elemento añadido} other {# elementos añadidos}}",
|
||||
"assets_added_to_album_count": "{count, plural, one {# elemento añadido} other {# elementos añadidos}} al álbum",
|
||||
"assets_added_to_albums_count": "{assetTotal, plural, one {# añadido} other {# añadidos}} {albumTotal, plural, one {# al álbum} other {# a los álbumes}}",
|
||||
"assets_cannot_be_added_to_album_count": "{count, plural, one {El elemento no se puede añadir al álbum} other {Los elementos no se pueden añadir al álbum}}",
|
||||
"assets_cannot_be_added_to_albums": "{count, plural, one {El elemento} other {Los elementos}} no se {count, plural, one {puede} other {pueden}} añadir a ninguno de los álbumes",
|
||||
"assets_added_count": "{count, plural, one {# elemento agregado} other {# elementos agregados}}",
|
||||
"assets_added_to_album_count": "{count, plural, one {# elemento agregado} other {# elementos agregados}} al álbum",
|
||||
"assets_added_to_albums_count": "{assetTotal, plural, one {# agregado} other {# agregados}} {albumTotal, plural, one {# al álbum} other {# a los álbumes}}",
|
||||
"assets_cannot_be_added_to_album_count": "{count, plural, one {El elemento no se puede agregar al álbum} other {Los elementos no se pueden agregar al álbum}}",
|
||||
"assets_cannot_be_added_to_albums": "{count, plural, one {El elemento} other {Los elementos}} no se {count, plural, one {puede} other {pueden}} agregar a ninguno de los álbumes",
|
||||
"assets_count": "{count, plural, one {# activo} other {# activos}}",
|
||||
"assets_deleted_permanently": "{count} elemento(s) eliminado(s) permanentemente",
|
||||
"assets_deleted_permanently_from_server": "{count} recurso(s) eliminado(s) de forma permanente del servidor de Immich",
|
||||
@@ -652,7 +630,6 @@
|
||||
"backup_options_page_title": "Opciones de Copia de Seguridad",
|
||||
"backup_setting_subtitle": "Administra las configuraciones de respaldo en segundo y primer plano",
|
||||
"backup_settings_subtitle": "Configura las opciones de subida",
|
||||
"backup_upload_details_page_more_details": "Toca para más detalles",
|
||||
"backward": "Retroceder",
|
||||
"biometric_auth_enabled": "Autentificación biométrica habilitada",
|
||||
"biometric_locked_out": "Estás bloqueado de la autentificación biométrica",
|
||||
@@ -692,7 +669,7 @@
|
||||
"cannot_merge_people": "No se pueden fusionar personas",
|
||||
"cannot_undo_this_action": "¡No puedes deshacer esta acción!",
|
||||
"cannot_update_the_description": "No se puede actualizar la descripción",
|
||||
"cast": "Transmitir",
|
||||
"cast": "Enviar contenido",
|
||||
"cast_description": "Configura los posibles destinos de retransmisión",
|
||||
"change_date": "Cambiar fecha",
|
||||
"change_description": "Cambiar descripción",
|
||||
@@ -719,7 +696,6 @@
|
||||
"check_corrupt_asset_backup_button": "Realizar comprobación",
|
||||
"check_corrupt_asset_backup_description": "Ejecutar esta comprobación solo por Wi-Fi y una vez que todos los archivos hayan sido respaldados. El procedimiento puede tardar unos minutos.",
|
||||
"check_logs": "Comprobar Registros",
|
||||
"checksum": "Suma de comprobación",
|
||||
"choose_matching_people_to_merge": "Elija ocurrencias duplicadas de la misma persona para fusionar",
|
||||
"city": "Ciudad",
|
||||
"clear": "Limpiar",
|
||||
@@ -742,7 +718,6 @@
|
||||
"collapse_all": "Desplegar todo",
|
||||
"color": "Color",
|
||||
"color_theme": "Color del tema",
|
||||
"command": "Comando",
|
||||
"comment_deleted": "Comentario borrado",
|
||||
"comment_options": "Opciones de comentarios",
|
||||
"comments_and_likes": "Comentarios y me gusta",
|
||||
@@ -769,7 +744,7 @@
|
||||
"control_bottom_app_bar_edit_location": "Editar ubicación",
|
||||
"control_bottom_app_bar_edit_time": "Editar fecha y hora",
|
||||
"control_bottom_app_bar_share_link": "Enlace para compartir",
|
||||
"control_bottom_app_bar_share_to": "Compartir a",
|
||||
"control_bottom_app_bar_share_to": "Enviar",
|
||||
"control_bottom_app_bar_trash_from_immich": "Mover a la papelera",
|
||||
"copied_image_to_clipboard": "Imagen copiada al portapapeles.",
|
||||
"copied_to_clipboard": "¡Copiado al portapapeles!",
|
||||
@@ -795,7 +770,7 @@
|
||||
"create_new_person": "Crear nueva persona",
|
||||
"create_new_person_hint": "Asignar los archivos seleccionados a una nueva persona",
|
||||
"create_new_user": "Crear nuevo usuario",
|
||||
"create_shared_album_page_share_add_assets": "AÑADIR ELEMENTOS",
|
||||
"create_shared_album_page_share_add_assets": "AGREGAR ELEMENTOS",
|
||||
"create_shared_album_page_share_select_photos": "Seleccionar fotos",
|
||||
"create_shared_link": "Crear un enlace compartido",
|
||||
"create_tag": "Crear etiqueta",
|
||||
@@ -862,7 +837,7 @@
|
||||
"deleted_shared_link": "Enlace compartido eliminado",
|
||||
"deletes_missing_assets": "Elimina archivos que faltan en el disco duro",
|
||||
"description": "Descripción",
|
||||
"description_input_hint_text": "Añadir descripción...",
|
||||
"description_input_hint_text": "Agregar descripción...",
|
||||
"description_input_submit_error": "Error al actualizar la descripción, comprueba el registro para obtener más detalles",
|
||||
"deselect_all": "Deseleccionar Todo",
|
||||
"details": "Detalles",
|
||||
@@ -919,6 +894,8 @@
|
||||
"edit_description_prompt": "Por favor selecciona una nueva descripción:",
|
||||
"edit_exclusion_pattern": "Editar patrón de exclusión",
|
||||
"edit_faces": "Editar rostros",
|
||||
"edit_import_path": "Editar ruta de importación",
|
||||
"edit_import_paths": "Editar ruta de importación",
|
||||
"edit_key": "Editar clave",
|
||||
"edit_link": "Editar enlace",
|
||||
"edit_location": "Editar ubicación",
|
||||
@@ -953,7 +930,7 @@
|
||||
"error_delete_face": "Error al eliminar la cara del archivo",
|
||||
"error_getting_places": "Error obteniendo lugares",
|
||||
"error_loading_image": "Error al cargar la imagen",
|
||||
"error_loading_partners": "Error al cargar miembros: {error}",
|
||||
"error_loading_partners": "Error al cargar compañeros: {error}",
|
||||
"error_saving_image": "Error: {error}",
|
||||
"error_tag_face_bounding_box": "Error al etiquetar la cara: no se pueden obtener las coordenadas del marco",
|
||||
"error_title": "Error: algo salió mal",
|
||||
@@ -968,8 +945,8 @@
|
||||
"cant_get_number_of_comments": "No se puede obtener la cantidad de comentarios",
|
||||
"cant_search_people": "No se puede buscar a personas",
|
||||
"cant_search_places": "No se pueden buscar lugares",
|
||||
"error_adding_assets_to_album": "Error al añadir los elementos al álbum",
|
||||
"error_adding_users_to_album": "Error al añadir los usuarios al álbum",
|
||||
"error_adding_assets_to_album": "Error al agregar los elementos al álbum",
|
||||
"error_adding_users_to_album": "Error al agregar los usuarios al álbum",
|
||||
"error_deleting_shared_user": "Error al eliminar usuario compartido",
|
||||
"error_downloading": "Error al descargar {filename}",
|
||||
"error_hiding_buy_button": "Error al ocultar el botón de compra",
|
||||
@@ -990,19 +967,20 @@
|
||||
"failed_to_stack_assets": "No se pudieron agrupar los archivos",
|
||||
"failed_to_unstack_assets": "Error al desagrupar los archivos",
|
||||
"failed_to_update_notification_status": "Error al actualizar el estado de la notificación",
|
||||
"import_path_already_exists": "Esta ruta de importación ya existe.",
|
||||
"incorrect_email_or_password": "Contraseña o email incorrecto",
|
||||
"library_folder_already_exists": "Esta ruta de importación ya existe.",
|
||||
"paths_validation_failed": "Falló la validación en {paths, plural, one {# carpeta} other {# carpetas}}",
|
||||
"profile_picture_transparent_pixels": "Las imágenes de perfil no pueden tener píxeles transparentes. Por favor amplíe y/o mueva la imagen.",
|
||||
"quota_higher_than_disk_size": "Se ha establecido una cuota superior al tamaño del disco",
|
||||
"something_went_wrong": "Algo salió mal",
|
||||
"unable_to_add_album_users": "No se pueden añadir usuarios al álbum",
|
||||
"unable_to_add_assets_to_shared_link": "No se pueden añadir archivos al enlace compartido",
|
||||
"unable_to_add_comment": "No se puede añadir comentario",
|
||||
"unable_to_add_exclusion_pattern": "No se puede añadir el patrón de exclusión",
|
||||
"unable_to_add_partners": "No se pueden añadir miembros",
|
||||
"unable_to_add_album_users": "No se pueden agregar usuarios al álbum",
|
||||
"unable_to_add_assets_to_shared_link": "No se pueden agregar archivos al enlace compartido",
|
||||
"unable_to_add_comment": "No se puede agregar comentario",
|
||||
"unable_to_add_exclusion_pattern": "No se puede agregar el patrón de exclusión",
|
||||
"unable_to_add_import_path": "No se puede agregar la ruta de importación",
|
||||
"unable_to_add_partners": "No se pueden agregar compañeros",
|
||||
"unable_to_add_remove_archive": "No se puede archivar {archived, select, true {remove asset from} other {add asset to}}",
|
||||
"unable_to_add_remove_favorites": "{favorite, select, true {No se pudo añadir el elemento a los favoritos} other {No se pudo eliminar el elemento de los favoritos}}",
|
||||
"unable_to_add_remove_favorites": "{favorite, select, true {No se pudo agregar el elemento a los favoritos} other {No se pudo eliminar el elemento de los favoritos}}",
|
||||
"unable_to_archive_unarchive": "{archived, select, true {No se pudo agregar el elemento al archivo} other {No se pudo quitar el elemento del archivo}}",
|
||||
"unable_to_change_album_user_role": "No se puede cambiar la función del usuario del álbum",
|
||||
"unable_to_change_date": "No se puede cambiar la fecha",
|
||||
@@ -1022,10 +1000,12 @@
|
||||
"unable_to_delete_asset": "No se puede eliminar el archivo",
|
||||
"unable_to_delete_assets": "Error al eliminar archivos",
|
||||
"unable_to_delete_exclusion_pattern": "No se puede eliminar el patrón de exclusión",
|
||||
"unable_to_delete_import_path": "No se puede eliminar la ruta de importación",
|
||||
"unable_to_delete_shared_link": "No se puede eliminar el enlace compartido",
|
||||
"unable_to_delete_user": "No se puede eliminar el usuario",
|
||||
"unable_to_download_files": "No se pueden descargar archivos",
|
||||
"unable_to_edit_exclusion_pattern": "No se puede editar el patrón de exclusión",
|
||||
"unable_to_edit_import_path": "No se puede editar la ruta de importación",
|
||||
"unable_to_empty_trash": "No se puede vaciar la papelera",
|
||||
"unable_to_enter_fullscreen": "No se puede acceder al modo pantalla completa",
|
||||
"unable_to_exit_fullscreen": "No se puede salir del modo pantalla completa",
|
||||
@@ -1076,15 +1056,14 @@
|
||||
"unable_to_update_user": "No se puede actualizar el usuario",
|
||||
"unable_to_upload_file": "Error al subir el archivo"
|
||||
},
|
||||
"exclusion_pattern": "Patrón de exclusión",
|
||||
"exif": "EXIF",
|
||||
"exif_bottom_sheet_description": "Añadir descripción…",
|
||||
"exif_bottom_sheet_description": "Agregar descripción…",
|
||||
"exif_bottom_sheet_description_error": "Error al actualizar la descripción",
|
||||
"exif_bottom_sheet_details": "DETALLES",
|
||||
"exif_bottom_sheet_location": "UBICACIÓN",
|
||||
"exif_bottom_sheet_no_description": "Sin descripción",
|
||||
"exif_bottom_sheet_people": "PERSONAS",
|
||||
"exif_bottom_sheet_person_add_person": "Añadir nombre",
|
||||
"exif_bottom_sheet_person_add_person": "Agregar nombre",
|
||||
"exit_slideshow": "Salir de la presentación",
|
||||
"expand_all": "Expandir todo",
|
||||
"experimental_settings_new_asset_list_subtitle": "Trabajo en progreso",
|
||||
@@ -1107,12 +1086,11 @@
|
||||
"external_network_sheet_info": "Cuando no tengas conexión con tu red Wi-Fi preferida, la aplicación se conectará al servidor utilizando la primera de las URL siguientes a la que pueda acceder, empezando de arriba hacia abajo",
|
||||
"face_unassigned": "Sin asignar",
|
||||
"failed": "Fallido",
|
||||
"failed_count": "Fallido: {count}",
|
||||
"failed_to_authenticate": "Fallo al autentificar",
|
||||
"failed_to_load_assets": "Error al cargar los activos",
|
||||
"failed_to_load_folder": "No se pudo cargar la carpeta",
|
||||
"favorite": "Favorito",
|
||||
"favorite_action_prompt": "{count} añadido(s) a Favoritos",
|
||||
"favorite_action_prompt": "{count} agregado(s) a Favoritos",
|
||||
"favorite_or_unfavorite_photo": "Foto favorita o no favorita",
|
||||
"favorites": "Favoritos",
|
||||
"favorites_page_no_favorites": "No se encontraron elementos marcados como favoritos",
|
||||
@@ -1137,7 +1115,6 @@
|
||||
"folders_feature_description": "Explorar la vista de carpetas para las fotos y los videos en el sistema de archivos",
|
||||
"forgot_pin_code_question": "¿Olvidaste tu código PIN?",
|
||||
"forward": "Avanzar",
|
||||
"full_path": "Ruta completa: {path}",
|
||||
"gcast_enabled": "Google Cast",
|
||||
"gcast_enabled_description": "Esta funcionalidad carga recursos externos desde Google para poder funcionar.",
|
||||
"general": "General",
|
||||
@@ -1163,40 +1140,38 @@
|
||||
"hash_asset": "Generar hash del archivo",
|
||||
"hashed_assets": "Archivos con hash generado",
|
||||
"hashing": "Generando hash",
|
||||
"header_settings_add_header_tip": "Añadir cabecera",
|
||||
"header_settings_add_header_tip": "Agregar cabecera",
|
||||
"header_settings_field_validator_msg": "El valor no puede estar vacío",
|
||||
"header_settings_header_name_input": "Nombre de la cabecera",
|
||||
"header_settings_header_value_input": "Valor de la cabecera",
|
||||
"headers_settings_tile_title": "Cabeceras de proxy personalizadas",
|
||||
"height": "Altura",
|
||||
"hi_user": "Hola {name} ({email})",
|
||||
"hide_all_people": "Ocultar a todas las personas",
|
||||
"hide_gallery": "Ocultar galería",
|
||||
"hide_named_person": "Ocultar persona {name}",
|
||||
"hide_password": "Ocultar contraseña",
|
||||
"hide_person": "Ocultar persona",
|
||||
"hide_text_recognition": "Ocultar reconocimiento de texto",
|
||||
"hide_unnamed_people": "Ocultar personas anónimas",
|
||||
"home_page_add_to_album_conflicts": "{added} elementos añadidos al álbum {album}.{failed} elementos ya existen en el álbum.",
|
||||
"home_page_add_to_album_err_local": "Aún no se pueden añadir elementos locales a álbumes, omitiendo",
|
||||
"home_page_add_to_album_success": "Se añadieron {added} elementos al álbum {album}.",
|
||||
"home_page_album_err_partner": "Aún no se pueden añadir elementos de un compañero a un álbum , omitiendo",
|
||||
"home_page_add_to_album_conflicts": "{added} elementos agregados al álbum {album}.{failed} elementos ya existen en el álbum.",
|
||||
"home_page_add_to_album_err_local": "Aún no se pueden agregar elementos locales a álbumes, omitiendo",
|
||||
"home_page_add_to_album_success": "Se agregaron {added} elementos al álbum {album}.",
|
||||
"home_page_album_err_partner": "Aún no se pueden agregar elementos a un álbum de un compañero, omitiendo",
|
||||
"home_page_archive_err_local": "Los elementos locales no pueden ser archivados, omitiendo",
|
||||
"home_page_archive_err_partner": "No se pueden archivar los elementos de un compañero, omitiendo",
|
||||
"home_page_archive_err_partner": "No se pueden archivar los elementos de un compañero; omitiendo",
|
||||
"home_page_building_timeline": "Construyendo la línea de tiempo",
|
||||
"home_page_delete_err_partner": "No se pueden eliminar los elementos de un compañero, omitiendo",
|
||||
"home_page_delete_err_partner": "No se pueden eliminar los elementos de un compañero; omitiendo",
|
||||
"home_page_delete_remote_err_local": "Elementos locales en la selección de eliminación remota, omitiendo",
|
||||
"home_page_favorite_err_local": "Aún no se pueden marcar como favoritos los elementos locales, omitiendo",
|
||||
"home_page_favorite_err_partner": "Aún no se pueden marcar los como favoritos los elementos de un compañero, omitiendo",
|
||||
"home_page_first_time_notice": "Si es la primera vez que usas la aplicación, asegúrate de elegir un álbum como copia de seguridad para que la línea de tiempo pueda mostrar fotos y vídeos en él",
|
||||
"home_page_locked_error_local": "No se pueden mover elementos locales a una carpeta protegida, omitiendo",
|
||||
"home_page_favorite_err_local": "Aún no se pueden archivar elementos locales, omitiendo",
|
||||
"home_page_favorite_err_partner": "Aún no se pueden marcar los elementos de un compañero como favoritos; omitiendo",
|
||||
"home_page_first_time_notice": "Si es la primera vez que usas la aplicación, asegúrate de elegir un álbum de copia de seguridad para que la línea de tiempo pueda mostrar fotos y vídeos en él",
|
||||
"home_page_locked_error_local": "No se pueden mover archivos locales a una carpeta protegida; omitiendo",
|
||||
"home_page_locked_error_partner": "No se pueden mover los elementos de un compañero a una carpeta protegida; omitiendo",
|
||||
"home_page_share_err_local": "No se pueden compartir elementos locales a través de un enlace, omitiendo",
|
||||
"home_page_upload_err_limit": "Solo se pueden subir 30 elementos simultáneamente, omitiendo",
|
||||
"host": "Host",
|
||||
"hour": "Hora",
|
||||
"hours": "Horas",
|
||||
"id": "ID",
|
||||
"id": "Id.",
|
||||
"idle": "Inactivo",
|
||||
"ignore_icloud_photos": "Ignorar fotos de iCloud",
|
||||
"ignore_icloud_photos_description": "Las fotos almacenadas en iCloud no se subirán a Immich",
|
||||
@@ -1221,8 +1196,6 @@
|
||||
"import_path": "Importar ruta",
|
||||
"in_albums": "En {count, plural, one {# álbum} other {# álbumes}}",
|
||||
"in_archive": "En archivo",
|
||||
"in_year": "En {year}",
|
||||
"in_year_selector": "En",
|
||||
"include_archived": "Incluir archivados",
|
||||
"include_shared_albums": "Incluir álbumes compartidos",
|
||||
"include_shared_partner_assets": "Incluir elementos compartidos por compañeros",
|
||||
@@ -1259,7 +1232,6 @@
|
||||
"language_setting_description": "Selecciona tu idioma preferido",
|
||||
"large_files": "Archivos Grandes",
|
||||
"last": "Último",
|
||||
"last_months": "{count, plural, one {Último mes} other {Últimos # meses}}",
|
||||
"last_seen": "Ultima vez visto",
|
||||
"latest_version": "Última versión",
|
||||
"latitude": "Latitud",
|
||||
@@ -1269,8 +1241,6 @@
|
||||
"let_others_respond": "Permitir que otros respondan",
|
||||
"level": "Nivel",
|
||||
"library": "Biblioteca",
|
||||
"library_add_folder": "Añadir carpeta",
|
||||
"library_edit_folder": "Editar carpeta",
|
||||
"library_options": "Opciones de biblioteca",
|
||||
"library_page_device_albums": "Álbumes en el dispositivo",
|
||||
"library_page_new_album": "Nuevo álbum",
|
||||
@@ -1291,7 +1261,6 @@
|
||||
"local": "Local",
|
||||
"local_asset_cast_failed": "No es posible transmitir un recurso que no está subido al servidor",
|
||||
"local_assets": "Archivos Locales",
|
||||
"local_id": "ID local",
|
||||
"local_media_summary": "Resumen de Medios Locales",
|
||||
"local_network": "Red local",
|
||||
"local_network_sheet_info": "La aplicación se conectará al servidor a través de esta URL cuando utilice la red Wi-Fi especificada",
|
||||
@@ -1343,19 +1312,10 @@
|
||||
"loop_videos_description": "Habilite la reproducción automática de un video en el visor de detalles.",
|
||||
"main_branch_warning": "Está utilizando una versión de desarrollo; ¡le recomendamos encarecidamente que utilice una versión de lanzamiento!",
|
||||
"main_menu": "Menú principal",
|
||||
"maintenance_description": "Immich se ha puesto en <link>modo de mantenimiento</link>.",
|
||||
"maintenance_end": "Finalizar el modo de mantenimiento",
|
||||
"maintenance_end_error": "Error al finalizar el modo de mantenimiento.",
|
||||
"maintenance_logged_in_as": "Sesión iniciada actualmente como {user}",
|
||||
"maintenance_title": "No disponible temporalmente",
|
||||
"make": "Marca",
|
||||
"manage_geolocation": "Administrar ubicación",
|
||||
"manage_media_access_rationale": "Este permiso se requiere para mover recursos a la papelera correctamente, así como para restaurarlos desde la misma.",
|
||||
"manage_media_access_settings": "Abrir configuración",
|
||||
"manage_media_access_subtitle": "Permitir a la app Immich gestionar y mover archivos multimedia.",
|
||||
"manage_media_access_title": "Acceso a gestión de archivos multimedia",
|
||||
"manage_shared_links": "Administrar enlaces compartidos",
|
||||
"manage_sharing_with_partners": "Gestionar el uso compartido con miembros",
|
||||
"manage_sharing_with_partners": "Gestionar el uso compartido con compañeros",
|
||||
"manage_the_app_settings": "Administrar la configuración de la aplicación",
|
||||
"manage_your_account": "Gestiona tu cuenta",
|
||||
"manage_your_api_keys": "Administre sus claves API",
|
||||
@@ -1380,11 +1340,11 @@
|
||||
"map_settings_date_range_option_years": "Últimos {years} años",
|
||||
"map_settings_dialog_title": "Ajustes del mapa",
|
||||
"map_settings_include_show_archived": "Incluir archivados",
|
||||
"map_settings_include_show_partners": "Incluir miembros",
|
||||
"map_settings_include_show_partners": "Incluir compañeros",
|
||||
"map_settings_only_show_favorites": "Mostrar solo favoritas",
|
||||
"map_settings_theme_settings": "Apariencia del Mapa",
|
||||
"map_zoom_to_see_photos": "Alejar para ver fotos",
|
||||
"mark_all_as_read": "Marcar todos como leído",
|
||||
"mark_all_as_read": "Marcar todas como leídas",
|
||||
"mark_as_read": "Marcar como leído",
|
||||
"marked_all_as_read": "Todos marcados como leídos",
|
||||
"matches": "Coincidencias",
|
||||
@@ -1417,8 +1377,7 @@
|
||||
"more": "Mas",
|
||||
"move": "Mover",
|
||||
"move_off_locked_folder": "Sacar de la carpeta protegida",
|
||||
"move_to": "Mover a",
|
||||
"move_to_lock_folder_action_prompt": "{count} añadido(s) a la carpeta protegida",
|
||||
"move_to_lock_folder_action_prompt": "{count} agregado(s) a la carpeta protegida",
|
||||
"move_to_locked_folder": "Mover a la carpeta protegida",
|
||||
"move_to_locked_folder_confirmation": "Estas fotos y vídeos se eliminarán de todos los álbumes; solo se podrán ver en la carpeta protegida",
|
||||
"moved_to_archive": "Movido(s) {count, plural, one {# recurso} other {# recursos}} a archivo",
|
||||
@@ -1447,7 +1406,6 @@
|
||||
"new_pin_code": "Nuevo PIN",
|
||||
"new_pin_code_subtitle": "Esta es la primera vez que accedes a la carpeta protegida. Crea un código PIN seguro para acceder a esta página",
|
||||
"new_timeline": "Nueva Línea de tiempo",
|
||||
"new_update": "Nueva actualización",
|
||||
"new_user_created": "Nuevo usuario creado",
|
||||
"new_version_available": "NUEVA VERSIÓN DISPONIBLE",
|
||||
"newest_first": "El más reciente primero",
|
||||
@@ -1463,14 +1421,12 @@
|
||||
"no_cast_devices_found": "No se encontraron dispositivos de transmisión",
|
||||
"no_checksum_local": "Suma de verificación no disponible. No se pueden obtener los elementos locales",
|
||||
"no_checksum_remote": "Suma de verificación no disponible. No se puede obtener el elemento remoto",
|
||||
"no_devices": "Dispositivos no autorizados",
|
||||
"no_duplicates_found": "No se encontraron duplicados.",
|
||||
"no_exif_info_available": "No hay información exif disponible",
|
||||
"no_explore_results_message": "Sube más fotos para explorar tu colección.",
|
||||
"no_favorites_message": "Añade favoritos para encontrar rápidamente sus mejores fotos y videos",
|
||||
"no_favorites_message": "Agregue favoritos para encontrar rápidamente sus mejores fotos y videos",
|
||||
"no_libraries_message": "Crea una biblioteca externa para ver tus fotos y vídeos",
|
||||
"no_local_assets_found": "No se encontraron elementos locales con esta suma de comprobación",
|
||||
"no_location_set": "No se ha establecido ninguna ubicación",
|
||||
"no_locked_photos_message": "Las fotos y los vídeos de la carpeta protegida se mantienen ocultos; no aparecerán cuando veas o busques elementos en tu biblioteca.",
|
||||
"no_name": "Sin nombre",
|
||||
"no_notifications": "Ninguna notificación",
|
||||
@@ -1481,7 +1437,6 @@
|
||||
"no_results_description": "Pruebe con un sinónimo o una palabra clave más general",
|
||||
"no_shared_albums_message": "Crea un álbum para compartir fotos y vídeos con personas de tu red",
|
||||
"no_uploads_in_progress": "No hay cargas en progreso",
|
||||
"not_allowed": "No permitido",
|
||||
"not_available": "N/D",
|
||||
"not_in_any_album": "Sin álbum",
|
||||
"not_selected": "No seleccionado",
|
||||
@@ -1530,7 +1485,6 @@
|
||||
"other_variables": "Otras variables",
|
||||
"owned": "Propios",
|
||||
"owner": "Propietario",
|
||||
"page": "Página",
|
||||
"partner": "Compañero",
|
||||
"partner_can_access": "{partner} tiene acceso",
|
||||
"partner_can_access_assets": "Todas tus fotos y vídeos excepto los Archivados y Eliminados",
|
||||
@@ -1538,13 +1492,13 @@
|
||||
"partner_list_user_photos": "Fotos de {user}",
|
||||
"partner_list_view_all": "Ver todas",
|
||||
"partner_page_empty_message": "Tus fotos aún no se han compartido con ningún compañero.",
|
||||
"partner_page_no_more_users": "No hay más usuarios para añadir",
|
||||
"partner_page_partner_add_failed": "No se pudo añadir al miembro",
|
||||
"partner_page_no_more_users": "No hay más usuarios para agregar",
|
||||
"partner_page_partner_add_failed": "No se pudo agregar el compañero",
|
||||
"partner_page_select_partner": "Seleccionar compañero",
|
||||
"partner_page_shared_to_title": "Compartido con",
|
||||
"partner_page_stop_sharing_content": "{partner} ya no podrá acceder a tus fotos.",
|
||||
"partner_sharing": "Compartir con compañeros",
|
||||
"partners": "Miembros",
|
||||
"partners": "Compañeros",
|
||||
"password": "Contraseña",
|
||||
"password_does_not_match": "Las contraseñas no coinciden",
|
||||
"password_required": "Contraseña requerida",
|
||||
@@ -1593,8 +1547,6 @@
|
||||
"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",
|
||||
"pick_custom_range": "Rango personalizado",
|
||||
"pick_date_range": "Seleccione un rango de fechas",
|
||||
"pin_code_changed_successfully": "PIN cambiado exitosamente",
|
||||
"pin_code_reset_successfully": "PIN restablecido exitosamente",
|
||||
"pin_code_setup_successfully": "PIN establecido exitosamente",
|
||||
@@ -1684,7 +1636,7 @@
|
||||
"recent-albums": "Últimos álbumes",
|
||||
"recent_searches": "Búsquedas recientes",
|
||||
"recently_added": "Añadidos recientemente",
|
||||
"recently_added_page_title": "Recién añadidos",
|
||||
"recently_added_page_title": "Recién Agregadas",
|
||||
"recently_taken": "Tomadas recientemente",
|
||||
"recently_taken_page_title": "Tomadas Recientemente",
|
||||
"refresh": "Actualizar",
|
||||
@@ -1862,8 +1814,6 @@
|
||||
"server_offline": "Servidor desconectado",
|
||||
"server_online": "Servidor en línea",
|
||||
"server_privacy": "Privacidad del Servidor",
|
||||
"server_restarting_description": "Esta página se actualizará en breve.",
|
||||
"server_restarting_title": "El servidor se está reiniciando",
|
||||
"server_stats": "Estadísticas del servidor",
|
||||
"server_update_available": "Actualización de servidor disponible",
|
||||
"server_version": "Versión del servidor",
|
||||
@@ -1905,7 +1855,7 @@
|
||||
"setup_pin_code": "Establecer un PIN",
|
||||
"share": "Compartir",
|
||||
"share_action_prompt": "{count} recursos compartidos",
|
||||
"share_add_photos": "Añadir fotos",
|
||||
"share_add_photos": "Agregar fotos",
|
||||
"share_assets_selected": "{count} seleccionado(s)",
|
||||
"share_dialog_preparing": "Preparando...",
|
||||
"share_link": "Compartir Enlace",
|
||||
@@ -1987,7 +1937,6 @@
|
||||
"show_slideshow_transition": "Mostrar la transición de las diapositivas",
|
||||
"show_supporter_badge": "Insignia de colaborador",
|
||||
"show_supporter_badge_description": "Mostrar una insignia de colaborador",
|
||||
"show_text_recognition": "Mostrar reconocimiento de texto",
|
||||
"show_text_search_menu": "Mostrar el menú de búsqueda",
|
||||
"shuffle": "Modo aleatorio",
|
||||
"sidebar": "Barra lateral",
|
||||
@@ -2058,7 +2007,6 @@
|
||||
"tags": "Etiquetas",
|
||||
"tap_to_run_job": "Toca para ejecutar la tarea",
|
||||
"template": "Plantilla",
|
||||
"text_recognition": "Reconocimiento de texto",
|
||||
"theme": "Tema",
|
||||
"theme_selection": "Selección de tema",
|
||||
"theme_selection_description": "Establece el tema automáticamente como \"claro\" u \"oscuro\" según las preferencias del sistema/navegador",
|
||||
@@ -2079,7 +2027,6 @@
|
||||
"third_party_resources": "Recursos de terceros",
|
||||
"time": "Tiempo",
|
||||
"time_based_memories": "Recuerdos basados en tiempo",
|
||||
"time_based_memories_duration": "Cantidad de segundos que se mostrará cada imagen.",
|
||||
"timeline": "Cronología",
|
||||
"timezone": "Zona horaria",
|
||||
"to_archive": "Archivar",
|
||||
@@ -2091,7 +2038,6 @@
|
||||
"to_select": "para seleccionar",
|
||||
"to_trash": "Descartar",
|
||||
"toggle_settings": "Alternar ajustes",
|
||||
"toggle_theme_description": "Cambiar tema",
|
||||
"total": "Total",
|
||||
"total_usage": "Uso total",
|
||||
"trash": "Papelera",
|
||||
@@ -2181,7 +2127,7 @@
|
||||
"user_usage_stats_description": "Ver estadísticas de uso de la cuenta",
|
||||
"username": "Nombre de usuario",
|
||||
"users": "Usuarios",
|
||||
"users_added_to_album_count": "{count, plural, one {# usuario añadido} other {# usuarios añadidos}} al álbum",
|
||||
"users_added_to_album_count": "{count, plural, one {# usuario agregado} other {# usuarios agregados}} al álbum",
|
||||
"utilities": "Utilidades",
|
||||
"validate": "Validar",
|
||||
"validate_endpoint_error": "Por favor, introduce una URL válida",
|
||||
@@ -2200,7 +2146,6 @@
|
||||
"view_album": "Ver Álbum",
|
||||
"view_all": "Ver todas",
|
||||
"view_all_users": "Mostrar todos los usuarios",
|
||||
"view_asset_owners": "Ver propietarios",
|
||||
"view_details": "Ver Detalles",
|
||||
"view_in_timeline": "Ver en la línea de tiempo",
|
||||
"view_link": "Ver enlace",
|
||||
@@ -2217,14 +2162,11 @@
|
||||
"viewer_unstack": "Desapilar",
|
||||
"visibility_changed": "Visibilidad cambiada para {count, plural, one {# persona} other {# personas}}",
|
||||
"waiting": "Esperando",
|
||||
"waiting_count": "Esperando: {count}",
|
||||
"warning": "Advertencia",
|
||||
"week": "Semana",
|
||||
"welcome": "Bienvenido",
|
||||
"welcome_to_immich": "Bienvenido a Immich",
|
||||
"width": "Ancho",
|
||||
"wifi_name": "Nombre Wi-Fi",
|
||||
"workflow": "Flujo de trabajo",
|
||||
"wrong_pin_code": "Código PIN incorrecto",
|
||||
"year": "Año",
|
||||
"years_ago": "Hace {years, plural, one {# año} other {# años}}",
|
||||
|
||||
79
i18n/et.json
79
i18n/et.json
@@ -7,7 +7,6 @@
|
||||
"action_common_update": "Uuenda",
|
||||
"actions": "Tegevused",
|
||||
"active": "Aktiivne",
|
||||
"active_count": "Aktiivsed: {count}",
|
||||
"activity": "Aktiivsus",
|
||||
"activity_changed": "Aktiivsus on {enabled, select, true {lubatud} other {keelatud}}",
|
||||
"add": "Lisa",
|
||||
@@ -18,6 +17,7 @@
|
||||
"add_birthday": "Lisa sünnipäev",
|
||||
"add_endpoint": "Lisa lõpp-punkt",
|
||||
"add_exclusion_pattern": "Lisa välistamismuster",
|
||||
"add_import_path": "Lisa imporditee",
|
||||
"add_location": "Lisa asukoht",
|
||||
"add_more_users": "Lisa rohkem kasutajaid",
|
||||
"add_partner": "Lisa partner",
|
||||
@@ -32,7 +32,6 @@
|
||||
"add_to_album_toggle": "Muuda albumi {album} valikut",
|
||||
"add_to_albums": "Lisa albumitesse",
|
||||
"add_to_albums_count": "Lisa albumitesse ({count})",
|
||||
"add_to_bottom_bar": "Lisa",
|
||||
"add_to_shared_album": "Lisa jagatud albumisse",
|
||||
"add_upload_to_stack": "Virnasta üleslaaditud üksus",
|
||||
"add_url": "Lisa URL",
|
||||
@@ -68,7 +67,6 @@
|
||||
"confirm_reprocess_all_faces": "Kas oled kindel, et soovid kõik näod uuesti töödelda? See eemaldab kõik nimega isikud.",
|
||||
"confirm_user_password_reset": "Kas oled kindel, et soovid kasutaja {user} parooli lähtestada?",
|
||||
"confirm_user_pin_code_reset": "Kas oled kindel, et soovid kasutaja {user} PIN-koodi lähtestada?",
|
||||
"copy_config_to_clipboard_description": "Kopeeri praegune süsteemi seadistus JSON-objektina lõikelauale",
|
||||
"create_job": "Lisa tööde",
|
||||
"cron_expression": "Cron avaldis",
|
||||
"cron_expression_description": "Määra skaneerimise intervall cron formaadis. Rohkema info jaoks vaata nt. <link>Crontab Guru</link>",
|
||||
@@ -76,8 +74,7 @@
|
||||
"disable_login": "Keela sisselogimine",
|
||||
"duplicate_detection_job_description": "Rakenda üksustele masinõpet, et leida sarnaseid pilte. Kasutab nutiotsingut",
|
||||
"exclusion_pattern_description": "Välistamismustrid võimaldavad ignoreerida faile ja kaustu selle kogu skaneerimisel. See on kasulik, kui sul on kaustu, mis sisaldavad faile, mida sa ei soovi importida, nagu RAW failid.",
|
||||
"export_config_as_json_description": "Laadi praegune süsteemi seadistus JSON-failina alla",
|
||||
"external_libraries_page_description": "Administraatori väliste kogude leht",
|
||||
"external_library_management": "Väliste kogude haldus",
|
||||
"face_detection": "Näoavastus",
|
||||
"face_detection_description": "Avasta üksustest nägusid masinõppe abil. Videote puhul kasutatakse ainult pisipilti. \"Värskenda\" töötleb kõik üksused uuesti. \"Lähtesta\" kustutab lisaks kõik seni leitud näod. \"Puuduvad\" võtab ette üksused, mida pole veel töödeldud. Avastatud näod suunatakse näotuvastusse, et grupeerida nad olemasolevateks või uuteks isikuteks.",
|
||||
"facial_recognition_job_description": "Grupeeri avastatud näod inimesteks. See samm käivitub siis, kui näoavastus on lõppenud. \"Lähtesta\" grupeerib kõik näod uuesti. \"Puuduvad\" võtab ette näod, mida pole isikuga seostatud.",
|
||||
@@ -105,28 +102,23 @@
|
||||
"image_thumbnail_description": "Väike pisipilt ilma metaandmeteta, kasutusel fotode grupikaupa vaatamisel, näiteks ajajoonel",
|
||||
"image_thumbnail_quality_description": "Pisipildi kvaliteet vahemikus 1-100. Kõrgem väärtus on parem, aga tekitab suuremaid faile ning võib mõjutada rakenduse töökiirust.",
|
||||
"image_thumbnail_title": "Pisipildi seaded",
|
||||
"import_config_from_json_description": "Impordi süsteemi seadistus JSON-faili üleslaadimise teel",
|
||||
"job_concurrency": "{job} samaaegsus",
|
||||
"job_created": "Tööde lisatud",
|
||||
"job_not_concurrency_safe": "Seda töödet pole ohutu samaaegselt käivitada.",
|
||||
"job_settings": "Töödete seaded",
|
||||
"job_settings_description": "Halda töödete samaaegsust",
|
||||
"job_status": "Tööte seisund",
|
||||
"jobs_delayed": "{jobCount, plural, other {# edasi lükatud}}",
|
||||
"jobs_failed": "{jobCount, plural, other {# ebaõnnestus}}",
|
||||
"jobs_over_time": "Tööted aja jooksul",
|
||||
"library_created": "Lisatud kogu: {library}",
|
||||
"library_deleted": "Kogu kustutatud",
|
||||
"library_details": "Kogu üksikasjad",
|
||||
"library_folder_description": "Vali kaust, mida importida. Sellest kaustast ja alamkaustadest otsitakse pilte ja videosid.",
|
||||
"library_remove_exclusion_pattern_prompt": "Kas oled kindel, et soovid selle välistamismustri eemaldada?",
|
||||
"library_remove_folder_prompt": "Kas oled kindel, et soovid selle impordikausta eemaldada?",
|
||||
"library_import_path_description": "Määra kaust, mida importida. Sellest kaustast ning alamkaustadest otsitakse pilte ja videosid.",
|
||||
"library_scanning": "Perioodiline skaneerimine",
|
||||
"library_scanning_description": "Seadista kogu perioodiline skaneerimine",
|
||||
"library_scanning_enable_description": "Luba kogu perioodiline skaneerimine",
|
||||
"library_settings": "Väline kogu",
|
||||
"library_settings_description": "Halda välise kogu seadeid",
|
||||
"library_tasks_description": "Otsi välistest kogudest uusi ja muutunud üksuseid",
|
||||
"library_updated": "Kogu uuendatud",
|
||||
"library_watching_enable_description": "Jälgi välises kogus failide muudatusi",
|
||||
"library_watching_settings": "Kogu jälgimine [EKSPERIMENTAALNE]",
|
||||
"library_watching_settings_description": "Jälgi automaatselt muutunud faile",
|
||||
@@ -181,12 +173,7 @@
|
||||
"machine_learning_smart_search_enabled": "Luba nutiotsing",
|
||||
"machine_learning_smart_search_enabled_description": "Kui keelatud, siis ei kodeerita pilte nutiotsingu jaoks.",
|
||||
"machine_learning_url_description": "Masinõppe serveri URL. Kui ette on antud rohkem kui üks URL, proovitakse neid järjest ükshaaval, kuni üks edukalt vastab. Servereid, mis ei vasta, ignoreeritakse ajutiselt, kuni ühendus taastub.",
|
||||
"maintenance_settings": "Hooldus",
|
||||
"maintenance_settings_description": "Pane Immich hooldusrežiimi.",
|
||||
"maintenance_start": "Käivita hooldusrežiim",
|
||||
"maintenance_start_error": "Hooldusrežiimi käivitamine ebaõnnestus.",
|
||||
"manage_concurrency": "Halda samaaegsust",
|
||||
"manage_concurrency_description": "Töödete samaaegsuse haldamiseks mine töödete lehele",
|
||||
"manage_log_settings": "Halda logi seadeid",
|
||||
"map_dark_style": "Tume stiil",
|
||||
"map_enable_description": "Luba kaardi funktsioonid",
|
||||
@@ -276,14 +263,10 @@
|
||||
"password_settings_description": "Halda parooliga sisselogimise seadeid",
|
||||
"paths_validated_successfully": "Kõik teed edukalt valideeritud",
|
||||
"person_cleanup_job": "Isikute korrastamine",
|
||||
"queue_details": "Järjekorra üksikasjad",
|
||||
"queues": "Töödete järjekorrad",
|
||||
"queues_page_description": "Administraatori töödete järjekordade leht",
|
||||
"quota_size_gib": "Kvoot (GiB)",
|
||||
"refreshing_all_libraries": "Kõikide kogude värskendamine",
|
||||
"registration": "Administraatori registreerimine",
|
||||
"registration_description": "Kuna sa oled süsteemis esimene kasutaja, määratakse sind administraatoriks, ning sa saad lisada täiendavaid kasutajaid.",
|
||||
"remove_failed_jobs": "Eemalda ebaõnnestunud tööted",
|
||||
"require_password_change_on_login": "Nõua kasutajalt esmakordsel sisenemisel parooli muutmist",
|
||||
"reset_settings_to_default": "Lähtesta seaded",
|
||||
"reset_settings_to_recent_saved": "Taasta hiljuti salvestatud seaded",
|
||||
@@ -296,10 +279,8 @@
|
||||
"server_public_users_description": "Kasutaja jagatud albumisse lisamisel kuvatakse kõiki kasutajaid (nime ja e-posti aadressiga). Kui keelatud, kuvatakse kasutajate nimekirja ainult administraatoritele.",
|
||||
"server_settings": "Serveri seaded",
|
||||
"server_settings_description": "Halda serveri seadeid",
|
||||
"server_stats_page_description": "Administraatori serveri statistika leht",
|
||||
"server_welcome_message": "Tervitusteade",
|
||||
"server_welcome_message_description": "Teade, mida kuvatakse sisselogimise lehel.",
|
||||
"settings_page_description": "Administraatori seadete leht",
|
||||
"sidecar_job": "Väliste failide metaandmed",
|
||||
"sidecar_job_description": "Avasta või sünkroniseeri väliste failide metaandmed failisüsteemist",
|
||||
"slideshow_duration_description": "Mitu sekundit igat pilti kuvada",
|
||||
@@ -410,7 +391,7 @@
|
||||
"user_delete_delay_settings_description": "Päevade arv, pärast mida kustutatakse eemaldatud kasutaja konto ja üksused jäädavalt. Kasutajate kustutamise tööde käivitub keskööl, et otsida kustutamiseks valmis kasutajaid. Selle seadistuse muudatused rakenduvad järgmisel käivitumisel.",
|
||||
"user_delete_immediately": "Kasutaja <b>{user}</b> konto ja üksused suunatakse <b>koheselt</b> jäädavale kustutamisele.",
|
||||
"user_delete_immediately_checkbox": "Suuna kasutaja ja üksused jäädavale kustutamisele",
|
||||
"user_details": "Kasutaja üksikasjad",
|
||||
"user_details": "Kasutaja detailid",
|
||||
"user_management": "Kasutajate haldus",
|
||||
"user_password_has_been_reset": "Kasutaja parool on lähtestatud:",
|
||||
"user_password_reset_description": "Sisesta kasutajale ajutine parool ja teavita teda, et järgmisel sisselogimisel tuleb parool ära muuta.",
|
||||
@@ -418,8 +399,7 @@
|
||||
"user_restore_scheduled_removal": "Taasta kasutaja - eemaldamine planeeritud {date, date, long}",
|
||||
"user_settings": "Kasutajate seaded",
|
||||
"user_settings_description": "Halda kasutajate seadeid",
|
||||
"user_successfully_removed": "Kasutaja {email} edukalt eemaldatud.",
|
||||
"users_page_description": "Administraatori kasutajate leht",
|
||||
"user_successfully_removed": "Kasutaja {email} on eemaldatud.",
|
||||
"version_check_enabled_description": "Luba versioonikontroll",
|
||||
"version_check_implications": "Versioonikontroll vajab perioodilist ühendumist github.com-iga",
|
||||
"version_check_settings": "Versioonikontroll",
|
||||
@@ -450,7 +430,6 @@
|
||||
"age_months": "Vanus {months, plural, one {# kuu} other {# kuud}}",
|
||||
"age_year_months": "Vanus 1 aasta, {months, plural, one {# kuu} other {# kuud}}",
|
||||
"age_years": "{years, plural, other {Vanus #}}",
|
||||
"album": "Album",
|
||||
"album_added": "Album lisatud",
|
||||
"album_added_notification_setting_description": "Saa teavitus e-posti teel, kui sind lisatakse jagatud albumisse",
|
||||
"album_cover_updated": "Albumi kaanepilt muudetud",
|
||||
@@ -496,7 +475,6 @@
|
||||
"allow_edits": "Luba muutmine",
|
||||
"allow_public_user_to_download": "Luba avalikul kasutajal alla laadida",
|
||||
"allow_public_user_to_upload": "Luba avalikul kasutajal üles laadida",
|
||||
"allowed": "Lubatud",
|
||||
"alt_text_qr_code": "QR kood",
|
||||
"anti_clockwise": "Vastupäeva",
|
||||
"api_key": "API võti",
|
||||
@@ -740,7 +718,6 @@
|
||||
"collapse_all": "Peida kõik",
|
||||
"color": "Värv",
|
||||
"color_theme": "Värviteema",
|
||||
"command": "Käsk",
|
||||
"comment_deleted": "Kommentaar kustutatud",
|
||||
"comment_options": "Kommentaari valikud",
|
||||
"comments_and_likes": "Kommentaarid ja meeldimised",
|
||||
@@ -917,6 +894,8 @@
|
||||
"edit_description_prompt": "Palun vali uus kirjeldus:",
|
||||
"edit_exclusion_pattern": "Muuda välistamismustrit",
|
||||
"edit_faces": "Muuda nägusid",
|
||||
"edit_import_path": "Muuda imporditeed",
|
||||
"edit_import_paths": "Muuda imporditeid",
|
||||
"edit_key": "Muuda võtit",
|
||||
"edit_link": "Muuda linki",
|
||||
"edit_location": "Muuda asukohta",
|
||||
@@ -988,8 +967,8 @@
|
||||
"failed_to_stack_assets": "Üksuste virnastamine ebaõnnestus",
|
||||
"failed_to_unstack_assets": "Üksuste eraldamine ebaõnnestus",
|
||||
"failed_to_update_notification_status": "Teavituste seisundi uuendamine ebaõnnestus",
|
||||
"import_path_already_exists": "See imporditee on juba olemas.",
|
||||
"incorrect_email_or_password": "Vale e-posti aadress või parool",
|
||||
"library_folder_already_exists": "See imporditee on juba olemas.",
|
||||
"paths_validation_failed": "{paths, plural, one {# tee} other {# teed}} ei valideerunud",
|
||||
"profile_picture_transparent_pixels": "Profiilipildis ei tohi olla läbipaistvaid piksleid. Palun suumi sisse ja/või liiguta pilti.",
|
||||
"quota_higher_than_disk_size": "Määratud kvoot on suurem kui kettamaht",
|
||||
@@ -998,6 +977,7 @@
|
||||
"unable_to_add_assets_to_shared_link": "Üksuste jagatud lingile lisamine ebaõnnestus",
|
||||
"unable_to_add_comment": "Kommentaari lisamine ebaõnnestus",
|
||||
"unable_to_add_exclusion_pattern": "Välistamismustri lisamine ebaõnnestus",
|
||||
"unable_to_add_import_path": "Imporditee lisamine ebaõnnestus",
|
||||
"unable_to_add_partners": "Partnerite lisamine ebaõnnestus",
|
||||
"unable_to_add_remove_archive": "{archived, select, true {Üksuse arhiivist taastamine} other {Üksuse arhiveerimine}} ebaõnnestus",
|
||||
"unable_to_add_remove_favorites": "Üksuse {favorite, select, true {lemmikuks lisamine} other {lemmikutest eemaldamine}} ebaõnnestus",
|
||||
@@ -1020,10 +1000,12 @@
|
||||
"unable_to_delete_asset": "Üksuse kustutamine ebaõnnestus",
|
||||
"unable_to_delete_assets": "Viga üksuste kustutamisel",
|
||||
"unable_to_delete_exclusion_pattern": "Välistamismustri kustutamine ebaõnnestus",
|
||||
"unable_to_delete_import_path": "Imporditee kustutamine ebaõnnestus",
|
||||
"unable_to_delete_shared_link": "Jagatud lingi kustutamine ebaõnnestus",
|
||||
"unable_to_delete_user": "Kasutaja kustutamine ebaõnnestus",
|
||||
"unable_to_download_files": "Failide allalaadimine ebaõnnestus",
|
||||
"unable_to_edit_exclusion_pattern": "Välistamismustri muutmine ebaõnnestus",
|
||||
"unable_to_edit_import_path": "Imporditee muutmine ebaõnnestus",
|
||||
"unable_to_empty_trash": "Prügikasti tühjendamine ebaõnnestus",
|
||||
"unable_to_enter_fullscreen": "Täisekraanile lülitamine ebaõnnestus",
|
||||
"unable_to_exit_fullscreen": "Täisekraanilt väljumine ebaõnnestus",
|
||||
@@ -1074,7 +1056,6 @@
|
||||
"unable_to_update_user": "Kasutaja muutmine ebaõnnestus",
|
||||
"unable_to_upload_file": "Faili üleslaadimine ebaõnnestus"
|
||||
},
|
||||
"exclusion_pattern": "Välistamismuster",
|
||||
"exif": "Exif",
|
||||
"exif_bottom_sheet_description": "Lisa kirjeldus...",
|
||||
"exif_bottom_sheet_description_error": "Viga kirjelduse muutmisel",
|
||||
@@ -1105,7 +1086,6 @@
|
||||
"external_network_sheet_info": "Kui seade ei ole eelistatud WiFi-võrgus, ühendub rakendus serveriga allolevatest URL-idest esimese kättesaadava kaudu, alustades ülevalt",
|
||||
"face_unassigned": "Seostamata",
|
||||
"failed": "Ebaõnnestus",
|
||||
"failed_count": "Ebaõnnestunud: {count}",
|
||||
"failed_to_authenticate": "Autentimine ebaõnnestus",
|
||||
"failed_to_load_assets": "Üksuste laadimine ebaõnnestus",
|
||||
"failed_to_load_folder": "Kausta laadimine ebaõnnestus",
|
||||
@@ -1135,7 +1115,6 @@
|
||||
"folders_feature_description": "Kaustavaate abil failisüsteemis olevate fotode ja videote sirvimine",
|
||||
"forgot_pin_code_question": "Unustasid oma PIN-koodi?",
|
||||
"forward": "Edasi",
|
||||
"full_path": "Täielik tee: {path}",
|
||||
"gcast_enabled": "Google Cast",
|
||||
"gcast_enabled_description": "See funktsionaalsus laadib töötamiseks Google'st väliseid ressursse.",
|
||||
"general": "Üldine",
|
||||
@@ -1172,7 +1151,6 @@
|
||||
"hide_named_person": "Peida isik {name}",
|
||||
"hide_password": "Peida parool",
|
||||
"hide_person": "Peida isik",
|
||||
"hide_text_recognition": "Peida tekstituvastus",
|
||||
"hide_unnamed_people": "Peida nimetud isikud",
|
||||
"home_page_add_to_album_conflicts": "{added} üksust lisati albumisse {album}. {failed} üksust oli juba albumis.",
|
||||
"home_page_add_to_album_err_local": "Lokaalseid üksuseid ei saa veel albumisse lisada, jäetakse vahele",
|
||||
@@ -1218,8 +1196,6 @@
|
||||
"import_path": "Imporditee",
|
||||
"in_albums": "{count, plural, one {# albumis} other {# albumis}}",
|
||||
"in_archive": "Arhiivis",
|
||||
"in_year": "Aastal {year}",
|
||||
"in_year_selector": "Aastal",
|
||||
"include_archived": "Kaasa arhiveeritud",
|
||||
"include_shared_albums": "Kaasa jagatud albumid",
|
||||
"include_shared_partner_assets": "Kaasa partneri jagatud üksused",
|
||||
@@ -1256,7 +1232,6 @@
|
||||
"language_setting_description": "Vali oma eelistatud keel",
|
||||
"large_files": "Suured failid",
|
||||
"last": "Viimane",
|
||||
"last_months": "{count, plural, one {Eelmine kuu} other {Eelmised # kuud}}",
|
||||
"last_seen": "Viimati nähtud",
|
||||
"latest_version": "Uusim versioon",
|
||||
"latitude": "Laiuskraad",
|
||||
@@ -1266,8 +1241,6 @@
|
||||
"let_others_respond": "Luba teistel vastata",
|
||||
"level": "Tase",
|
||||
"library": "Kogu",
|
||||
"library_add_folder": "Lisa kaust",
|
||||
"library_edit_folder": "Muuda kausta",
|
||||
"library_options": "Kogu seaded",
|
||||
"library_page_device_albums": "Albumid seadmes",
|
||||
"library_page_new_album": "Uus album",
|
||||
@@ -1339,17 +1312,8 @@
|
||||
"loop_videos_description": "Lülita sisse, et detailvaates videot automaatselt taasesitada.",
|
||||
"main_branch_warning": "Sa kasutad arendusversiooni; soovitame tungivalt kasutada väljalaskeversiooni!",
|
||||
"main_menu": "Peamenüü",
|
||||
"maintenance_description": "Immich on <link>hooldusrežiimis</link>.",
|
||||
"maintenance_end": "Lõpeta hooldusrežiim",
|
||||
"maintenance_end_error": "Hooldusrežiimi lõpetamine ebaõnnestus.",
|
||||
"maintenance_logged_in_as": "Logitud sisse kasutajana {user}",
|
||||
"maintenance_title": "Ajutiselt mittesaadaval",
|
||||
"make": "Mark",
|
||||
"manage_geolocation": "Halda asukohta",
|
||||
"manage_media_access_rationale": "Seda luba on vaja üksuste prügikasti liigutamiseks ja sealt taastamiseks.",
|
||||
"manage_media_access_settings": "Ava seaded",
|
||||
"manage_media_access_subtitle": "Luba Immich'i rakendusel multimeediafaile hallata ja liigutada.",
|
||||
"manage_media_access_title": "Üksuste haldamise ligipääs",
|
||||
"manage_shared_links": "Halda jagatud linke",
|
||||
"manage_sharing_with_partners": "Halda partneritega jagamist",
|
||||
"manage_the_app_settings": "Halda rakenduse seadeid",
|
||||
@@ -1413,7 +1377,6 @@
|
||||
"more": "Rohkem",
|
||||
"move": "Liiguta",
|
||||
"move_off_locked_folder": "Liiguta lukustatud kaustast välja",
|
||||
"move_to": "Liiguta",
|
||||
"move_to_lock_folder_action_prompt": "{count} lisatud lukustatud kausta",
|
||||
"move_to_locked_folder": "Liiguta lukustatud kausta",
|
||||
"move_to_locked_folder_confirmation": "Need fotod ja videod eemaldatakse kõigist albumitest ning nad on nähtavad ainult lukustatud kaustas",
|
||||
@@ -1443,7 +1406,6 @@
|
||||
"new_pin_code": "Uus PIN-kood",
|
||||
"new_pin_code_subtitle": "See on sul esimene kord lukustatud kausta kasutada. Turvaliseks ligipääsuks loo PIN-kood",
|
||||
"new_timeline": "Uus ajajoon",
|
||||
"new_update": "Uus uuendus",
|
||||
"new_user_created": "Uus kasutaja lisatud",
|
||||
"new_version_available": "UUS VERSIOON SAADAVAL",
|
||||
"newest_first": "Uuemad eespool",
|
||||
@@ -1459,14 +1421,12 @@
|
||||
"no_cast_devices_found": "Edastamise seadmeid ei leitud",
|
||||
"no_checksum_local": "Kontrollsumma pole saadaval - lokaalse üksuse pärimine ebaõnnestus",
|
||||
"no_checksum_remote": "Kontrollsumma pole saadaval - kaugüksuse pärimine ebaõnnestus",
|
||||
"no_devices": "Autoriseeritud seadmeid pole",
|
||||
"no_duplicates_found": "Ühtegi duplikaati ei leitud.",
|
||||
"no_exif_info_available": "Exif info pole saadaval",
|
||||
"no_explore_results_message": "Oma kogu avastamiseks laadi üles rohkem fotosid.",
|
||||
"no_favorites_message": "Lisa lemmikud, et oma parimaid fotosid ja videosid kiiresti leida",
|
||||
"no_libraries_message": "Lisa väline kogu oma fotode ja videote vaatamiseks",
|
||||
"no_local_assets_found": "Selle kontrollsummaga lokaalseid üksuseid ei leitud",
|
||||
"no_location_set": "Asukoht pole määratud",
|
||||
"no_locked_photos_message": "Lukustatud kaustas olevad fotod ja videod on peidetud ning need pole kogu sirvimisel ja otsimisel nähtavad.",
|
||||
"no_name": "Nimetu",
|
||||
"no_notifications": "Teavitusi pole",
|
||||
@@ -1477,7 +1437,6 @@
|
||||
"no_results_description": "Proovi sünonüümi või üldisemat märksõna",
|
||||
"no_shared_albums_message": "Lisa album, et fotosid ja videosid teistega jagada",
|
||||
"no_uploads_in_progress": "Üleslaadimisi käimas ei ole",
|
||||
"not_allowed": "Keelatud",
|
||||
"not_available": "Pole saadaval",
|
||||
"not_in_any_album": "Pole üheski albumis",
|
||||
"not_selected": "Ei ole valitud",
|
||||
@@ -1526,7 +1485,6 @@
|
||||
"other_variables": "Muud muutujad",
|
||||
"owned": "Minu omad",
|
||||
"owner": "Omanik",
|
||||
"page": "Leht",
|
||||
"partner": "Partner",
|
||||
"partner_can_access": "{partner} pääseb ligi",
|
||||
"partner_can_access_assets": "Kõik su fotod ja videod, välja arvatud arhiveeritud ja kustutatud",
|
||||
@@ -1589,8 +1547,6 @@
|
||||
"photos_count": "{count, plural, one {{count, number} foto} other {{count, number} fotot}}",
|
||||
"photos_from_previous_years": "Fotod varasematest aastatest",
|
||||
"pick_a_location": "Vali asukoht",
|
||||
"pick_custom_range": "Kohandatud vahemik",
|
||||
"pick_date_range": "Vali kuupäevavahemik",
|
||||
"pin_code_changed_successfully": "PIN-kood edukalt muudetud",
|
||||
"pin_code_reset_successfully": "PIN-kood edukalt lähtestatud",
|
||||
"pin_code_setup_successfully": "PIN-kood edukalt seadistatud",
|
||||
@@ -1858,8 +1814,6 @@
|
||||
"server_offline": "Serveriga ühendus puudub",
|
||||
"server_online": "Server ühendatud",
|
||||
"server_privacy": "Serveri privaatsus",
|
||||
"server_restarting_description": "Leht värskendatakse hetkeliselt.",
|
||||
"server_restarting_title": "Server taaskäivitub",
|
||||
"server_stats": "Serveri statistika",
|
||||
"server_update_available": "Serveri uuendus on saadaval",
|
||||
"server_version": "Serveri versioon",
|
||||
@@ -1983,7 +1937,6 @@
|
||||
"show_slideshow_transition": "Kuva slaidiesitluse üleminekud",
|
||||
"show_supporter_badge": "Toetaja märk",
|
||||
"show_supporter_badge_description": "Kuva toetaja märki",
|
||||
"show_text_recognition": "Kuva tekstituvastust",
|
||||
"show_text_search_menu": "Kuva tekstiotsingu menüüd",
|
||||
"shuffle": "Juhuslik",
|
||||
"sidebar": "Külgmenüü",
|
||||
@@ -2054,7 +2007,6 @@
|
||||
"tags": "Sildid",
|
||||
"tap_to_run_job": "Puuduta tööte käivitamiseks",
|
||||
"template": "Mall",
|
||||
"text_recognition": "Tekstituvastus",
|
||||
"theme": "Teema",
|
||||
"theme_selection": "Teema valik",
|
||||
"theme_selection_description": "Sea automaatselt hele või tume teema vastavalt veebilehitseja eelistustele",
|
||||
@@ -2075,7 +2027,6 @@
|
||||
"third_party_resources": "Kolmanda osapoole ressursid",
|
||||
"time": "Aeg",
|
||||
"time_based_memories": "Ajapõhised mälestused",
|
||||
"time_based_memories_duration": "Aeg sekundites, kui kaua igat pilti kuvada.",
|
||||
"timeline": "Ajajoon",
|
||||
"timezone": "Ajavöönd",
|
||||
"to_archive": "Arhiivi",
|
||||
@@ -2087,7 +2038,6 @@
|
||||
"to_select": "vali",
|
||||
"to_trash": "Prügikasti",
|
||||
"toggle_settings": "Kuva/peida seaded",
|
||||
"toggle_theme_description": "Vaheta teema",
|
||||
"total": "Kokku",
|
||||
"total_usage": "Kogukasutus",
|
||||
"trash": "Prügikast",
|
||||
@@ -2192,11 +2142,10 @@
|
||||
"video_hover_setting_description": "Esita video eelvaade, kui hiirt selle kohal hõljutada. Isegi kui keelatud, saab taasesituse alustada taasesitusnupu kohal hõljutades.",
|
||||
"videos": "Videod",
|
||||
"videos_count": "{count, plural, one {# video} other {# videot}}",
|
||||
"view": "Vaata",
|
||||
"view": "Vaade",
|
||||
"view_album": "Vaata albumit",
|
||||
"view_all": "Vaata kõiki",
|
||||
"view_all_users": "Vaata kõiki kasutajaid",
|
||||
"view_asset_owners": "Vaata üksuse omanikke",
|
||||
"view_details": "Vaata üksikasju",
|
||||
"view_in_timeline": "Vaata ajajoonel",
|
||||
"view_link": "Vaata linki",
|
||||
@@ -2213,13 +2162,11 @@
|
||||
"viewer_unstack": "Eralda",
|
||||
"visibility_changed": "{count, plural, one {# isiku} other {# isiku}} nähtavus muudetud",
|
||||
"waiting": "Ootel",
|
||||
"waiting_count": "Ootel: {count}",
|
||||
"warning": "Hoiatus",
|
||||
"week": "Nädal",
|
||||
"welcome": "Tere tulemast",
|
||||
"welcome_to_immich": "Tere tulemast Immich'isse",
|
||||
"wifi_name": "WiFi-võrgu nimi",
|
||||
"workflow": "Töövoog",
|
||||
"wrong_pin_code": "Vale PIN-kood",
|
||||
"year": "Aasta",
|
||||
"years_ago": "{years, plural, one {# aasta} other {# aastat}} tagasi",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"add_birthday": "Urtebetetzea gehitu",
|
||||
"add_endpoint": "Endpoint-a gehitu",
|
||||
"add_exclusion_pattern": "Bazterketa eredua gehitu",
|
||||
"add_import_path": "Inportazio bidea gehitu",
|
||||
"add_location": "Kokapena gehitu",
|
||||
"add_more_users": "Erabiltzaile gehiago gehitu",
|
||||
"add_partner": "Kidea gehitu",
|
||||
@@ -62,6 +63,7 @@
|
||||
"image_thumbnail_title": "Argazki Txikien Konfigurazioa",
|
||||
"job_created": "Zeregina sortuta",
|
||||
"job_settings": "Zereginaren konfigurazioa",
|
||||
"job_status": "Zereginaren Egoera",
|
||||
"logging_enable_description": "Gaitu erregistroak",
|
||||
"logging_level_description": "Erregistroak gaituta daudenean, nolako erregistro maila erabili.",
|
||||
"logging_settings": "Erregistroak",
|
||||
|
||||
11
i18n/fa.json
11
i18n/fa.json
@@ -15,28 +15,23 @@
|
||||
"add_a_title": "افزودن عنوان",
|
||||
"add_birthday": "افزودن تاریخ تولد",
|
||||
"add_exclusion_pattern": "افزودن الگوی استثنا",
|
||||
"add_import_path": "افزودن مسیر ورودی",
|
||||
"add_location": "افزودن مکان",
|
||||
"add_more_users": "افزودن کاربرهای بیشتر",
|
||||
"add_partner": "افزودن شریک",
|
||||
"add_path": "افزودن مسیر",
|
||||
"add_photos": "افزودن عکس ها",
|
||||
"add_tag": "افزودن تگ",
|
||||
"add_to": "افزودن به …",
|
||||
"add_to_album": "افزودن به آلبوم",
|
||||
"add_to_album_bottom_sheet_added": "به آلبوم {album} اضافه شد",
|
||||
"add_to_album_bottom_sheet_already_exists": "قبلا در آلبوم {album} موجود است",
|
||||
"add_to_album_bottom_sheet_some_local_assets": "برخی از محتواهای محلی را نشد به آلبوم اضافه کرد",
|
||||
"add_to_albums": "افزودن به آلبوم",
|
||||
"add_to_albums_count": "افزودن به آلبوم ها {count}",
|
||||
"add_to_shared_album": "افزودن به آلبوم اشتراکی",
|
||||
"add_upload_to_stack": "افزودن فایل ارسالی به مجموعه",
|
||||
"add_url": "افزودن آدرس URL",
|
||||
"added_to_archive": "به آرشیو اضافه شد",
|
||||
"added_to_favorites": "به علاقه مندی ها اضافه شد",
|
||||
"added_to_favorites_count": "{count, number} تا به علاقه مندی ها اضافه شد",
|
||||
"admin": {
|
||||
"add_exclusion_pattern_description": "الگوهای استثنا را اضافه کنید. پشتیبانی از گلابینگ با استفاده از *, ** و ? وجود دارد. برای نادیده گرفتن تمام فایلها در هر دایرکتوری با نام \"Raw\"، از \"**/Raw/**\" استفاده کنید. برای نادیده گرفتن تمام فایلهایی که با \".tif\" پایان مییابند، از \"**/*.tif\" استفاده کنید. برای نادیده گرفتن یک مسیر مطلق، از \"/path/to/ignore/**\" استفاده کنید.",
|
||||
"admin_user": "ادمین",
|
||||
"authentication_settings": "تنظیمات احراز هویت",
|
||||
"authentication_settings_description": "مدیریت رمز عبور، OAuth، و سایر تنظیمات احراز هویت",
|
||||
"authentication_settings_disable_all": "آیا مطمئن هستید که میخواهید تمام روشهای ورود را غیرفعال کنید؟ ورود به طور کامل غیرفعال خواهد شد.",
|
||||
@@ -53,6 +48,7 @@
|
||||
"disable_login": "غیرفعال کردن ورود",
|
||||
"duplicate_detection_job_description": "اجرای یادگیری ماشین بر روی فایلها برای شناسایی تصاویر مشابه. این وابسته به جستجوی هوشمند است",
|
||||
"exclusion_pattern_description": "الگوهای استثنا به شما امکان میدهد هنگام اسکن کتابخانه خود فایلها و پوشهها را نادیده بگیرید . این مفید است اگر پوشههایی دارید که فایلهایی را شامل میشوند که نمیخواهید وارد کنید، مانند فایلهای RAW.",
|
||||
"external_library_management": "مدیریت کتابخانه خارجی",
|
||||
"face_detection": "تشخیص چهره",
|
||||
"face_detection_description": "تشخیص چهرهها در فایلها با استفاده از یادگیری ماشین. برای ویدیوها، تنها تصویر بندانگشتی در نظر گرفته میشود. گزینه \"همه\" تمام فایلها را (مجددا) پردازش میکند. گزینه \"گمشده\" فایلها را در صف قرار میدهد که هنوز پردازش نشدهاند. چهرههای تشخیص داده شده پس از اتمام تشخیص چهره، برای تشخیص چهره به صورت صف انتظار قرار میگیرند، آنها را به افراد موجود یا جدید گروهبندی میکند.",
|
||||
"facial_recognition_job_description": "گروهبندی چهرههای تشخیص داده شده به افراد. این مرحله پس از تشخیص چهره انجام میشود. گزینه \"همه\" تمام چهرهها را (مجددا) دسته بندی میکند. گزینه \"گمشده\" چهرهها را در صف قرار میدهد که به هیچ فردی اختصاص داده نشدهاند.",
|
||||
@@ -80,8 +76,10 @@
|
||||
"job_not_concurrency_safe": "این کار ایمنی همزمانی را تضمین نمیکند.",
|
||||
"job_settings": "تنظیمات کار",
|
||||
"job_settings_description": "مدیریت همزمانی کار",
|
||||
"job_status": "وضعیت کار",
|
||||
"library_created": "کتابخانه ایجاد شده: {library}",
|
||||
"library_deleted": "کتابخانه حذف شد",
|
||||
"library_import_path_description": "یک پوشه برای وارد کردن مشخص کنید. این پوشه، به همراه زیرپوشهها، برای یافتن تصاویر و ویدیوها اسکن خواهد شد.",
|
||||
"library_scanning": "اسکن دوره ای",
|
||||
"library_scanning_description": "تنظیم اسکن دورهای کتابخانه",
|
||||
"library_scanning_enable_description": "فعال کردن اسکن دورهای کتابخانه",
|
||||
@@ -291,6 +289,7 @@
|
||||
"user_restore_scheduled_removal": "بازیابی کاربر - حذف برنامه ریزی شده در {date, date, long}",
|
||||
"user_settings": "تنظیمات کاربر",
|
||||
"user_settings_description": "مدیریت تنظیمات کاربر",
|
||||
"user_successfully_removed": "کاربر {email} با موفقیت حذف شد.",
|
||||
"version_check_enabled_description": "فعالسازی بررسی نسخه",
|
||||
"version_check_implications": "ویژگی بررسی نسخه به ارتباط دوره ای با github.com متکی است",
|
||||
"version_check_settings": "بررسی نسخه",
|
||||
|
||||
86
i18n/fi.json
86
i18n/fi.json
@@ -7,7 +7,6 @@
|
||||
"action_common_update": "Päivitä",
|
||||
"actions": "Toimintoja",
|
||||
"active": "Aktiivinen",
|
||||
"active_count": "Aktiivisia: {count}",
|
||||
"activity": "Tapahtumat",
|
||||
"activity_changed": "Toiminto {enabled, select, true {otettu käyttöön} other {poistettu käytöstä}}",
|
||||
"add": "Lisää",
|
||||
@@ -18,6 +17,7 @@
|
||||
"add_birthday": "Lisää syntymäpäivä",
|
||||
"add_endpoint": "Lisää päätepiste",
|
||||
"add_exclusion_pattern": "Lisää poissulkemismalli",
|
||||
"add_import_path": "Lisää tuontipolku",
|
||||
"add_location": "Lisää sijainti",
|
||||
"add_more_users": "Lisää käyttäjiä",
|
||||
"add_partner": "Lisää kumppani",
|
||||
@@ -32,7 +32,6 @@
|
||||
"add_to_album_toggle": "Vaihda albumin {album} valintaa",
|
||||
"add_to_albums": "Lisää albumeihin",
|
||||
"add_to_albums_count": "Lisää albumeihin ({count})",
|
||||
"add_to_bottom_bar": "Lisää",
|
||||
"add_to_shared_album": "Lisää jaettuun albumiin",
|
||||
"add_upload_to_stack": "Lisää kuvapinoon",
|
||||
"add_url": "Lisää URL",
|
||||
@@ -68,7 +67,6 @@
|
||||
"confirm_reprocess_all_faces": "Haluatko varmasti käsitellä uudelleen kaikki kasvot? Tämä poistaa myös nimetyt henkilöt.",
|
||||
"confirm_user_password_reset": "Haluatko varmasti nollata käyttäjän {user} salasanan?",
|
||||
"confirm_user_pin_code_reset": "Haluatko varmasti nollata käyttäjän {user} PIN-koodin?",
|
||||
"copy_config_to_clipboard_description": "Kopio järjestelmän asetukset JSON-objektina leikepöydälle",
|
||||
"create_job": "Luo tehtävä",
|
||||
"cron_expression": "Cron-lauseke",
|
||||
"cron_expression_description": "Aseta skannausväli käyttämällä cron-formaattia. Lisätietoja linkistä. <link>Crontab Guru</link>",
|
||||
@@ -76,8 +74,7 @@
|
||||
"disable_login": "Poista kirjautuminen käytöstä",
|
||||
"duplicate_detection_job_description": "Tunnista samankaltaiset kuvat käyttäen koneoppimista. Tukeutuu Smart Search:iin",
|
||||
"exclusion_pattern_description": "Poissulkemismallit mahdollistavat tiettyjen tiedostojen ja kansioiden jättämisen pois kirjastoasi skannatessa. Tästä on hyötyä jos kansiot sisältävät tiedostoja mitä et halua tuoda, kuten RAW-tiedostot.",
|
||||
"export_config_as_json_description": "Lataa nykyiset järjestelmän asetukset JSON-tiedostona",
|
||||
"external_libraries_page_description": "Pääkäyttäjän ulkoisen kirjaston sivu",
|
||||
"external_library_management": "Ulkoisen kirjaston hallinta",
|
||||
"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öä.",
|
||||
@@ -105,28 +102,23 @@
|
||||
"image_thumbnail_description": "Pieni pikkukuva, josta metatiedot on poistettu, käytetään valokuvaryhmien katseluun, kuten pääaikajanalla",
|
||||
"image_thumbnail_quality_description": "Pikkukuvan laatu 1-100. Korkeampi arvo on parempi, mutta tuottaa suurempia tiedostoja ja voi heikentää sovelluksen reagointikykyä.",
|
||||
"image_thumbnail_title": "Pikkukuva-asetukset",
|
||||
"import_config_from_json_description": "Tuo järjestelmän asetukset JSON-tiedostosta",
|
||||
"job_concurrency": "Tehtävän \"{job}\" samanaikaisuus",
|
||||
"job_created": "Tehtävä luotu",
|
||||
"job_not_concurrency_safe": "Tätä tehtävää ei ole turvallista ajaa yhtäaikaisesti.",
|
||||
"job_settings": "Tehtävän asetukset",
|
||||
"job_settings_description": "Hallitse työn samanaikaisuusasetuksia",
|
||||
"job_settings_description": "Hallitse tehtävän samanaikaisuusasetuksia",
|
||||
"job_status": "Tehtävän tila",
|
||||
"jobs_delayed": "{jobCount, plural, other {# viivästynyttä}}",
|
||||
"jobs_failed": "{jobCount, plural, other {# epäonnistunutta}}",
|
||||
"jobs_over_time": "Tehtävät ajan kuluessa",
|
||||
"library_created": "Kirjasto {library} luotu",
|
||||
"library_deleted": "Kirjasto poistettu",
|
||||
"library_details": "Kirjaston tiedot",
|
||||
"library_folder_description": "Ilmoita kansio tuotavaksi. Tämä kansio ja sen alakansiot skannataan kuvia ja videoita varten.",
|
||||
"library_remove_exclusion_pattern_prompt": "Oletko varma että haluat poistaa tämän poissulkemismenetelmän?",
|
||||
"library_remove_folder_prompt": "Oletko varma että haluat poistaa tämän tuodun kansion?",
|
||||
"library_import_path_description": "Määritä kansio joka tuodaan. Kuvat ja videot skannataan tästä kansiosta, sekä alikansioista.",
|
||||
"library_scanning": "Ajoittainen skannaus",
|
||||
"library_scanning_description": "Määritä ajoittaiset kirjastojen skannaukset",
|
||||
"library_scanning_enable_description": "Ota käyttöön ajoittaiset kirjastojen skannaukset",
|
||||
"library_settings": "Ulkoinen kirjasto",
|
||||
"library_settings_description": "Hallitse ulkoisen kirjaston asetuksia",
|
||||
"library_tasks_description": "Skannaa ulkoisia kirjastoja uusien ja/tai muutoksien varalta",
|
||||
"library_updated": "Kirjasto päivitetty",
|
||||
"library_watching_enable_description": "Tarkkaile tiedostojen muuttumisia ulkoisissa kirjastoissa",
|
||||
"library_watching_settings": "Kirjaston tarkkailu (KOKEELLINEN)",
|
||||
"library_watching_settings_description": "Tarkkaile muuttuvia tiedostoja automaattisesti",
|
||||
@@ -168,8 +160,8 @@
|
||||
"machine_learning_ocr_enabled_description": "Jos asetus on pois päältä, kuvia ei prosessoida tekstin tunnistamiseksi.",
|
||||
"machine_learning_ocr_max_resolution": "Maksimiresoluutio",
|
||||
"machine_learning_ocr_max_resolution_description": "Tätä suuremmat esikatselukuvat tullaan pienentämään samassa kuvasuhteessa. Suuremmat arvot ovat tarkempia, mutta kestävät pidempään prosessoida ja käyttävät enemmän muistia.",
|
||||
"machine_learning_ocr_min_detection_score": "Tunnistuksen vähimmäispistemäärä",
|
||||
"machine_learning_ocr_min_detection_score_description": "Tekstin tunnistuksen vähimmäisluottamusarvo (0–1). Pienemmät arvot tunnistavat enemmän tekstiä, mutta voivat johtaa virheellisiin osumiin.",
|
||||
"machine_learning_ocr_min_detection_score": "Pienin paikannuksen pistemäärä",
|
||||
"machine_learning_ocr_min_detection_score_description": "Pienin arvo tekstin paikannukselle varmuudelle välillä 0-1. Pienemmät arvot paikantavat enemmän tekstiä, mutta saattavat johtaa useampaan väärään positiiviseen.",
|
||||
"machine_learning_ocr_min_recognition_score": "Pienin tunnistuksen pistemäärä",
|
||||
"machine_learning_ocr_min_score_recognition_description": "Pienin arvo tekstin tunnistuksen varmuudelle välillä 0-1. Pienemmät arvot tunnistavat enemmän tekstiä, mutta saattavat johtaa useampaan väärään positiiviseen.",
|
||||
"machine_learning_ocr_model": "OCR-malli",
|
||||
@@ -181,12 +173,7 @@
|
||||
"machine_learning_smart_search_enabled": "Ota käyttöön älykäs haku",
|
||||
"machine_learning_smart_search_enabled_description": "Jos ei käytössä, kuvia ei koodata älykkäälle etsinnälle.",
|
||||
"machine_learning_url_description": "Koneoppimispalvelimen URL-osoite. Jos lisätään useampi kuin yksi URL-osoite, kutakin osoitetta kohden yritetään kerran, kunnes yksi niistä vastaa. Yritykset tehdään järjestyksessä ensimmäisestä viimeiseen. Palvelimet, jotka eivät vastaa, ohitetaan tilapäisesti, kunnes ne ovat taas tavoitettavissa.",
|
||||
"maintenance_settings": "Ylläpito",
|
||||
"maintenance_settings_description": "Laita Immich ylläpitotilaan.",
|
||||
"maintenance_start": "Käynnistä ylläpitotila",
|
||||
"maintenance_start_error": "Ylläpitotilan käynnistys epäonnistui.",
|
||||
"manage_concurrency": "Hallitse yhtäaikaisia toimintoja",
|
||||
"manage_concurrency_description": "Mene töiden sivulle muuttamaan töiden yhtäaikaisuutta",
|
||||
"manage_log_settings": "Hallitse lokien asetuksia",
|
||||
"map_dark_style": "Tumma teema",
|
||||
"map_enable_description": "Ota käyttöön karttatoiminnot",
|
||||
@@ -276,14 +263,10 @@
|
||||
"password_settings_description": "Hallitse salasanakirjautumisen asetuksia",
|
||||
"paths_validated_successfully": "Kaikki polut validoitu",
|
||||
"person_cleanup_job": "Henkilöpuhdistus",
|
||||
"queue_details": "Jonon tiedot",
|
||||
"queues": "Töiden jonot",
|
||||
"queues_page_description": "Ylläpitäjän töiden jonosivu",
|
||||
"quota_size_gib": "Kiintiön koko (Gt)",
|
||||
"refreshing_all_libraries": "Virkistetään kaikki kirjastot",
|
||||
"registration": "Pääkäyttäjän rekisteröinti",
|
||||
"registration_description": "Pääkäyttäjänä olet vastuussa järjestelmän hallinnallisista tehtävistä ja uusien käyttäjien luomisesta.",
|
||||
"remove_failed_jobs": "Poista epäonnistuneet tehtävät",
|
||||
"require_password_change_on_login": "Vaadi käyttäjää vaihtamaan salasana ensimmäisellä kirjautumiskerralla",
|
||||
"reset_settings_to_default": "Nollaa asetukset oletuksille",
|
||||
"reset_settings_to_recent_saved": "Palauta aiemmin tallennetut asetukset",
|
||||
@@ -296,10 +279,8 @@
|
||||
"server_public_users_description": "Kaikki käyttäjät (nimi ja sähköpostiosoite) luetellaan, kun käyttäjä lisätään jaettuihin albumeihin. Kun toiminto on poistettu käytöstä, käyttäjäluettelo on vain pääkäyttäjien käytettävissä.",
|
||||
"server_settings": "Palvelimen asetukset",
|
||||
"server_settings_description": "Ylläpidä palvelimen asetuksia",
|
||||
"server_stats_page_description": "Ylläpitäjän palvelimen tilastosivu",
|
||||
"server_welcome_message": "Tervetuloviesti",
|
||||
"server_welcome_message_description": "Viesti, joka näytetään kirjautumissivulla.",
|
||||
"settings_page_description": "Ylläpitäjän asetuksien sivu",
|
||||
"sidecar_job": "Kylkiäismetadata",
|
||||
"sidecar_job_description": "Havaitse tai synkronoi tiedostojen kylkiäismetadatat",
|
||||
"slideshow_duration_description": "Montako sekuntia kuvaa näytetään",
|
||||
@@ -418,8 +399,7 @@
|
||||
"user_restore_scheduled_removal": "Palauta käyttäjä - Aikataulutettu poisto tapahtuu {date, date, long}",
|
||||
"user_settings": "Käyttäjäasetukset",
|
||||
"user_settings_description": "Hallitse käyttäjäasetuksia",
|
||||
"user_successfully_removed": "Käyttäjä {email} on poistettu onnistuneesti.",
|
||||
"users_page_description": "Ylläpitäjän käyttäjien lista",
|
||||
"user_successfully_removed": "Käyttäjä {email} on poistettu.",
|
||||
"version_check_enabled_description": "Ota käyttöön versiotarkastus",
|
||||
"version_check_implications": "Versiotarkistus vaatii säännöllisen yhteyden github.comiin",
|
||||
"version_check_settings": "Versiotarkistus",
|
||||
@@ -450,7 +430,6 @@
|
||||
"age_months": "Ikä {months, plural, one {# kuukausi} other {# kuukautta}}",
|
||||
"age_year_months": "Ikä 1 vuosi, {months, plural, one {# kuukausi} other {# kuukautta}}",
|
||||
"age_years": "{years, plural, other {Ikä #v}}",
|
||||
"album": "Albumi",
|
||||
"album_added": "Albumi lisätty",
|
||||
"album_added_notification_setting_description": "Saa sähköpostia kun sinut lisätään jaettuun albumiin",
|
||||
"album_cover_updated": "Albumin kansikuva päivitetty",
|
||||
@@ -496,7 +475,6 @@
|
||||
"allow_edits": "Salli muutokset",
|
||||
"allow_public_user_to_download": "Salli julkisten käyttäjien ladata tiedostoja",
|
||||
"allow_public_user_to_upload": "Salli julkisten käyttäjien lähettää tiedostoja",
|
||||
"allowed": "Sallittu",
|
||||
"alt_text_qr_code": "QR-koodi",
|
||||
"anti_clockwise": "Vastapäivään",
|
||||
"api_key": "API-avain",
|
||||
@@ -705,7 +683,7 @@
|
||||
"change_password_form_confirm_password": "Vahvista salasana",
|
||||
"change_password_form_description": "Hei {name},\n\nTämä on joko ensimmäinen kerta, kun kirjaudut järjestelmään, tai sinulta on pyydetty salasanan vaihtoa. Ole hyvä ja syötä uusi salasana alle.",
|
||||
"change_password_form_log_out": "Kirjaudu ulos kaikilta muilta laitteilta",
|
||||
"change_password_form_log_out_description": "On suositeltavaa kirjautua ulos kaikilta laitteilta",
|
||||
"change_password_form_log_out_description": "On suositeltavaa kirjautua ulos kaikilta muilta laitteilta",
|
||||
"change_password_form_new_password": "Uusi salasana",
|
||||
"change_password_form_password_mismatch": "Salasanat eivät täsmää",
|
||||
"change_password_form_reenter_new_password": "Uusi salasana uudelleen",
|
||||
@@ -740,7 +718,6 @@
|
||||
"collapse_all": "Sulje kaikki",
|
||||
"color": "Väri",
|
||||
"color_theme": "Väriteema",
|
||||
"command": "Komento",
|
||||
"comment_deleted": "Kommentti poistettu",
|
||||
"comment_options": "Kommentin valinnat",
|
||||
"comments_and_likes": "Kommentit ja tykkäykset",
|
||||
@@ -917,6 +894,8 @@
|
||||
"edit_description_prompt": "Valitse uusi kuvaus:",
|
||||
"edit_exclusion_pattern": "Muokkaa poissulkemismallia",
|
||||
"edit_faces": "Muokkaa kasvoja",
|
||||
"edit_import_path": "Muokkaa tuontipolkua",
|
||||
"edit_import_paths": "Muokkaa tuontipolkuja",
|
||||
"edit_key": "Muokkaa avainta",
|
||||
"edit_link": "Muokkaa linkkiä",
|
||||
"edit_location": "Muokkaa sijaintia",
|
||||
@@ -988,8 +967,8 @@
|
||||
"failed_to_stack_assets": "Medioiden pinoaminen epäonnistui",
|
||||
"failed_to_unstack_assets": "Medioiden pinoamisen purku epäonnistui",
|
||||
"failed_to_update_notification_status": "Ilmoituksen tilan päivittäminen epäonnistui",
|
||||
"import_path_already_exists": "Tämä tuontipolku on jo olemassa.",
|
||||
"incorrect_email_or_password": "Väärä sähköpostiosoite tai salasana",
|
||||
"library_folder_already_exists": "Tämä tuonnin polku on jo olemassa.",
|
||||
"paths_validation_failed": "{paths, plural, one {# polun} other {# polun}} validointi epäonnistui",
|
||||
"profile_picture_transparent_pixels": "Profiilikuvassa ei voi olla läpinäkyviä pikseleitä. Zoomaa lähemmäs ja/tai siirrä kuvaa.",
|
||||
"quota_higher_than_disk_size": "Asettamasi kiintiö on suurempi kuin levyn koko",
|
||||
@@ -998,6 +977,7 @@
|
||||
"unable_to_add_assets_to_shared_link": "Medioiden lisääminen jaettuun linkkiin epäonnistui",
|
||||
"unable_to_add_comment": "Kommentin lisääminen epäonnistui",
|
||||
"unable_to_add_exclusion_pattern": "Ei voida lisätä poissulkemismallia",
|
||||
"unable_to_add_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}}",
|
||||
"unable_to_add_remove_favorites": "Ei voida {favorite, select, true {lisätä kohdetta suosikkeihin} other {poistaa kohdetta suosikeista}}",
|
||||
@@ -1020,10 +1000,12 @@
|
||||
"unable_to_delete_asset": "Kohteen poistaminen epäonnistui",
|
||||
"unable_to_delete_assets": "Virhe kohteen poistamisessa",
|
||||
"unable_to_delete_exclusion_pattern": "Ei voida poistaa poissulkemismallia",
|
||||
"unable_to_delete_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 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",
|
||||
"unable_to_exit_fullscreen": "Koko ruudun tilasta poistuminen epäonnistui",
|
||||
@@ -1074,7 +1056,6 @@
|
||||
"unable_to_update_user": "Käyttäjän muokkaus epäonnistui",
|
||||
"unable_to_upload_file": "Tiedostoa ei voitu ladata"
|
||||
},
|
||||
"exclusion_pattern": "Poissulkemismenetelmä",
|
||||
"exif": "Exif",
|
||||
"exif_bottom_sheet_description": "Lisää kuvaus…",
|
||||
"exif_bottom_sheet_description_error": "Kuvauksen muuttaminen epäonnistui",
|
||||
@@ -1105,7 +1086,6 @@
|
||||
"external_network_sheet_info": "Kun laite ei ole yhteydessä valittuun Wi-Fi-verkkoon, sovellus yrittää muodostaa yhteyden palvelimeen alla olevista URL-osoitteista ylhäältä alas, kunnes yhteys muodostuu",
|
||||
"face_unassigned": "Ei määritelty",
|
||||
"failed": "Epäonnistui",
|
||||
"failed_count": "Epäonnistuneita: {count}",
|
||||
"failed_to_authenticate": "Todennus epäonnistui",
|
||||
"failed_to_load_assets": "Kohteiden lataus epäonnistui",
|
||||
"failed_to_load_folder": "Kansion lataaminen epäonnistui",
|
||||
@@ -1135,7 +1115,6 @@
|
||||
"folders_feature_description": "Käytetään kansionäkymää valokuvien ja videoiden selaamiseen järjestelmässä",
|
||||
"forgot_pin_code_question": "Unohditko PIN-koodisi?",
|
||||
"forward": "Eteenpäin",
|
||||
"full_path": "Koko polku: {path}",
|
||||
"gcast_enabled": "Google Cast",
|
||||
"gcast_enabled_description": "Ominaisuus lataa ulkoisia resursseja Googlelta toimiakseen.",
|
||||
"general": "Yleinen",
|
||||
@@ -1172,7 +1151,6 @@
|
||||
"hide_named_person": "Piilota henkilön {name}",
|
||||
"hide_password": "Piilota salasana",
|
||||
"hide_person": "Piilota henkilö",
|
||||
"hide_text_recognition": "Piilota tekstin tunnistus",
|
||||
"hide_unnamed_people": "Piilota nimeämättömät henkilöt",
|
||||
"home_page_add_to_album_conflicts": "Lisätty {added} kohdetta albumiin {album}. {failed} kohdetta on jo albumissa.",
|
||||
"home_page_add_to_album_err_local": "Paikallisten kohteiden lisääminen albumeihin ei ole mahdollista, ohitetaan",
|
||||
@@ -1218,8 +1196,6 @@
|
||||
"import_path": "Tuontipolku",
|
||||
"in_albums": "{count, plural, one {# albumissa} other {# albumissa}}",
|
||||
"in_archive": "Arkistossa",
|
||||
"in_year": "Vuonna {year}",
|
||||
"in_year_selector": "Vuosi",
|
||||
"include_archived": "Sisällytä arkistoidut",
|
||||
"include_shared_albums": "Sisällytä jaetut albumit",
|
||||
"include_shared_partner_assets": "Sisällytä jaetut kumppanikohteet",
|
||||
@@ -1256,7 +1232,6 @@
|
||||
"language_setting_description": "Valitse suosimasi kieli",
|
||||
"large_files": "Suuret tiedostot",
|
||||
"last": "Viimeinen",
|
||||
"last_months": "{count, plural, one {Viime kuukausi} other {Viimeiset # kuukautta}}",
|
||||
"last_seen": "Viimeksi nähty",
|
||||
"latest_version": "Viimeisin versio",
|
||||
"latitude": "Leveysaste",
|
||||
@@ -1266,8 +1241,6 @@
|
||||
"let_others_respond": "Anna muiden vastata",
|
||||
"level": "Taso",
|
||||
"library": "Kirjasto",
|
||||
"library_add_folder": "Lisää kansio",
|
||||
"library_edit_folder": "Muokkaa kansiota",
|
||||
"library_options": "Kirjastovaihtoehdot",
|
||||
"library_page_device_albums": "Laitteen albumit",
|
||||
"library_page_new_album": "Uusi albumi",
|
||||
@@ -1339,17 +1312,8 @@
|
||||
"loop_videos_description": "Ota käyttöön jatkuva videotoisto tarkemmassa näkymässä.",
|
||||
"main_branch_warning": "Käytät kehitysversiota; suosittelemme vahvasti käyttämään julkaisuversiota!",
|
||||
"main_menu": "Päävalikko",
|
||||
"maintenance_description": "Immich on asetettu <link>ylläpitotilaan</link>.",
|
||||
"maintenance_end": "Poistu ylläpitotilasta",
|
||||
"maintenance_end_error": "Poistuminen ylläpitotilasta epäonnistui.",
|
||||
"maintenance_logged_in_as": "Kirjautuneena käyttäjänä {user}",
|
||||
"maintenance_title": "Tilapäisesti ei saatavilla",
|
||||
"make": "Valmistaja",
|
||||
"manage_geolocation": "Muokkaa sijaintia",
|
||||
"manage_media_access_rationale": "Tämä lupa tarvitaan sisällön siirtämiseen roskakoriin ja sisällön palauttamiseen sieltä.",
|
||||
"manage_media_access_settings": "Avaa asetukset",
|
||||
"manage_media_access_subtitle": "Anna Immich-sovellukselle lupa hallinoida ja siirtää mediatiedostoja.",
|
||||
"manage_media_access_title": "Lupa median hallinnointiin",
|
||||
"manage_shared_links": "Hallitse jaettuja linkkejä",
|
||||
"manage_sharing_with_partners": "Hallitse jakamista kumppaneille",
|
||||
"manage_the_app_settings": "Hallitse sovelluksen asetuksia",
|
||||
@@ -1413,7 +1377,6 @@
|
||||
"more": "Enemmän",
|
||||
"move": "Siirrä",
|
||||
"move_off_locked_folder": "Siirrä pois lukitusta kansiosta",
|
||||
"move_to": "Siirrä kohteeseen",
|
||||
"move_to_lock_folder_action_prompt": "{count} lisätty lukittuun kansioon",
|
||||
"move_to_locked_folder": "Siirrä lukittuun kansioon",
|
||||
"move_to_locked_folder_confirmation": "Nämä kuvat ja videot poistetaan kaikista albumeista, ja ne ovat nähtävissä vain lukitussa kansiossa",
|
||||
@@ -1443,7 +1406,6 @@
|
||||
"new_pin_code": "Uusi PIN-koodi",
|
||||
"new_pin_code_subtitle": "Tämä on ensimmäinen kerta, kun käytät lukittua kansiota. Luo PIN-koodi päästäksesi tähän sisältöön turvallisesti",
|
||||
"new_timeline": "Uusi aikajana",
|
||||
"new_update": "Uusi päivitys",
|
||||
"new_user_created": "Uusi käyttäjä lisätty",
|
||||
"new_version_available": "UUSI VERSIO SAATAVILLA",
|
||||
"newest_first": "Uusin ensin",
|
||||
@@ -1459,14 +1421,12 @@
|
||||
"no_cast_devices_found": "Cast-laitteita ei löytynyt",
|
||||
"no_checksum_local": "Ei tarkistussummaa - paikallista sisältöä ei voida hakea",
|
||||
"no_checksum_remote": "Ei tarkistussummaa - etänä olevaa sisältöä ei voida hakea",
|
||||
"no_devices": "Ei valtuutettuja laitteita",
|
||||
"no_duplicates_found": "Kaksoiskappaleita ei löytynyt.",
|
||||
"no_exif_info_available": "EXIF-tietoa ei saatavilla",
|
||||
"no_explore_results_message": "Lataa lisää kuvia tutkiaksesi kokoelmaasi.",
|
||||
"no_favorites_message": "Lisää suosikkeja löytääksesi nopeasti parhaat kuvasi ja videosi",
|
||||
"no_libraries_message": "Luo ulkoinen kirjasto nähdäksesi valokuvasi ja videot",
|
||||
"no_local_assets_found": "Paikallista sisältöä ei löytynyt tällä tarkistussummalla",
|
||||
"no_location_set": "Ei sijaintia asetettuna",
|
||||
"no_locked_photos_message": "Kuvat ja videot lukitussa kansiossa ovat piilotettuja, eivätkä ne näy selatessasi tai etsiessäsi kirjastoasi.",
|
||||
"no_name": "Ei nimeä",
|
||||
"no_notifications": "Ei ilmoituksia",
|
||||
@@ -1477,7 +1437,6 @@
|
||||
"no_results_description": "Kokeile synonyymiä tai yleisempää avainsanaa",
|
||||
"no_shared_albums_message": "Luo albumi, jotta voit jakaa kuvia ja videoita toisille",
|
||||
"no_uploads_in_progress": "Ei käynnissä olevia latauksia",
|
||||
"not_allowed": "Ei sallittu",
|
||||
"not_available": "N/A",
|
||||
"not_in_any_album": "Ei yhdessäkään albumissa",
|
||||
"not_selected": "Ei valittu",
|
||||
@@ -1526,7 +1485,6 @@
|
||||
"other_variables": "Muut muuttujat",
|
||||
"owned": "Omistettu",
|
||||
"owner": "Omistaja",
|
||||
"page": "Sivu",
|
||||
"partner": "Kumppani",
|
||||
"partner_can_access": "{partner} voi päästä",
|
||||
"partner_can_access_assets": "Kaikki valokuvasi ja videosi, lukuun ottamatta arkistoituja ja poistettuja",
|
||||
@@ -1589,13 +1547,11 @@
|
||||
"photos_count": "{count, plural, one {{count, number} Kuva} other {{count, number} kuvaa}}",
|
||||
"photos_from_previous_years": "Kuvia edellisiltä vuosilta",
|
||||
"pick_a_location": "Valitse sijainti",
|
||||
"pick_custom_range": "Mukautettu väli",
|
||||
"pick_date_range": "Valitse päivämäärien väli",
|
||||
"pin_code_changed_successfully": "PIN-koodin vaihto onnistui",
|
||||
"pin_code_reset_successfully": "PIN-koodin nollaus onnistui",
|
||||
"pin_code_setup_successfully": "PIN-koodin asettaminen onnistui",
|
||||
"pin_verification": "PIN-koodin vahvistus",
|
||||
"place": "Paikka",
|
||||
"place": "Sijainti",
|
||||
"places": "Paikat",
|
||||
"places_count": "{count, plural, one {{count, number} Paikka} other {{count, number} Paikkaa}}",
|
||||
"play": "Toista",
|
||||
@@ -1760,7 +1716,6 @@
|
||||
"running": "Käynnissä",
|
||||
"save": "Tallenna",
|
||||
"save_to_gallery": "Tallenna galleriaan",
|
||||
"saved": "Tallennettu",
|
||||
"saved_api_key": "API-avain tallennettu",
|
||||
"saved_profile": "Profiili tallennettu",
|
||||
"saved_settings": "Asetukset tallennettu",
|
||||
@@ -1858,8 +1813,6 @@
|
||||
"server_offline": "Palvelin Offline-tilassa",
|
||||
"server_online": "Palvelin Online-tilassa",
|
||||
"server_privacy": "Palvelimen tietosuoja",
|
||||
"server_restarting_description": "Tämä sivu latautuu uudelleen hetken kuluttua.",
|
||||
"server_restarting_title": "Palvelin käynnistyy uudelleen",
|
||||
"server_stats": "Palvelimen tilastot",
|
||||
"server_update_available": "Palvelimeen on saatavilla päivitys",
|
||||
"server_version": "Palvelimen versio",
|
||||
@@ -1983,7 +1936,6 @@
|
||||
"show_slideshow_transition": "Näytä diaesitys siirtymä",
|
||||
"show_supporter_badge": "Kannattajan merkki",
|
||||
"show_supporter_badge_description": "Näytä kannattajan merkki",
|
||||
"show_text_recognition": "Näytä tekstin tunnistus",
|
||||
"show_text_search_menu": "Näytä tekstihakuvalikko",
|
||||
"shuffle": "Sekoita",
|
||||
"sidebar": "Sivupalkki",
|
||||
@@ -2054,7 +2006,6 @@
|
||||
"tags": "Tunnisteet",
|
||||
"tap_to_run_job": "Napauta suorittaaksesi tehtävän",
|
||||
"template": "Nimeämismalli",
|
||||
"text_recognition": "Tekstin tunnistus",
|
||||
"theme": "Teema",
|
||||
"theme_selection": "Teeman valinta",
|
||||
"theme_selection_description": "Aseta vaalea tai tumma tila automaattisesti perustuen selaimesi asetuksiin",
|
||||
@@ -2075,7 +2026,6 @@
|
||||
"third_party_resources": "Kolmannen osapuolen resurssit",
|
||||
"time": "Aika",
|
||||
"time_based_memories": "Aikaan perustuvat muistot",
|
||||
"time_based_memories_duration": "Kuvien näyttöaika sekunteina.",
|
||||
"timeline": "Aikajana",
|
||||
"timezone": "Aikavyöhyke",
|
||||
"to_archive": "Arkistoi",
|
||||
@@ -2087,7 +2037,6 @@
|
||||
"to_select": "valitsemiseksi",
|
||||
"to_trash": "Roskakoriin",
|
||||
"toggle_settings": "Määritä asetukset",
|
||||
"toggle_theme_description": "Vaihda teemaa",
|
||||
"total": "Yhteensä",
|
||||
"total_usage": "Käyttö yhteensä",
|
||||
"trash": "Roskakori",
|
||||
@@ -2196,7 +2145,6 @@
|
||||
"view_album": "Näytä albumi",
|
||||
"view_all": "Näytä kaikki",
|
||||
"view_all_users": "Näytä kaikki käyttäjät",
|
||||
"view_asset_owners": "Näytä omistajat",
|
||||
"view_details": "Näytä tiedot",
|
||||
"view_in_timeline": "Näytä aikajanalla",
|
||||
"view_link": "Näytä linkki",
|
||||
@@ -2213,13 +2161,11 @@
|
||||
"viewer_unstack": "Pura pino",
|
||||
"visibility_changed": "{count, plural, one {# henkilön} other {# henkilöiden}} näkyvyys vaihdettu",
|
||||
"waiting": "Odottaa",
|
||||
"waiting_count": "Odottaa: {count}",
|
||||
"warning": "Varoitus",
|
||||
"week": "Viikko",
|
||||
"welcome": "Tervetuloa",
|
||||
"welcome_to_immich": "Tervetuloa Immichiin",
|
||||
"wifi_name": "Wi-Fi-verkon nimi",
|
||||
"workflow": "Työnkulku",
|
||||
"wrong_pin_code": "Väärä PIN-koodi",
|
||||
"year": "Vuosi",
|
||||
"years_ago": "{years, plural, one {# vuosi} other {# vuotta}} sitten",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user