Compare commits

..

1 Commits

Author SHA1 Message Date
Thomas Way c087b7c063 chore(mobile): replace maplibre_gl with maplibre
maplibre is a ground-up rewrite of maplibre_gl with a more modern and
ergonomic API. It should fix a few bugs we've seen with maps, and
perform better.
2026-02-21 01:17:06 +00:00
1240 changed files with 36784 additions and 67137 deletions
+1 -1
View File
@@ -1 +1 @@
24.14.1 24.13.1
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"scripts": { "scripts": {
"format": "prettier --cache --check .", "format": "prettier --check .",
"format:fix": "prettier --cache --write --list-different ." "format:fix": "prettier --write ."
}, },
"devDependencies": { "devDependencies": {
"prettier": "^3.7.4" "prettier": "^3.7.4"
-148
View File
@@ -1,148 +0,0 @@
name: Auto-close PRs
on:
pull_request_target: # zizmor: ignore[dangerous-triggers]
types: [opened, edited, labeled]
permissions: {}
jobs:
parse_template:
runs-on: ubuntu-latest
if: ${{ github.event.action != 'labeled' && github.event.pull_request.head.repo.fork == true }}
permissions:
contents: read
outputs:
uses_template: ${{ steps.check.outputs.uses_template }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
sparse-checkout: .github/pull_request_template.md
sparse-checkout-cone-mode: false
persist-credentials: false
- name: Check required sections
id: check
env:
BODY: ${{ github.event.pull_request.body }}
run: |
OK=true
while IFS= read -r header; do
printf '%s\n' "$BODY" | grep -qF "$header" || OK=false
done < <(sed '/<!--/,/-->/d' .github/pull_request_template.md | grep "^## ")
echo "uses_template=$OK" | tee --append "$GITHUB_OUTPUT"
close_template:
runs-on: ubuntu-latest
needs: parse_template
if: >-
${{
needs.parse_template.outputs.uses_template == 'false'
&& github.event.pull_request.state != 'closed'
&& !contains(github.event.pull_request.labels.*.name, 'auto-closed:template')
}}
permissions:
pull-requests: write
steps:
- name: Comment and close
env:
GH_TOKEN: ${{ github.token }}
NODE_ID: ${{ github.event.pull_request.node_id }}
run: |
gh api graphql \
-f prId="$NODE_ID" \
-f body="This PR has been automatically closed as the description doesn't follow our template. After you edit it to match the template, the PR will automatically be reopened." \
-f query='
mutation CommentAndClosePR($prId: ID!, $body: String!) {
addComment(input: {
subjectId: $prId,
body: $body
}) {
__typename
}
closePullRequest(input: {
pullRequestId: $prId
}) {
__typename
}
}'
- name: Add label
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: gh pr edit "$PR_NUMBER" --repo "${{ github.repository }}" --add-label "auto-closed:template"
close_llm:
runs-on: ubuntu-latest
if: ${{ github.event.action == 'labeled' && github.event.label.name == 'auto-closed:llm' }}
permissions:
pull-requests: write
steps:
- name: Comment and close
env:
GH_TOKEN: ${{ github.token }}
NODE_ID: ${{ github.event.pull_request.node_id }}
run: |
gh api graphql \
-f prId="$NODE_ID" \
-f body="Thank you for your interest in contributing to Immich! Unfortunately this PR looks like it was generated using an LLM. As noted in our [CONTRIBUTING.md](https://github.com/immich-app/immich/blob/main/CONTRIBUTING.md#use-of-generative-ai), we request that you don't use LLMs to generate PRs as those are not a good use of maintainer time." \
-f query='
mutation CommentAndClosePR($prId: ID!, $body: String!) {
addComment(input: {
subjectId: $prId,
body: $body
}) {
__typename
}
closePullRequest(input: {
pullRequestId: $prId
}) {
__typename
}
}'
reopen:
runs-on: ubuntu-latest
needs: parse_template
if: >-
${{
needs.parse_template.outputs.uses_template == 'true'
&& github.event.pull_request.state == 'closed'
&& contains(github.event.pull_request.labels.*.name, 'auto-closed:template')
}}
permissions:
pull-requests: write
steps:
- name: Remove template label
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: gh pr edit "$PR_NUMBER" --repo "${{ github.repository }}" --remove-label "auto-closed:template" || true
- name: Check for remaining auto-closed labels
id: check_labels
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
REMAINING=$(gh pr view "$PR_NUMBER" --repo "${{ github.repository }}" --json labels \
--jq '[.labels[].name | select(startswith("auto-closed:"))] | length')
echo "remaining=$REMAINING" | tee --append "$GITHUB_OUTPUT"
- name: Reopen PR
if: ${{ steps.check_labels.outputs.remaining == '0' }}
env:
GH_TOKEN: ${{ github.token }}
NODE_ID: ${{ github.event.pull_request.node_id }}
run: |
gh api graphql \
-f prId="$NODE_ID" \
-f query='
mutation ReopenPR($prId: ID!) {
reopenPullRequest(input: {
pullRequestId: $prId
}) {
__typename
}
}'
+11 -11
View File
@@ -51,14 +51,14 @@ jobs:
should_run: ${{ steps.check.outputs.should_run }} should_run: ${{ steps.check.outputs.should_run }}
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- name: Check what should run - name: Check what should run
id: check id: check
uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 uses: immich-app/devtools/actions/pre-job@eed0f8b8165ffcb951f2ba854b2dd031935e1d73 # pre-job-action-v2.0.2
with: with:
github-token: ${{ steps.token.outputs.token }} github-token: ${{ steps.token.outputs.token }}
filters: | filters: |
@@ -79,7 +79,7 @@ jobs:
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -103,7 +103,7 @@ jobs:
- name: Restore Gradle Cache - name: Restore Gradle Cache
id: cache-gradle-restore id: cache-gradle-restore
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@@ -114,7 +114,7 @@ jobs:
key: build-mobile-gradle-${{ runner.os }}-main key: build-mobile-gradle-${{ runner.os }}-main
- name: Setup Flutter SDK - name: Setup Flutter SDK
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2.21.0
with: with:
channel: 'stable' channel: 'stable'
flutter-version-file: ./mobile/pubspec.yaml flutter-version-file: ./mobile/pubspec.yaml
@@ -153,14 +153,14 @@ jobs:
fi fi
- name: Publish Android Artifact - name: Publish Android Artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with: with:
name: release-apk-signed name: release-apk-signed
path: mobile/build/app/outputs/flutter-apk/*.apk path: mobile/build/app/outputs/flutter-apk/*.apk
- name: Save Gradle Cache - name: Save Gradle Cache
id: cache-gradle-save id: cache-gradle-save
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
if: github.ref == 'refs/heads/main' if: github.ref == 'refs/heads/main'
with: with:
path: | path: |
@@ -185,13 +185,13 @@ jobs:
run: sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer run: sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
ref: ${{ inputs.ref || github.sha }} ref: ${{ inputs.ref || github.sha }}
persist-credentials: false persist-credentials: false
- name: Setup Flutter SDK - name: Setup Flutter SDK
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2
with: with:
channel: 'stable' channel: 'stable'
flutter-version-file: ./mobile/pubspec.yaml flutter-version-file: ./mobile/pubspec.yaml
@@ -210,7 +210,7 @@ jobs:
working-directory: ./mobile working-directory: ./mobile
- name: Setup Ruby - name: Setup Ruby
uses: ruby/setup-ruby@c515ec17f69368147deb311832da000dd229d338 # v1.297.0 uses: ruby/setup-ruby@v1
with: with:
ruby-version: '3.3' ruby-version: '3.3'
bundler-cache: true bundler-cache: true
@@ -291,7 +291,7 @@ jobs:
security delete-keychain build.keychain || true security delete-keychain build.keychain || true
- name: Upload IPA artifact - name: Upload IPA artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with: with:
name: ios-release-ipa name: ios-release-ipa
path: mobile/ios/Runner.ipa path: mobile/ios/Runner.ipa
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
actions: write actions: write
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
+3 -2
View File
@@ -19,12 +19,13 @@ jobs:
contents: read contents: read
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with: with:
persist-credentials: false persist-credentials: false
- name: Check for breaking API changes - name: Check for breaking API changes
uses: oasdiff/oasdiff-action/breaking@1f38ea5ea0b4a2e4e49901c3bcdf4386a05e9ea1 # v0.0.37 # sha is pinning to a commit instead of a tag since the action does not tag versions
uses: oasdiff/oasdiff-action/breaking@ccb863950ce437a50f8f1a40d2a1112117e06ce4
with: with:
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
revision: open-api/immich-openapi-specs.json revision: open-api/immich-openapi-specs.json
+9 -9
View File
@@ -31,7 +31,7 @@ jobs:
working-directory: ./cli working-directory: ./cli
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -42,10 +42,10 @@ jobs:
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './cli/.nvmrc' node-version-file: './cli/.nvmrc'
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
@@ -71,7 +71,7 @@ jobs:
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -83,13 +83,13 @@ jobs:
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
if: ${{ !github.event.pull_request.head.repo.fork }} if: ${{ !github.event.pull_request.head.repo.fork }}
with: with:
registry: ghcr.io registry: ghcr.io
@@ -104,7 +104,7 @@ jobs:
- name: Generate docker image tags - name: Generate docker image tags
id: metadata id: metadata
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
with: with:
flavor: | flavor: |
latest=false latest=false
@@ -115,7 +115,7 @@ jobs:
type=raw,value=latest,enable=${{ github.event_name == 'release' }} type=raw,value=latest,enable=${{ github.event_name == 'release' }}
- name: Build and push image - name: Build and push image
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
with: with:
file: cli/Dockerfile file: cli/Dockerfile
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
needs: [get_body, should_run] needs: [get_body, should_run]
if: ${{ needs.should_run.outputs.should_run == 'true' }} if: ${{ needs.should_run.outputs.should_run == 'true' }}
container: container:
image: ghcr.io/immich-app/mdq:main@sha256:df7188ba88abb0800d73cc97d3633280f0c0c3d4c441d678225067bf154150fb image: ghcr.io/immich-app/mdq:main@sha256:4f9860d04c88f7f87861f8ee84bfeedaec15ed7ca5ca87bc7db44b036f81645f
outputs: outputs:
checked: ${{ steps.get_checkbox.outputs.checked }} checked: ${{ steps.get_checkbox.outputs.checked }}
steps: steps:
+38
View File
@@ -0,0 +1,38 @@
name: Close LLM-generated PRs
on:
pull_request_target:
types: [labeled]
permissions: {}
jobs:
comment_and_close:
runs-on: ubuntu-latest
if: ${{ github.event.label.name == 'llm-generated' }}
permissions:
pull-requests: write
steps:
- name: Comment and close
env:
GH_TOKEN: ${{ github.token }}
NODE_ID: ${{ github.event.pull_request.node_id }}
run: |
gh api graphql \
-f prId="$NODE_ID" \
-f body="Thank you for your interest in contributing to Immich! Unfortunately this PR looks like it was generated using an LLM. As noted in our [CONTRIBUTING.md](https://github.com/immich-app/immich/blob/main/CONTRIBUTING.md#use-of-generative-ai), we request that you don't use LLMs to generate PRs as those are not a good use of maintainer time." \
-f query='
mutation CommentAndClosePR($prId: ID!, $body: String!) {
addComment(input: {
subjectId: $prId,
body: $body
}) {
__typename
}
closePullRequest(input: {
pullRequestId: $prId
}) {
__typename
}
}'
+4 -4
View File
@@ -44,7 +44,7 @@ jobs:
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -57,7 +57,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -70,7 +70,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 uses: github/codeql-action/autobuild@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
# â„šī¸ Command-line programs to run using the OS shell. # â„šī¸ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -83,6 +83,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh # ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
with: with:
category: '/language:${{matrix.language}}' category: '/language:${{matrix.language}}'
+9 -9
View File
@@ -23,14 +23,14 @@ jobs:
should_run: ${{ steps.check.outputs.should_run }} should_run: ${{ steps.check.outputs.should_run }}
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- name: Check what should run - name: Check what should run
id: check id: check
uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 uses: immich-app/devtools/actions/pre-job@eed0f8b8165ffcb951f2ba854b2dd031935e1d73 # pre-job-action-v2.0.2
with: with:
github-token: ${{ steps.token.outputs.token }} github-token: ${{ steps.token.outputs.token }}
filters: | filters: |
@@ -60,7 +60,7 @@ jobs:
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn'] suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
steps: steps:
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@@ -90,7 +90,7 @@ jobs:
suffix: [''] suffix: ['']
steps: steps:
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@@ -131,8 +131,8 @@ jobs:
- device: rocm - device: rocm
suffixes: '-rocm' suffixes: '-rocm'
platforms: linux/amd64 platforms: linux/amd64
runner-mapping: '{"linux/amd64": "pokedex-large"}' runner-mapping: '{"linux/amd64": "pokedex-giant"}'
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@61a0fc2b41524edcc7c9fffb8bb178e6b0ccf21d # multi-runner-build-workflow-v2.3.0 uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@bd49ed7a5a6022149f79b6564df48177476a822b # multi-runner-build-workflow-v2.2.1
permissions: permissions:
contents: read contents: read
actions: read actions: read
@@ -155,7 +155,7 @@ jobs:
name: Build and Push Server name: Build and Push Server
needs: pre-job needs: pre-job
if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }} if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }}
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@61a0fc2b41524edcc7c9fffb8bb178e6b0ccf21d # multi-runner-build-workflow-v2.3.0 uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@bd49ed7a5a6022149f79b6564df48177476a822b # multi-runner-build-workflow-v2.2.1
permissions: permissions:
contents: read contents: read
actions: read actions: read
@@ -178,7 +178,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: always() if: always()
steps: steps:
- uses: immich-app/devtools/actions/success-check@53bb77345ee9f953f93bd6fd9980f07a2f24965e # success-check-action-v0.0.5 - uses: immich-app/devtools/actions/success-check@68f10eb389bb02a3cf9d1156111964c549eb421b # 0.0.4
with: with:
needs: ${{ toJSON(needs) }} needs: ${{ toJSON(needs) }}
@@ -189,6 +189,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: always() if: always()
steps: steps:
- uses: immich-app/devtools/actions/success-check@53bb77345ee9f953f93bd6fd9980f07a2f24965e # success-check-action-v0.0.5 - uses: immich-app/devtools/actions/success-check@68f10eb389bb02a3cf9d1156111964c549eb421b # 0.0.4
with: with:
needs: ${{ toJSON(needs) }} needs: ${{ toJSON(needs) }}
+6 -6
View File
@@ -21,14 +21,14 @@ jobs:
should_run: ${{ steps.check.outputs.should_run }} should_run: ${{ steps.check.outputs.should_run }}
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- name: Check what should run - name: Check what should run
id: check id: check
uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 uses: immich-app/devtools/actions/pre-job@eed0f8b8165ffcb951f2ba854b2dd031935e1d73 # pre-job-action-v2.0.2
with: with:
github-token: ${{ steps.token.outputs.token }} github-token: ${{ steps.token.outputs.token }}
filters: | filters: |
@@ -54,7 +54,7 @@ jobs:
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -67,10 +67,10 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './docs/.nvmrc' node-version-file: './docs/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -86,7 +86,7 @@ jobs:
run: pnpm build run: pnpm build
- name: Upload build output - name: Upload build output
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with: with:
name: docs-build-output name: docs-build-output
path: docs/build/ path: docs/build/
+3 -3
View File
@@ -20,7 +20,7 @@ jobs:
artifact: ${{ steps.get-artifact.outputs.result }} artifact: ${{ steps.get-artifact.outputs.result }}
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -119,7 +119,7 @@ jobs:
if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }} if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }}
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -131,7 +131,7 @@ jobs:
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup Mise - name: Setup Mise
uses: immich-app/devtools/actions/use-mise@035e80a7d4355d5f087ffb95db9e4a0944c04e56 # use-mise-action-v1.1.3 uses: immich-app/devtools/actions/use-mise@dab18118da6476e8237ac94080fd937983fecd42 # use-mise-action-v1.1.2
- name: Load parameters - name: Load parameters
id: parameters id: parameters
+2 -2
View File
@@ -17,7 +17,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -29,7 +29,7 @@ jobs:
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup Mise - name: Setup Mise
uses: immich-app/devtools/actions/use-mise@035e80a7d4355d5f087ffb95db9e4a0944c04e56 # use-mise-action-v1.1.3 uses: immich-app/devtools/actions/use-mise@dab18118da6476e8237ac94080fd937983fecd42 # use-mise-action-v1.1.2
- name: Destroy Docs Subdomain - name: Destroy Docs Subdomain
env: env:
+3 -3
View File
@@ -16,7 +16,7 @@ jobs:
steps: steps:
- name: Generate a token - name: Generate a token
id: generate-token id: generate-token
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -29,10 +29,10 @@ jobs:
persist-credentials: true persist-credentials: true
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
- name: Generate a token - name: Generate a token
id: generate_token id: generate_token
if: ${{ inputs.skip != true }} if: ${{ inputs.skip != true }}
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
+2 -2
View File
@@ -14,13 +14,13 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- name: Require PR to have a changelog label - name: Require PR to have a changelog label
uses: mheap/github-action-required-labels@0ac283b4e65c1fb28ce6079dea5546ceca98ccbe # v5.5.2 uses: mheap/github-action-required-labels@8afbe8ae6ab7647d0c9f0cfa7c2f939650d22509 # v5.5.1
with: with:
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
mode: exactly mode: exactly
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
+7 -8
View File
@@ -50,7 +50,7 @@ jobs:
steps: steps:
- name: Generate a token - name: Generate a token
id: generate-token id: generate-token
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -63,13 +63,13 @@ jobs:
ref: main ref: main
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -124,7 +124,7 @@ jobs:
steps: steps:
- name: Generate a token - name: Generate a token
id: generate-token id: generate-token
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -136,13 +136,13 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Download APK - name: Download APK
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with: with:
name: release-apk-signed name: release-apk-signed
github-token: ${{ steps.generate-token.outputs.token }} github-token: ${{ steps.generate-token.outputs.token }}
- name: Create draft release - name: Create draft release
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with: with:
draft: true draft: true
tag_name: ${{ needs.bump_version.outputs.version }} tag_name: ${{ needs.bump_version.outputs.version }}
@@ -151,7 +151,6 @@ jobs:
body_path: misc/release/notes.tmpl body_path: misc/release/notes.tmpl
files: | files: |
docker/docker-compose.yml docker/docker-compose.yml
docker/docker-compose.rootless.yml
docker/example.env docker/example.env
docker/hwaccel.ml.yml docker/hwaccel.ml.yml
docker/hwaccel.transcoding.yml docker/hwaccel.transcoding.yml
+5 -5
View File
@@ -14,12 +14,12 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- uses: mshick/add-pr-comment@ffd016c7e151d97d69d21a843022fd4cd5b96fe5 # v3.9.0 - uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2
with: with:
github-token: ${{ steps.token.outputs.token }} github-token: ${{ steps.token.outputs.token }}
message-id: 'preview-status' message-id: 'preview-status'
@@ -32,7 +32,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -48,14 +48,14 @@ jobs:
name: 'preview' name: 'preview'
}) })
- uses: mshick/add-pr-comment@ffd016c7e151d97d69d21a843022fd4cd5b96fe5 # v3.9.0 - uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2
if: ${{ github.event.pull_request.head.repo.fork }} if: ${{ github.event.pull_request.head.repo.fork }}
with: with:
github-token: ${{ steps.token.outputs.token }} github-token: ${{ steps.token.outputs.token }}
message-id: 'preview-status' message-id: 'preview-status'
message: 'PRs from forks cannot have preview environments.' message: 'PRs from forks cannot have preview environments.'
- uses: mshick/add-pr-comment@ffd016c7e151d97d69d21a843022fd4cd5b96fe5 # v3.9.0 - uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2
if: ${{ !github.event.pull_request.head.repo.fork }} if: ${{ !github.event.pull_request.head.repo.fork }}
with: with:
github-token: ${{ steps.token.outputs.token }} github-token: ${{ steps.token.outputs.token }}
+170
View File
@@ -0,0 +1,170 @@
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@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
token: ${{ steps.generate-token.outputs.token }}
persist-credentials: true
ref: main
- name: Install uv
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.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@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
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
+149
View File
@@ -0,0 +1,149 @@
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@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
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@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: release-apk-signed
github-token: ${{ steps.generate-token.outputs.token }}
- name: Create draft release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
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/docker-compose.rootless.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');
}
+3 -3
View File
@@ -19,7 +19,7 @@ jobs:
working-directory: ./open-api/typescript-sdk working-directory: ./open-api/typescript-sdk
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -30,10 +30,10 @@ jobs:
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
# Setup .npmrc file to publish to npm # Setup .npmrc file to publish to npm
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './open-api/typescript-sdk/.nvmrc' node-version-file: './open-api/typescript-sdk/.nvmrc'
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
+4 -4
View File
@@ -20,14 +20,14 @@ jobs:
should_run: ${{ steps.check.outputs.should_run }} should_run: ${{ steps.check.outputs.should_run }}
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- name: Check what should run - name: Check what should run
id: check id: check
uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 uses: immich-app/devtools/actions/pre-job@eed0f8b8165ffcb951f2ba854b2dd031935e1d73 # pre-job-action-v2.0.2
with: with:
github-token: ${{ steps.token.outputs.token }} github-token: ${{ steps.token.outputs.token }}
filters: | filters: |
@@ -49,7 +49,7 @@ jobs:
working-directory: ./mobile working-directory: ./mobile
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -61,7 +61,7 @@ jobs:
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup Flutter SDK - name: Setup Flutter SDK
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2.21.0
with: with:
channel: 'stable' channel: 'stable'
flutter-version-file: ./mobile/pubspec.yaml flutter-version-file: ./mobile/pubspec.yaml
+53 -53
View File
@@ -17,14 +17,14 @@ jobs:
should_run: ${{ steps.check.outputs.should_run }} should_run: ${{ steps.check.outputs.should_run }}
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- name: Check what should run - name: Check what should run
id: check id: check
uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 uses: immich-app/devtools/actions/pre-job@eed0f8b8165ffcb951f2ba854b2dd031935e1d73 # pre-job-action-v2.0.2
with: with:
github-token: ${{ steps.token.outputs.token }} github-token: ${{ steps.token.outputs.token }}
filters: | filters: |
@@ -63,7 +63,7 @@ jobs:
working-directory: ./server working-directory: ./server
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -75,9 +75,9 @@ jobs:
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -108,7 +108,7 @@ jobs:
working-directory: ./cli working-directory: ./cli
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -119,9 +119,9 @@ jobs:
persist-credentials: false persist-credentials: false
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './cli/.nvmrc' node-version-file: './cli/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -155,7 +155,7 @@ jobs:
working-directory: ./cli working-directory: ./cli
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -166,9 +166,9 @@ jobs:
persist-credentials: false persist-credentials: false
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './cli/.nvmrc' node-version-file: './cli/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -197,7 +197,7 @@ jobs:
working-directory: ./web working-directory: ./web
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -208,9 +208,9 @@ jobs:
persist-credentials: false persist-credentials: false
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './web/.nvmrc' node-version-file: './web/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -241,7 +241,7 @@ jobs:
working-directory: ./web working-directory: ./web
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -252,9 +252,9 @@ jobs:
persist-credentials: false persist-credentials: false
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './web/.nvmrc' node-version-file: './web/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -279,7 +279,7 @@ jobs:
contents: read contents: read
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -290,9 +290,9 @@ jobs:
persist-credentials: false persist-credentials: false
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './web/.nvmrc' node-version-file: './web/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -327,7 +327,7 @@ jobs:
working-directory: ./e2e working-directory: ./e2e
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -338,9 +338,9 @@ jobs:
persist-credentials: false persist-credentials: false
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './e2e/.nvmrc' node-version-file: './e2e/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -373,7 +373,7 @@ jobs:
working-directory: ./server working-directory: ./server
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -385,9 +385,9 @@ jobs:
submodules: 'recursive' submodules: 'recursive'
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -412,7 +412,7 @@ jobs:
runner: [ubuntu-latest, ubuntu-24.04-arm] runner: [ubuntu-latest, ubuntu-24.04-arm]
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -424,9 +424,9 @@ jobs:
submodules: 'recursive' submodules: 'recursive'
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './e2e/.nvmrc' node-version-file: './e2e/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -464,7 +464,7 @@ jobs:
run: docker compose logs --no-color > docker-compose-logs.txt run: docker compose logs --no-color > docker-compose-logs.txt
working-directory: ./e2e working-directory: ./e2e
- name: Archive Docker logs - name: Archive Docker logs
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
if: always() if: always()
with: with:
name: e2e-server-docker-logs-${{ matrix.runner }} name: e2e-server-docker-logs-${{ matrix.runner }}
@@ -484,7 +484,7 @@ jobs:
runner: [ubuntu-latest, ubuntu-24.04-arm] runner: [ubuntu-latest, ubuntu-24.04-arm]
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -496,9 +496,9 @@ jobs:
submodules: 'recursive' submodules: 'recursive'
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './e2e/.nvmrc' node-version-file: './e2e/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -511,7 +511,7 @@ jobs:
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Install Playwright Browsers - name: Install Playwright Browsers
run: pnpm exec playwright install chromium --only-shell run: npx playwright install chromium --only-shell
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Docker build - name: Docker build
run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300 run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
@@ -522,7 +522,7 @@ jobs:
run: pnpm test:web run: pnpm test:web
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Archive e2e test (web) results - name: Archive e2e test (web) results
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
if: success() || failure() if: success() || failure()
with: with:
name: e2e-web-test-results-${{ matrix.runner }} name: e2e-web-test-results-${{ matrix.runner }}
@@ -533,7 +533,7 @@ jobs:
run: pnpm test:web:ui run: pnpm test:web:ui
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Archive ui test (web) results - name: Archive ui test (web) results
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
if: success() || failure() if: success() || failure()
with: with:
name: e2e-ui-test-results-${{ matrix.runner }} name: e2e-ui-test-results-${{ matrix.runner }}
@@ -544,7 +544,7 @@ jobs:
run: pnpm test:web:maintenance run: pnpm test:web:maintenance
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Archive maintenance tests (web) results - name: Archive maintenance tests (web) results
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
if: success() || failure() if: success() || failure()
with: with:
name: e2e-maintenance-isolated-test-results-${{ matrix.runner }} name: e2e-maintenance-isolated-test-results-${{ matrix.runner }}
@@ -554,7 +554,7 @@ jobs:
run: docker compose logs --no-color > docker-compose-logs.txt run: docker compose logs --no-color > docker-compose-logs.txt
working-directory: ./e2e working-directory: ./e2e
- name: Archive Docker logs - name: Archive Docker logs
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
if: always() if: always()
with: with:
name: e2e-web-docker-logs-${{ matrix.runner }} name: e2e-web-docker-logs-${{ matrix.runner }}
@@ -566,7 +566,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: always() if: always()
steps: steps:
- uses: immich-app/devtools/actions/success-check@53bb77345ee9f953f93bd6fd9980f07a2f24965e # success-check-action-v0.0.5 - uses: immich-app/devtools/actions/success-check@68f10eb389bb02a3cf9d1156111964c549eb421b # 0.0.4
with: with:
needs: ${{ toJSON(needs) }} needs: ${{ toJSON(needs) }}
mobile-unit-tests: mobile-unit-tests:
@@ -578,7 +578,7 @@ jobs:
contents: read contents: read
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -588,7 +588,7 @@ jobs:
persist-credentials: false persist-credentials: false
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup Flutter SDK - name: Setup Flutter SDK
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2.21.0
with: with:
channel: 'stable' channel: 'stable'
flutter-version-file: ./mobile/pubspec.yaml flutter-version-file: ./mobile/pubspec.yaml
@@ -610,7 +610,7 @@ jobs:
working-directory: ./machine-learning working-directory: ./machine-learning
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -620,7 +620,7 @@ jobs:
persist-credentials: false persist-credentials: false
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
with: with:
python-version: 3.11 python-version: 3.11
- name: Install dependencies - name: Install dependencies
@@ -650,7 +650,7 @@ jobs:
working-directory: ./.github working-directory: ./.github
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -661,9 +661,9 @@ jobs:
persist-credentials: false persist-credentials: false
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './.github/.nvmrc' node-version-file: './.github/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -680,7 +680,7 @@ jobs:
contents: read contents: read
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -701,7 +701,7 @@ jobs:
contents: read contents: read
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -712,9 +712,9 @@ jobs:
persist-credentials: false persist-credentials: false
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -763,7 +763,7 @@ jobs:
working-directory: ./server working-directory: ./server
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -774,9 +774,9 @@ jobs:
persist-credentials: false persist-credentials: false
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
+4 -4
View File
@@ -24,14 +24,14 @@ jobs:
should_run: ${{ steps.check.outputs.should_run }} should_run: ${{ steps.check.outputs.should_run }}
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- name: Check what should run - name: Check what should run
id: check id: check
uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 uses: immich-app/devtools/actions/pre-job@eed0f8b8165ffcb951f2ba854b2dd031935e1d73 # pre-job-action-v2.0.2
with: with:
github-token: ${{ steps.token.outputs.token }} github-token: ${{ steps.token.outputs.token }}
filters: | filters: |
@@ -47,7 +47,7 @@ jobs:
if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }} if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }}
steps: steps:
- id: token - id: token
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -68,6 +68,6 @@ jobs:
permissions: {} permissions: {}
if: always() if: always()
steps: steps:
- uses: immich-app/devtools/actions/success-check@53bb77345ee9f953f93bd6fd9980f07a2f24965e # success-check-action-v0.0.5 - uses: immich-app/devtools/actions/success-check@68f10eb389bb02a3cf9d1156111964c549eb421b # 0.0.4
with: with:
needs: ${{ toJSON(needs) }} needs: ${{ toJSON(needs) }}
+1 -8
View File
@@ -5,13 +5,6 @@
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"dart-code.flutter", "dart-code.flutter",
"dart-code.dart-code", "dart-code.dart-code",
"dcmdev.dcm-vscode-extension", "dcmdev.dcm-vscode-extension"
"bradlc.vscode-tailwindcss",
"ms-playwright.playwright",
"vitest.explorer",
"editorconfig.editorconfig",
"foxundermoon.shell-format",
"timonwong.shellcheck",
"bluebrown.yamlfmt"
] ]
} }
+13 -35
View File
@@ -1,7 +1,8 @@
{ {
"[css]": { "[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true,
"editor.tabSize": 2
}, },
"[dart]": { "[dart]": {
"editor.defaultFormatter": "Dart-Code.dart-code", "editor.defaultFormatter": "Dart-Code.dart-code",
@@ -18,15 +19,18 @@
"source.removeUnusedImports": "explicit" "source.removeUnusedImports": "explicit"
}, },
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true,
"editor.tabSize": 2
}, },
"[json]": { "[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true,
"editor.tabSize": 2
}, },
"[jsonc]": { "[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true,
"editor.tabSize": 2
}, },
"[svelte]": { "[svelte]": {
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
@@ -34,7 +38,8 @@
"source.removeUnusedImports": "explicit" "source.removeUnusedImports": "explicit"
}, },
"editor.defaultFormatter": "svelte.svelte-vscode", "editor.defaultFormatter": "svelte.svelte-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true,
"editor.tabSize": 2
}, },
"[typescript]": { "[typescript]": {
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
@@ -42,45 +47,18 @@
"source.removeUnusedImports": "explicit" "source.removeUnusedImports": "explicit"
}, },
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true,
"editor.tabSize": 2
}, },
"cSpell.words": ["immich"], "cSpell.words": ["immich"],
"css.lint.unknownAtRules": "ignore",
"editor.bracketPairColorization.enabled": true,
"editor.formatOnSave": true, "editor.formatOnSave": true,
"eslint.useFlatConfig": true,
"eslint.validate": ["javascript", "typescript", "svelte"], "eslint.validate": ["javascript", "typescript", "svelte"],
"eslint.workingDirectories": [
{ "directory": "cli", "changeProcessCWD": true },
{ "directory": "e2e", "changeProcessCWD": true },
{ "directory": "server", "changeProcessCWD": true },
{ "directory": "web", "changeProcessCWD": true }
],
"files.watcherExclude": {
"**/.jj/**": true,
"**/.git/**": true,
"**/node_modules/**": true,
"**/build/**": true,
"**/dist/**": true,
"**/.svelte-kit/**": true
},
"explorer.fileNesting.enabled": true, "explorer.fileNesting.enabled": true,
"explorer.fileNesting.patterns": { "explorer.fileNesting.patterns": {
"*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart", "*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart",
"*.ts": "${capture}.spec.ts,${capture}.mock.ts", "*.ts": "${capture}.spec.ts,${capture}.mock.ts",
"package.json": "package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb, bun.lock, pnpm-workspace.yaml, .pnpmfile.cjs" "package.json": "package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb, bun.lock, pnpm-workspace.yaml, .pnpmfile.cjs"
}, },
"search.exclude": {
"**/node_modules": true,
"**/build": true,
"**/dist": true,
"**/.svelte-kit": true,
"**/open-api/typescript-sdk/src": true
},
"svelte.enable-ts-plugin": true, "svelte.enable-ts-plugin": true,
"tailwindCSS.experimental.configFile": { "typescript.preferences.importModuleSpecifier": "non-relative"
"web/src/app.css": "web/src/**"
},
"js/ts.preferences.importModuleSpecifier": "non-relative",
"vitest.maximumConfigs": 10
} }
-2
View File
@@ -15,8 +15,6 @@ Please try to keep pull requests as focused as possible. A PR should do exactly
If you are looking for something to work on, there are discussions and issues with a `good-first-issue` label on them. These are always a good starting point. If none of them sound interesting or fit your skill set, feel free to reach out on our Discord. We're happy to help you find something to work on! If you are looking for something to work on, there are discussions and issues with a `good-first-issue` label on them. These are always a good starting point. If none of them sound interesting or fit your skill set, feel free to reach out on our Discord. We're happy to help you find something to work on!
We usually do not assign issues to new contributors, since it happens often that a PR is never even opened. Again, reach out on Discord if you fear putting a lot of time into fixing an issue, but ending up with a duplicate PR.
## Use of generative AI ## Use of generative AI
We ask you not to open PRs generated with an LLM. We find that code generated like this tends to need a large amount of back-and-forth, which is a very inefficient use of our time. If we want LLM-generated code, it's much faster for us to use an LLM ourselves than to go through an intermediary via a pull request. We ask you not to open PRs generated with an LLM. We find that code generated like this tends to need a large amount of back-and-forth, which is a very inefficient use of our time. If we want LLM-generated code, it's much faster for us to use an LLM ourselves than to go through an intermediary via a pull request.
+1 -1
View File
@@ -52,7 +52,7 @@ attach-server:
docker exec -it docker_immich-server_1 sh docker exec -it docker_immich-server_1 sh
renovate: renovate:
LOG_LEVEL=debug pnpm exec renovate --platform=local --repository-cache=reset LOG_LEVEL=debug npx renovate --platform=local --repository-cache=reset
# Directories that need to be created for volumes or build output # Directories that need to be created for volumes or build output
VOLUME_DIRS = \ VOLUME_DIRS = \
+1 -1
View File
@@ -1 +1 @@
24.14.1 24.13.1
+15 -14
View File
@@ -1,6 +1,6 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.6.3", "version": "2.5.6",
"description": "Command Line Interface (CLI) for Immich", "description": "Command Line Interface (CLI) for Immich",
"type": "module", "type": "module",
"exports": "./dist/index.js", "exports": "./dist/index.js",
@@ -13,30 +13,31 @@
"cli" "cli"
], ],
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.0", "@eslint/js": "^9.8.0",
"@immich/sdk": "workspace:*", "@immich/sdk": "workspace:*",
"@types/byte-size": "^8.1.0", "@types/byte-size": "^8.1.0",
"@types/cli-progress": "^3.11.0", "@types/cli-progress": "^3.11.0",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/micromatch": "^4.0.9", "@types/micromatch": "^4.0.9",
"@types/mock-fs": "^4.13.1", "@types/mock-fs": "^4.13.1",
"@types/node": "^24.12.0", "@types/node": "^24.10.13",
"@vitest/coverage-v8": "^4.0.0", "@vitest/coverage-v8": "^3.0.0",
"byte-size": "^9.0.0", "byte-size": "^9.0.0",
"cli-progress": "^3.12.0", "cli-progress": "^3.12.0",
"commander": "^12.0.0", "commander": "^12.0.0",
"eslint": "^10.0.0", "eslint": "^9.14.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^63.0.0", "eslint-plugin-unicorn": "^62.0.0",
"globals": "^17.0.0", "globals": "^16.0.0",
"mock-fs": "^5.2.0", "mock-fs": "^5.2.0",
"prettier": "^3.7.4", "prettier": "^3.7.4",
"prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-organize-imports": "^4.0.0",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"typescript-eslint": "^8.28.0", "typescript-eslint": "^8.28.0",
"vite": "^8.0.0", "vite": "^7.0.0",
"vitest": "^4.0.0", "vite-tsconfig-paths": "^6.0.0",
"vitest": "^3.0.0",
"vitest-fetch-mock": "^0.4.0", "vitest-fetch-mock": "^0.4.0",
"yaml": "^2.3.1" "yaml": "^2.3.1"
}, },
@@ -44,12 +45,12 @@
"build": "vite build", "build": "vite build",
"build:dev": "vite build --sourcemap true", "build:dev": "vite build --sourcemap true",
"lint": "eslint \"src/**/*.ts\" --max-warnings 0", "lint": "eslint \"src/**/*.ts\" --max-warnings 0",
"lint:fix": "pnpm run lint --fix", "lint:fix": "npm run lint -- --fix",
"prepack": "pnpm run build", "prepack": "npm run build",
"test": "vitest", "test": "vitest",
"test:cov": "vitest --coverage", "test:cov": "vitest --coverage",
"format": "prettier --cache --check .", "format": "prettier --check .",
"format:fix": "prettier --cache --write --list-different .", "format:fix": "prettier --write .",
"check": "tsc --noEmit" "check": "tsc --noEmit"
}, },
"repository": { "repository": {
@@ -68,6 +69,6 @@
"micromatch": "^4.0.8" "micromatch": "^4.0.8"
}, },
"volta": { "volta": {
"node": "24.14.1" "node": "24.13.1"
} }
} }
+36 -45
View File
@@ -1,6 +1,6 @@
import fs from 'node:fs'; import * as fs from 'node:fs';
import os from 'node:os'; import * as os from 'node:os';
import path from 'node:path'; import * as path from 'node:path';
import { setTimeout as sleep } from 'node:timers/promises'; import { setTimeout as sleep } from 'node:timers/promises';
import { describe, expect, it, MockedFunction, vi } from 'vitest'; import { describe, expect, it, MockedFunction, vi } from 'vitest';
@@ -58,7 +58,7 @@ describe('uploadFiles', () => {
}); });
it('returns new assets when upload file is successful', async () => { it('returns new assets when upload file is successful', async () => {
fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), function () { fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), () => {
return { return {
status: 200, status: 200,
body: JSON.stringify({ id: 'fc5621b1-86f6-44a1-9905-403e607df9f5', status: 'created' }), body: JSON.stringify({ id: 'fc5621b1-86f6-44a1-9905-403e607df9f5', status: 'created' }),
@@ -75,7 +75,7 @@ describe('uploadFiles', () => {
it('returns new assets when upload file retry is successful', async () => { it('returns new assets when upload file retry is successful', async () => {
let counter = 0; let counter = 0;
fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), function () { fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), () => {
counter++; counter++;
if (counter < retry) { if (counter < retry) {
throw new Error('Network error'); throw new Error('Network error');
@@ -96,7 +96,7 @@ describe('uploadFiles', () => {
}); });
it('returns new assets when upload file retry is failed', async () => { it('returns new assets when upload file retry is failed', async () => {
fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), function () { fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), () => {
throw new Error('Network error'); throw new Error('Network error');
}); });
@@ -236,19 +236,16 @@ describe('startWatch', () => {
await sleep(100); // to debounce the watcher from considering the test file as a existing file await sleep(100); // to debounce the watcher from considering the test file as a existing file
await fs.promises.writeFile(testFilePath, 'testjpg'); await fs.promises.writeFile(testFilePath, 'testjpg');
await vi.waitFor( await vi.waitUntil(() => checkBulkUploadMocked.mock.calls.length > 0, 3000);
() => expect(checkBulkUpload).toHaveBeenCalledWith({
expect(checkBulkUpload).toHaveBeenCalledWith({ assetBulkUploadCheckDto: {
assetBulkUploadCheckDto: { assets: [
assets: [ expect.objectContaining({
expect.objectContaining({ id: testFilePath,
id: testFilePath, }),
}), ],
], },
}, });
}),
{ timeout: 5000 },
);
}); });
it('should filter out unsupported files', async () => { it('should filter out unsupported files', async () => {
@@ -260,19 +257,16 @@ describe('startWatch', () => {
await fs.promises.writeFile(testFilePath, 'testjpg'); await fs.promises.writeFile(testFilePath, 'testjpg');
await fs.promises.writeFile(unsupportedFilePath, 'testtxt'); await fs.promises.writeFile(unsupportedFilePath, 'testtxt');
await vi.waitFor( await vi.waitUntil(() => checkBulkUploadMocked.mock.calls.length > 0, 3000);
() => expect(checkBulkUpload).toHaveBeenCalledWith({
expect(checkBulkUpload).toHaveBeenCalledWith({ assetBulkUploadCheckDto: {
assetBulkUploadCheckDto: { assets: expect.arrayContaining([
assets: expect.arrayContaining([ expect.objectContaining({
expect.objectContaining({ id: testFilePath,
id: testFilePath, }),
}), ]),
]), },
}, });
}),
{ timeout: 5000 },
);
expect(checkBulkUpload).not.toHaveBeenCalledWith({ expect(checkBulkUpload).not.toHaveBeenCalledWith({
assetBulkUploadCheckDto: { assetBulkUploadCheckDto: {
@@ -297,19 +291,16 @@ describe('startWatch', () => {
await fs.promises.writeFile(testFilePath, 'testjpg'); await fs.promises.writeFile(testFilePath, 'testjpg');
await fs.promises.writeFile(ignoredFilePath, 'ignoredjpg'); await fs.promises.writeFile(ignoredFilePath, 'ignoredjpg');
await vi.waitFor( await vi.waitUntil(() => checkBulkUploadMocked.mock.calls.length > 0, 3000);
() => expect(checkBulkUpload).toHaveBeenCalledWith({
expect(checkBulkUpload).toHaveBeenCalledWith({ assetBulkUploadCheckDto: {
assetBulkUploadCheckDto: { assets: expect.arrayContaining([
assets: expect.arrayContaining([ expect.objectContaining({
expect.objectContaining({ id: testFilePath,
id: testFilePath, }),
}), ]),
]), },
}, });
}),
{ timeout: 5000 },
);
expect(checkBulkUpload).not.toHaveBeenCalledWith({ expect(checkBulkUpload).not.toHaveBeenCalledWith({
assetBulkUploadCheckDto: { assetBulkUploadCheckDto: {
+1 -1
View File
@@ -81,7 +81,7 @@ export const connect = async (url: string, key: string) => {
const [error] = await withError(getMyUser()); const [error] = await withError(getMyUser());
if (isHttpError(error)) { if (isHttpError(error)) {
logError(error, `Failed to connect to server ${url}`); logError(error, 'Failed to connect to server');
process.exit(1); process.exit(1);
} }
+6 -11
View File
@@ -1,12 +1,10 @@
import { defineConfig, UserConfig } from 'vite'; import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({ export default defineConfig({
resolve: { resolve: { alias: { src: '/src' } },
alias: { src: '/src' },
tsconfigPaths: true,
},
build: { build: {
rolldownOptions: { rollupOptions: {
input: 'src/index.ts', input: 'src/index.ts',
output: { output: {
dir: 'dist', dir: 'dist',
@@ -18,8 +16,5 @@ export default defineConfig({
// bundle everything except for Node built-ins // bundle everything except for Node built-ins
noExternal: /^(?!node:).*$/, noExternal: /^(?!node:).*$/,
}, },
test: { plugins: [tsconfigPaths()],
name: 'cli:unit', });
globals: true,
},
} as UserConfig);
+7
View File
@@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
},
});
+2 -2
View File
@@ -1,6 +1,6 @@
[tools] [tools]
terragrunt = "0.99.4" terragrunt = "0.98.0"
opentofu = "1.11.5" opentofu = "1.11.4"
[tasks."tg:fmt"] [tasks."tg:fmt"]
run = "terragrunt hclfmt" run = "terragrunt hclfmt"
+1 -2
View File
@@ -90,7 +90,6 @@ services:
IMMICH_THIRD_PARTY_BUG_FEATURE_URL: https://github.com/immich-app/immich/issues 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_DOCUMENTATION_URL: https://docs.immich.app
IMMICH_THIRD_PARTY_SUPPORT_URL: https://docs.immich.app/community-guides IMMICH_THIRD_PARTY_SUPPORT_URL: https://docs.immich.app/community-guides
IMMICH_HELMET_FILE: 'true'
ports: ports:
- 9230:9230 - 9230:9230
- 9231:9231 - 9231:9231
@@ -156,7 +155,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:3eeb09785cd61ec8e3be35f8804c8892080f3ca21934d628abc24ee4ed1698f6 image: docker.io/valkey/valkey:9@sha256:930b41430fb727f533c5982fe509b6f04233e26d0f7354e04de4b0d5c706e44e
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
+3 -3
View File
@@ -56,7 +56,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:3eeb09785cd61ec8e3be35f8804c8892080f3ca21934d628abc24ee4ed1698f6 image: docker.io/valkey/valkey:9@sha256:930b41430fb727f533c5982fe509b6f04233e26d0f7354e04de4b0d5c706e44e
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
restart: always restart: always
@@ -85,7 +85,7 @@ services:
container_name: immich_prometheus container_name: immich_prometheus
ports: ports:
- 9090:9090 - 9090:9090
image: prom/prometheus@sha256:4a61322ac1103a0e3aea2a61ef1718422a48fa046441f299d71e660a3bc71ae9 image: prom/prometheus@sha256:1f0f50f06acaceb0f5670d2c8a658a599affe7b0d8e78b898c1035653849a702
volumes: volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml - ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus - prometheus-data:/prometheus
@@ -97,7 +97,7 @@ services:
command: ['./run.sh', '-disable-reporting'] command: ['./run.sh', '-disable-reporting']
ports: ports:
- 3000:3000 - 3000:3000
image: grafana/grafana:12.4.2-ubuntu@sha256:78839fe49e1425c02416fa8072591533a72bd9598e563b54a07d78f9e27fb5d3 image: grafana/grafana:12.3.2-ubuntu@sha256:6cca4b429a1dc0d37d401dee54825c12d40056c3c6f3f56e3f0d6318ce77749b
volumes: volumes:
- grafana-data:/var/lib/grafana - grafana-data:/var/lib/grafana
+1 -1
View File
@@ -61,7 +61,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:3eeb09785cd61ec8e3be35f8804c8892080f3ca21934d628abc24ee4ed1698f6 image: docker.io/valkey/valkey:9@sha256:930b41430fb727f533c5982fe509b6f04233e26d0f7354e04de4b0d5c706e44e
user: '1000:1000' user: '1000:1000'
security_opt: security_opt:
- no-new-privileges:true - no-new-privileges:true
+1 -1
View File
@@ -49,7 +49,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:3eeb09785cd61ec8e3be35f8804c8892080f3ca21934d628abc24ee4ed1698f6 image: docker.io/valkey/valkey:9@sha256:930b41430fb727f533c5982fe509b6f04233e26d0f7354e04de4b0d5c706e44e
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
restart: always restart: always
+1 -1
View File
@@ -1 +1 @@
24.14.1 24.13.1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

+3 -4
View File
@@ -67,8 +67,7 @@ graph TD
C --> D["Thumbnail Generation (Large, small, blurred and person)"] C --> D["Thumbnail Generation (Large, small, blurred and person)"]
D --> E[Smart Search] D --> E[Smart Search]
D --> F[Face Detection] D --> F[Face Detection]
D --> G[OCR] D --> G[Video Transcoding]
D --> H[Video Transcoding] E --> H[Duplicate Detection]
E --> I[Duplicate Detection] F --> I[Facial Recognition]
F --> J[Facial Recognition]
``` ```
-37
View File
@@ -14,7 +14,6 @@ Immich supports 3rd party authentication via [OpenID Connect][oidc] (OIDC), an i
- [Authelia](https://www.authelia.com/integration/openid-connect/immich/) - [Authelia](https://www.authelia.com/integration/openid-connect/immich/)
- [Okta](https://www.okta.com/openid-connect/) - [Okta](https://www.okta.com/openid-connect/)
- [Google](https://developers.google.com/identity/openid-connect/openid-connect) - [Google](https://developers.google.com/identity/openid-connect/openid-connect)
- [Keycloak](https://www.keycloak.org)
## Prerequisites ## Prerequisites
@@ -254,40 +253,4 @@ Configuration of OAuth in Immich System Settings
</details> </details>
<details>
<summary>Keycloak Example</summary>
### Keycloak Example
Here's an example of OAuth configured for Keycloak:
Create your immich client on your Keycloak Realm.
<img src={require('./img/keycloak-general-settings.webp').default} width='100%' title="Keycloak Client general Settings" />
<img src={require('./img/keycloak-access-settings.webp').default} width='100%' title="Keycloak Client Access Settings" />
<img src={require('./img/keycloak-capability-config.webp').default} width='100%' title="Keycloak Client Capability Configuration" />
Configuration of OAuth in Immich System Settings
| Setting | Value |
| ---------------------------- | ----------------------------------------------------- |
| Issuer URL | `https://<KEYCLOAK_DOMAIN>/realms/<YOUR_REALM>` |
| Client ID | immich |
| Client Secret | can be optained from Clients -> immich -> Credentials |
| Scope | openid email profile |
| Signing Algorithm | RS256 |
| Storage Label Claim | preferred_username |
| Role Claim | immich_role |
| Storage Quota Claim | immich_quota |
| Default Storage Quota (GiB) | 0 (empty for unlimited quota) |
| Button Text | Sign in with Keycloak (recommended) |
| Auto Register | Enabled (optional) |
| Auto Launch | Enabled (optional) |
| Mobile Redirect URI Override | Disabled |
| Mobile Redirect URI | |
Role Claim can be managed via Client Role. Remember to create a mapper with claim name `immich_role`.
</details>
[oidc]: https://openid.net/connect/ [oidc]: https://openid.net/connect/
+1 -1
View File
@@ -230,7 +230,7 @@ The default value is `ultrafast`.
### Audio codec (`ffmpeg.targetAudioCodec`) {#ffmpeg.targetAudioCodec} ### Audio codec (`ffmpeg.targetAudioCodec`) {#ffmpeg.targetAudioCodec}
Which audio codec to use when the audio stream is being transcoded. Can be one of `mp3`, `aac`, `opus`. Which audio codec to use when the audio stream is being transcoded. Can be one of `mp3`, `aac`, `libopus`.
The default value is `aac`. The default value is `aac`.
+2 -2
View File
@@ -24,7 +24,7 @@ Immich has three main clients:
3. CLI - Command-line utility for bulk upload 3. CLI - Command-line utility for bulk upload
:::info :::info
All three clients use [OpenAPI](/api.md) to auto-generate rest clients for easy integration. For more information about this process, see [OpenAPI](/api.md). All three clients use [OpenAPI](./open-api.md) to auto-generate rest clients for easy integration. For more information about this process, see [OpenAPI](./open-api.md).
::: :::
### Mobile App ### Mobile App
@@ -71,7 +71,7 @@ An incoming HTTP request is mapped to a controller (`src/controllers`). Controll
### Domain Transfer Objects (DTOs) ### Domain Transfer Objects (DTOs)
The server uses [Domain Transfer Objects](https://en.wikipedia.org/wiki/Data_transfer_object) as public interfaces for the inputs (query, params, and body) and outputs (response) for each endpoint. DTOs translate to [OpenAPI](/api.md) schemas and control the generated code used by each client. The server uses [Domain Transfer Objects](https://en.wikipedia.org/wiki/Data_transfer_object) as public interfaces for the inputs (query, params, and body) and outputs (response) for each endpoint. DTOs translate to [OpenAPI](./open-api.md) schemas and control the generated code used by each client.
### Background Jobs ### Background Jobs
@@ -1,4 +1,4 @@
# API # OpenAPI
Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generate API documentation. To view the published docs see [here](https://api.immich.app/). Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generate API documentation. To view the published docs see [here](https://api.immich.app/).
+1 -1
View File
@@ -53,7 +53,7 @@ You can use `dart fix --apply` and `dcm fix lib` to potentially correct some iss
## OpenAPI ## OpenAPI
The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/api.md) for more details. The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/developer/open-api.md) for more details.
## Database Migrations ## Database Migrations
-28
View File
@@ -1,28 +0,0 @@
# Duplicates Utility
Immich comes with a duplicates utility to help you detect assets that look visually similar. The duplicate detection feature relies on machine learning and is enabled by default. For more information about when the duplicate detection job runs, see [Jobs and Workers](/administration/jobs-workers). Once an asset has been processed and added to a duplicate group, it becomes available to review in the "Review duplicates" utility, which can be found [here](https://my.immich.app/utilities/duplicates).
## Reviewing duplicates
The review duplicates page allows the user to individually select which assets should be kept and which ones should be trashed. When more than one asset is kept, there is an option to automatically put the kept assets into a stack.
### Automatic preselection
When using "Deduplicate All" or viewing suggestions, Immich automatically preselects which assets to keep based on:
1. **Image size in bytes** — larger files are preferred as they typically have higher quality.
2. **Count of EXIF data** — assets with more metadata are preferred.
### Synchronizing metadata
When resolving duplicates, metadata from trashed assets is automatically synchronized to the kept assets. The following metadata is synchronized:
| Name | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Album | The kept assets will be added to _every_ album that the other assets in the group belong to. |
| Favorite | If any of the assets in the group have been added to favorites, every kept asset will also be added to favorites. |
| Rating | If one or more assets in the duplicate group have a rating, the highest rating is selected and synchronized to the kept assets. |
| Description | Descriptions from each asset are combined together and synchronized to all the kept assets. |
| Visibility | The most restrictive visibility is applied to the kept assets. |
| Location | Latitude and longitude are copied if all assets with geolocation data in the group share the same coordinates. |
| Tag | Tags from all assets in the group are merged and applied to every kept asset. |
-4
View File
@@ -80,10 +80,6 @@ There is an automatic scan job that is scheduled to run once a day. Its schedule
This job also cleans up any libraries stuck in deletion. It is possible to trigger the cleanup by clicking "Scan all libraries" in the library management page. This job also cleans up any libraries stuck in deletion. It is possible to trigger the cleanup by clicking "Scan all libraries" in the library management page.
### Deleting a Library
When deleting an external library, all assets inside are immediately deleted along with the library. Note that while a library can take a long time to fully delete in the background, it is immediately removed from the library list. If the deletion process is interrupted (for example, due to server restart), it will be cleaned up in the next nightly cron job. The cleanup process can also be manually initiated by clicking the "Scan All Libraries" button in the library list.
## Usage ## Usage
Let's show a concrete example where we add an existing gallery to Immich. Here, we have the following folders we want to add: Let's show a concrete example where we add an existing gallery to Immich. Here, we have the following folders we want to add:
@@ -50,7 +50,6 @@ You do not need to redo any machine learning jobs after enabling hardware accele
- The GPU must be supported by ROCm. If it isn't officially supported, you can attempt to use the `HSA_OVERRIDE_GFX_VERSION` environmental variable: `HSA_OVERRIDE_GFX_VERSION=<a supported version, e.g. 10.3.0>`. If this doesn't work, you might need to also set `HSA_USE_SVM=0`. - The GPU must be supported by ROCm. If it isn't officially supported, you can attempt to use the `HSA_OVERRIDE_GFX_VERSION` environmental variable: `HSA_OVERRIDE_GFX_VERSION=<a supported version, e.g. 10.3.0>`. If this doesn't work, you might need to also set `HSA_USE_SVM=0`.
- The ROCm image is quite large and requires at least 35GiB of free disk space. However, pulling later updates to the service through Docker will generally only amount to a few hundred megabytes as the rest will be cached. - The ROCm image is quite large and requires at least 35GiB of free disk space. However, pulling later updates to the service through Docker will generally only amount to a few hundred megabytes as the rest will be cached.
- This backend is new and may experience some issues. For example, GPU power consumption can be higher than usual after running inference, even if the machine learning service is idle. In this case, it will only go back to normal after being idle for 5 minutes (configurable with the [MACHINE_LEARNING_MODEL_TTL](/install/environment-variables) setting). - This backend is new and may experience some issues. For example, GPU power consumption can be higher than usual after running inference, even if the machine learning service is idle. In this case, it will only go back to normal after being idle for 5 minutes (configurable with the [MACHINE_LEARNING_MODEL_TTL](/install/environment-variables) setting).
- MIGraphX is a new backend for AMD cards, which compiles models at runtime. As such, the first few inferences will be slow.
#### OpenVINO #### OpenVINO
+14 -14
View File
@@ -28,17 +28,17 @@ For the full list, refer to the [Immich source code](https://github.com/immich-a
## Video formats ## Video formats
| Format | Extension(s) | Supported? | Notes | | Format | Extension(s) | Supported? | Notes |
| :---------- | :-------------------------- | :----------------: | :---- | | :---------- | :-------------------- | :----------------: | :---- |
| `3GPP` | `.3gp` `.3gpp` | :white_check_mark: | | | `3GPP` | `.3gp` `.3gpp` | :white_check_mark: | |
| `AVI` | `.avi` | :white_check_mark: | | | `AVI` | `.avi` | :white_check_mark: | |
| `FLV` | `.flv` | :white_check_mark: | | | `FLV` | `.flv` | :white_check_mark: | |
| `M4V` | `.m4v` | :white_check_mark: | | | `M4V` | `.m4v` | :white_check_mark: | |
| `MATROSKA` | `.mkv` | :white_check_mark: | | | `MATROSKA` | `.mkv` | :white_check_mark: | |
| `MP2T` | `.mts` `.m2ts` `.m2t` `.ts` | :white_check_mark: | | | `MP2T` | `.mts` `.m2ts` `.m2t` | :white_check_mark: | |
| `MP4` | `.mp4` `.insv` | :white_check_mark: | | | `MP4` | `.mp4` `.insv` | :white_check_mark: | |
| `MPEG` | `.mpg` `.mpe` `.mpeg` | :white_check_mark: | | | `MPEG` | `.mpg` `.mpe` `.mpeg` | :white_check_mark: | |
| `MXF` | `.mxf` | :white_check_mark: | | | `MXF` | `.mxf` | :white_check_mark: | |
| `QUICKTIME` | `.mov` | :white_check_mark: | | | `QUICKTIME` | `.mov` | :white_check_mark: | |
| `WEBM` | `.webm` | :white_check_mark: | | | `WEBM` | `.webm` | :white_check_mark: | |
| `WMV` | `.wmv` | :white_check_mark: | | | `WMV` | `.wmv` | :white_check_mark: | |
+2 -2
View File
@@ -3,8 +3,8 @@
You may decide that you'd like to modify the style document which is used to You may decide that you'd like to modify the style document which is used to
draw the maps in Immich. In addition to visual customization, this also allows draw the maps in Immich. In addition to visual customization, this also allows
you to pick your own map tile provider instead of the default one. The default you to pick your own map tile provider instead of the default one. The default
`style.json` for [light theme](https://tiles.immich.cloud/v1/style/light.json) `style.json` for [light theme](https://github.com/immich-app/immich/tree/main/server/resources/style-light.json)
and [dark theme](https://tiles.immich.cloud/v1/style/dark.json) and [dark theme](https://github.com/immich-app/immich/blob/main/server/resources/style-dark.json)
can be used as a basis for creating your own style. can be used as a basis for creating your own style.
There are several sources for already-made `style.json` map themes, as well as There are several sources for already-made `style.json` map themes, as well as
+1 -1
View File
@@ -27,7 +27,7 @@ The default configuration looks like this:
"ffmpeg": { "ffmpeg": {
"accel": "disabled", "accel": "disabled",
"accelDecode": false, "accelDecode": false,
"acceptedAudioCodecs": ["aac", "mp3", "opus"], "acceptedAudioCodecs": ["aac", "mp3", "libopus"],
"acceptedContainers": ["mov", "ogg", "webm"], "acceptedContainers": ["mov", "ogg", "webm"],
"acceptedVideoCodecs": ["h264"], "acceptedVideoCodecs": ["h264"],
"bframes": -1, "bframes": -1,
+16 -19
View File
@@ -29,23 +29,22 @@ These environment variables are used by the `docker-compose.yml` file and do **N
## General ## General
| Variable | Description | Default | Containers | Workers | | Variable | Description | Default | Containers | Workers |
| :---------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- | | :---------------------------------- | :---------------------------------------------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- |
| `TZ` | Timezone | <sup>\*1</sup> | server | microservices | | `TZ` | Timezone | <sup>\*1</sup> | server | microservices |
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices | | `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
| `IMMICH_LOG_LEVEL` | Log level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices | | `IMMICH_LOG_LEVEL` | Log level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
| `IMMICH_LOG_FORMAT` | Log output format (`console`, `json`) | `console` | server | api, microservices | | `IMMICH_LOG_FORMAT` | Log output format (`console`, `json`) | `console` | server | api, microservices |
| `IMMICH_MEDIA_LOCATION` | Media location inside the container âš ī¸**You probably shouldn't set this**<sup>\*2</sup>âš ī¸ | `/data` | server | api, microservices | | `IMMICH_MEDIA_LOCATION` | Media location inside the container âš ī¸**You probably shouldn't set this**<sup>\*2</sup>âš ī¸ | `/data` | server | api, microservices |
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices | | `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
| `IMMICH_HELMET_FILE` | Path to a json file with [helmet](https://www.npmjs.com/package/helmet) options. Set to `false` to disable. Set to `true` to use `server/helmet.json`. | `false` | server | api, microservices | | `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | | | `CPU_CORES` | Number of cores available to the Immich server | auto-detected CPU core count | server | |
| `CPU_CORES` | Number of cores available to the Immich server | auto-detected CPU core count | server | | | `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api | | `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices |
| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices | | `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices | | `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api |
| `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api | | `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/administration/system-integrity) | | server | api, microservices |
| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/administration/system-integrity) | | server | api, microservices | | `IMMICH_ALLOW_SETUP` | When `false` disables the `/auth/admin-sign-up` endpoint | `true` | server | api |
| `IMMICH_ALLOW_SETUP` | When `false` disables the `/auth/admin-sign-up` endpoint | `true` | server | api |
\*1: `TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`. \*1: `TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`.
`TZ` is used by `exiftool` as a fallback in case the timezone cannot be determined from the image metadata. It is also used for logfile timestamps and cron job execution. `TZ` is used by `exiftool` as a fallback in case the timezone cannot be determined from the image metadata. It is also used for logfile timestamps and cron job execution.
@@ -167,8 +166,6 @@ Redis (Sentinel) URL example JSON before encoding:
| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning | | `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning |
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning | | `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning |
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning | | `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning |
| `MACHINE_LEARNING_PRELOAD__OCR__RECOGNITION` | Comma-separated list of (recognition) OCR model(s) to preload and cache | | machine learning |
| `MACHINE_LEARNING_PRELOAD__OCR__DETECTION` | Comma-separated list of (detection) OCR model(s) to preload and cache | | machine learning |
| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning | | `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning |
| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning | | `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning |
| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning | | `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning |
+1 -5
View File
@@ -8,7 +8,7 @@ Hardware and software requirements for Immich:
## Hardware ## Hardware
- **OS**: Recommended Linux or \*nix 64-bit operating system (Ubuntu, Debian, etc). - **OS**: Recommended Linux or \*nix operating system (Ubuntu, Debian, etc).
- Non-Linux OSes tend to provide a poor Docker experience and are strongly discouraged. - Non-Linux OSes tend to provide a poor Docker experience and are strongly discouraged.
Our ability to assist with setup or troubleshooting on non-Linux OSes will be severely reduced. Our ability to assist with setup or troubleshooting on non-Linux OSes will be severely reduced.
If you still want to try to use a non-Linux OS, you can set it up as follows: If you still want to try to use a non-Linux OS, you can set it up as follows:
@@ -19,10 +19,6 @@ Hardware and software requirements for Immich:
If you have issues, we recommend that you switch to a supported VM deployment. If you have issues, we recommend that you switch to a supported VM deployment.
- **RAM**: Minimum 6GB, recommended 8GB. - **RAM**: Minimum 6GB, recommended 8GB.
- **CPU**: Minimum 2 cores, recommended 4 cores. - **CPU**: Minimum 2 cores, recommended 4 cores.
- Immich runs on the `amd64` and `arm64` platforms.
Since `v2.6`, the machine learning container on `amd64` requires the `>= x86-64-v2` [microarchitecture level](https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels).
Most CPUs released since ~2012 support this microarchitecture.
If you are using a virtual machine, ensure you have selected a [supported microarchitecture](https://pve.proxmox.com/pve-docs/chapter-qm.html#_qemu_cpu_types).
- **Storage**: Recommended Unix-compatible filesystem (EXT4, ZFS, APFS, etc.) with support for user/group ownership and permissions. - **Storage**: Recommended Unix-compatible filesystem (EXT4, ZFS, APFS, etc.) with support for user/group ownership and permissions.
- The generation of thumbnails and transcoded video can increase the size of the photo library by 10-20% on average. - The generation of thumbnails and transcoded video can increase the size of the photo library by 10-20% on average.
+48 -70
View File
@@ -6,7 +6,7 @@ const prism = require('prism-react-renderer');
/** @type {import('@docusaurus/types').Config} */ /** @type {import('@docusaurus/types').Config} */
const config = { const config = {
title: 'Immich', title: 'Immich',
tagline: 'Self-hosted photo and video management solution', tagline: 'High performance self-hosted photo and video backup solution directly from your mobile phone',
url: 'https://docs.immich.app', url: 'https://docs.immich.app',
baseUrl: '/', baseUrl: '/',
onBrokenLinks: 'throw', onBrokenLinks: 'throw',
@@ -93,15 +93,35 @@ const config = {
position: 'right', position: 'right',
}, },
{ {
href: 'https://immich.app/', to: '/overview/quick-start',
position: 'right', position: 'right',
label: 'Home', label: 'Docs',
},
{
href: 'https://immich.app/roadmap',
position: 'right',
label: 'Roadmap',
},
{
href: 'https://api.immich.app/',
position: 'right',
label: 'API',
},
{
href: 'https://immich.store',
position: 'right',
label: 'Merch',
}, },
{ {
href: 'https://github.com/immich-app/immich', href: 'https://github.com/immich-app/immich',
label: 'GitHub', label: 'GitHub',
position: 'right', position: 'right',
}, },
{
href: 'https://discord.immich.app',
label: 'Discord',
position: 'right',
},
{ {
type: 'html', type: 'html',
position: 'right', position: 'right',
@@ -114,78 +134,19 @@ const config = {
style: 'light', style: 'light',
links: [ links: [
{ {
title: 'Download', title: 'Overview',
items: [ items: [
{ {
label: 'Android', label: 'Quick start',
href: 'https://get.immich.app/android', to: '/overview/quick-start',
}, },
{ {
label: 'iOS', label: 'Installation',
href: 'https://get.immich.app/ios', to: '/install/requirements',
}, },
{ {
label: 'Server', label: 'Contributing',
href: 'https://immich.app/download', to: '/overview/support-the-project',
},
],
},
{
title: 'Company',
items: [
{
label: 'FUTO',
href: 'https://futo.tech/',
},
{
label: 'Purchase',
href: 'https://buy.immich.app/',
},
{
label: 'Merch',
href: 'https://immich.store/',
},
],
},
{
title: 'Sites',
items: [
{
label: 'Home',
href: 'https://immich.app',
},
{
label: 'My Immich',
href: 'https://my.immich.app/',
},
{
label: 'Awesome Immich',
href: 'https://awesome.immich.app/',
},
{
label: 'Immich API',
href: 'https://api.immich.app/',
},
{
label: 'Immich Data',
href: 'https://data.immich.app/',
},
{
label: 'Immich Datasets',
href: 'https://datasets.immich.app/',
},
],
},
{
title: 'Miscellaneous',
items: [
{
label: 'Roadmap',
href: 'https://immich.app/roadmap',
},
{
label: 'Cursed Knowledge',
href: 'https://immich.app/cursed-knowledge',
}, },
{ {
label: 'Privacy Policy', label: 'Privacy Policy',
@@ -194,7 +155,24 @@ const config = {
], ],
}, },
{ {
title: 'Social', title: 'Documentation',
items: [
{
label: 'Roadmap',
href: 'https://immich.app/roadmap',
},
{
label: 'API',
href: 'https://api.immich.app/',
},
{
label: 'Cursed Knowledge',
href: 'https://immich.app/cursed-knowledge',
},
],
},
{
title: 'Links',
items: [ items: [
{ {
label: 'GitHub', label: 'GitHub',
+4 -4
View File
@@ -4,11 +4,11 @@
"private": true, "private": true,
"scripts": { "scripts": {
"docusaurus": "docusaurus", "docusaurus": "docusaurus",
"format": "prettier --cache --check .", "format": "prettier --check .",
"format:fix": "prettier --cache --write --list-different .", "format:fix": "prettier --write .",
"start": "docusaurus start --port 3005", "start": "docusaurus start --port 3005",
"copy:openapi": "jq -c < ../open-api/immich-openapi-specs.json > ./static/openapi.json || exit 0", "copy:openapi": "jq -c < ../open-api/immich-openapi-specs.json > ./static/openapi.json || exit 0",
"build": "pnpm run copy:openapi && docusaurus build", "build": "npm run copy:openapi && docusaurus build",
"swizzle": "docusaurus swizzle", "swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy", "deploy": "docusaurus deploy",
"clear": "docusaurus clear", "clear": "docusaurus clear",
@@ -58,6 +58,6 @@
"node": ">=20" "node": ">=20"
}, },
"volta": { "volta": {
"node": "24.14.1" "node": "24.13.1"
} }
} }
-1
View File
@@ -23,7 +23,6 @@
/features/storage-template /administration/storage-template 307 /features/storage-template /administration/storage-template 307
/features/user-management /administration/user-management 307 /features/user-management /administration/user-management 307
/developer/contributing /developer/pr-checklist 307 /developer/contributing /developer/pr-checklist 307
/developer/open-api /api 307
/guides/machine-learning /guides/remote-machine-learning 307 /guides/machine-learning /guides/remote-machine-learning 307
/administration/password-login /administration/system-settings 307 /administration/password-login /administration/system-settings 307
/features/search /features/searching 307 /features/search /features/searching 307
-4
View File
@@ -1,8 +1,4 @@
[ [
{
"label": "v2.6.3",
"url": "https://docs.v2.6.3.archive.immich.app"
},
{ {
"label": "v2.5.6", "label": "v2.5.6",
"url": "https://docs.v2.5.6.archive.immich.app" "url": "https://docs.v2.5.6.archive.immich.app"
+4 -25
View File
@@ -10,7 +10,6 @@ export enum OAuthClient {
export enum OAuthUser { export enum OAuthUser {
NO_EMAIL = 'no-email', NO_EMAIL = 'no-email',
NO_NAME = 'no-name', NO_NAME = 'no-name',
ID_TOKEN_CLAIMS = 'id-token-claims',
WITH_QUOTA = 'with-quota', WITH_QUOTA = 'with-quota',
WITH_USERNAME = 'with-username', WITH_USERNAME = 'with-username',
WITH_ROLE = 'with-role', WITH_ROLE = 'with-role',
@@ -53,25 +52,12 @@ const withDefaultClaims = (sub: string) => ({
email_verified: true, email_verified: true,
}); });
const getClaims = (sub: string, use?: string) => { const getClaims = (sub: string) => claims.find((user) => user.sub === sub) || withDefaultClaims(sub);
if (sub === OAuthUser.ID_TOKEN_CLAIMS) {
return {
sub,
email: `oauth-${sub}@immich.app`,
email_verified: true,
name: use === 'id_token' ? 'ID Token User' : 'Userinfo User',
};
}
return claims.find((user) => user.sub === sub) || withDefaultClaims(sub);
};
const setup = async () => { const setup = async () => {
const { privateKey, publicKey } = await generateKeyPair('RS256'); const { privateKey, publicKey } = await generateKeyPair('RS256');
const redirectUris = [ const redirectUris = ['http://127.0.0.1:2285/auth/login', 'https://photos.immich.app/oauth/mobile-redirect'];
'http://127.0.0.1:2285/auth/login',
'https://photos.immich.app/oauth/mobile-redirect',
];
const port = 2286; const port = 2286;
const host = '0.0.0.0'; const host = '0.0.0.0';
const oidc = new Provider(`http://${host}:${port}`, { const oidc = new Provider(`http://${host}:${port}`, {
@@ -80,10 +66,7 @@ const setup = async () => {
console.error(error); console.error(error);
ctx.body = 'Internal Server Error'; ctx.body = 'Internal Server Error';
}, },
findAccount: (ctx, sub) => ({ findAccount: (ctx, sub) => ({ accountId: sub, claims: () => getClaims(sub) }),
accountId: sub,
claims: (use) => getClaims(sub, use),
}),
scopes: ['openid', 'email', 'profile'], scopes: ['openid', 'email', 'profile'],
claims: { claims: {
openid: ['sub'], openid: ['sub'],
@@ -111,7 +94,6 @@ const setup = async () => {
state: 'oidc.state', state: 'oidc.state',
}, },
}, },
conformIdTokenClaims: false,
pkce: { pkce: {
required: () => false, required: () => false,
}, },
@@ -143,10 +125,7 @@ const setup = async () => {
], ],
}); });
const onStart = () => const onStart = () => console.log(`[e2e-auth-server] http://${host}:${port}/.well-known/openid-configuration`);
console.log(
`[e2e-auth-server] http://${host}:${port}/.well-known/openid-configuration`,
);
const app = oidc.listen(port, host, onStart); const app = oidc.listen(port, host, onStart);
return () => app.close(); return () => app.close();
}; };
+1 -1
View File
@@ -1 +1 @@
24.14.1 24.13.1
+1 -1
View File
@@ -44,7 +44,7 @@ services:
redis: redis:
container_name: immich-e2e-redis container_name: immich-e2e-redis
image: docker.io/valkey/valkey:9@sha256:3eeb09785cd61ec8e3be35f8804c8892080f3ca21934d628abc24ee4ed1698f6 image: docker.io/valkey/valkey:9@sha256:930b41430fb727f533c5982fe509b6f04233e26d0f7354e04de4b0d5c706e44e
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
+19 -20
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "2.6.3", "version": "2.5.6",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
@@ -8,41 +8,41 @@
"test": "vitest --run", "test": "vitest --run",
"test:watch": "vitest", "test:watch": "vitest",
"test:maintenance": "vitest --run --config vitest.maintenance.config.ts", "test:maintenance": "vitest --run --config vitest.maintenance.config.ts",
"test:web": "pnpm exec playwright test --project=web", "test:web": "npx playwright test --project=web",
"test:web:maintenance": "pnpm exec playwright test --project=maintenance", "test:web:maintenance": "npx playwright test --project=maintenance",
"test:web:ui": "pnpm exec playwright test --project=ui", "test:web:ui": "npx playwright test --project=ui",
"start:web": "pnpm exec playwright test --ui --project=web", "start:web": "npx playwright test --ui --project=web",
"start:web:maintenance": "pnpm exec playwright test --ui --project=maintenance", "start:web:maintenance": "npx playwright test --ui --project=maintenance",
"start:web:ui": "pnpm exec playwright test --ui --project=ui", "start:web:ui": "npx playwright test --ui --project=ui",
"format": "prettier --cache --check .", "format": "prettier --check .",
"format:fix": "prettier --cache --write --list-different .", "format:fix": "prettier --write .",
"lint": "eslint \"src/**/*.ts\" --max-warnings 0", "lint": "eslint \"src/**/*.ts\" --max-warnings 0",
"lint:fix": "pnpm run lint --fix", "lint:fix": "npm run lint -- --fix",
"check": "tsc --noEmit" "check": "tsc --noEmit"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.0", "@eslint/js": "^9.8.0",
"@faker-js/faker": "^10.1.0", "@faker-js/faker": "^10.1.0",
"@immich/cli": "workspace:*", "@immich/cli": "workspace:*",
"@immich/e2e-auth-server": "workspace:*", "@immich/e2e-auth-server": "workspace:*",
"@immich/sdk": "workspace:*", "@immich/sdk": "workspace:*",
"@playwright/test": "^1.44.1", "@playwright/test": "^1.44.1",
"@socket.io/component-emitter": "^3.1.2", "@socket.io/component-emitter": "^3.1.2",
"@types/luxon": "^3.4.2", "@types/luxon": "^3.4.2",
"@types/node": "^24.12.0", "@types/node": "^24.10.13",
"@types/pg": "^8.15.1", "@types/pg": "^8.15.1",
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"eslint": "^10.0.0", "eslint": "^9.14.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^63.0.0", "eslint-plugin-unicorn": "^62.0.0",
"exiftool-vendored": "^35.0.0", "exiftool-vendored": "^34.3.0",
"globals": "^17.0.0", "globals": "^16.0.0",
"luxon": "^3.4.4", "luxon": "^3.4.4",
"pg": "^8.11.3", "pg": "^8.11.3",
"pngjs": "^7.0.0", "pngjs": "^7.0.0",
@@ -54,10 +54,9 @@
"typescript": "^5.3.3", "typescript": "^5.3.3",
"typescript-eslint": "^8.28.0", "typescript-eslint": "^8.28.0",
"utimes": "^5.2.1", "utimes": "^5.2.1",
"vite-tsconfig-paths": "^6.1.1", "vitest": "^3.0.0"
"vitest": "^4.0.0"
}, },
"volta": { "volta": {
"node": "24.14.1" "node": "24.13.1"
} }
} }
-651
View File
@@ -1,651 +0,0 @@
import { LoginResponseDto } from '@immich/sdk';
import { createUserDto, uuidDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { app, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
describe('/duplicates', () => {
let admin: LoginResponseDto;
let user1: LoginResponseDto;
let user2: LoginResponseDto;
beforeAll(async () => {
await utils.resetDatabase();
admin = await utils.adminSetup();
[user1, user2] = await Promise.all([
utils.userSetup(admin.accessToken, createUserDto.user1),
utils.userSetup(admin.accessToken, createUserDto.user2),
]);
});
beforeEach(async () => {
// Reset assets, albums, tags, and stacks between tests to ensure clean state for repeated test runs
// Note: We don't reset users since they're set up once in beforeAll
// Stack must be reset before asset due to foreign key constraint
await utils.resetDatabase(['stack', 'asset', 'album', 'tag']);
});
describe('GET /duplicates', () => {
it('should return empty array when no duplicates', async () => {
const { status, body } = await request(app)
.get('/duplicates')
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual([]);
});
it('should return duplicate groups with suggestedKeepAssetIds', async () => {
// Create assets with different file sizes for duplicate detection
const [asset1, asset2] = await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
// Manually set duplicateId on both assets to create a duplicate group
const duplicateId = '00000000-0000-4000-8000-000000000001';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId);
await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId);
const { status, body } = await request(app)
.get('/duplicates')
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual([
{
duplicateId,
assets: expect.arrayContaining([
expect.objectContaining({ id: asset1.id }),
expect.objectContaining({ id: asset2.id }),
]),
suggestedKeepAssetIds: expect.any(Array),
},
]);
expect(body[0].suggestedKeepAssetIds.length).toBe(1);
});
});
describe('POST /duplicates/resolve', () => {
it('should require authentication', async () => {
const { status, body } = await request(app)
.post('/duplicates/resolve')
.send({
groups: [{ duplicateId: uuidDto.dummy, keepAssetIds: [], trashAssetIds: [] }],
});
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should return failure for non-existent duplicate group', async () => {
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [{ duplicateId: uuidDto.dummy, keepAssetIds: [], trashAssetIds: [] }],
});
expect(status).toBe(200);
expect(body).toEqual({
status: 'COMPLETED',
results: [
{
duplicateId: uuidDto.dummy,
status: 'FAILED',
reason: expect.stringContaining('not found or access denied'),
},
],
});
});
it('should resolve duplicate group with keepers', async () => {
const [asset1, asset2] = await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
const duplicateId = '00000000-0000-4000-8000-000000000002';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId);
await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId);
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }],
});
expect(status).toBe(200);
expect(body).toEqual({
status: 'COMPLETED',
results: [
{
duplicateId,
status: 'SUCCESS',
},
],
});
// Verify side effects: duplicateId cleared on kept asset
const keptAsset = await utils.getAssetInfo(user1.accessToken, asset1.id);
expect(keptAsset.duplicateId).toBeNull();
// Verify side effects: trashed asset is trashed and duplicateId cleared
const trashedAsset = await utils.getAssetInfo(user1.accessToken, asset2.id);
expect(trashedAsset.isTrashed).toBe(true);
expect(trashedAsset.duplicateId).toBeNull();
});
it('should reject when keepAssetIds and trashAssetIds overlap', async () => {
const [asset1, asset2] = await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
const duplicateId = '00000000-0000-4000-8000-000000000003';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId);
await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId);
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset1.id] }],
});
expect(status).toBe(200);
expect(body.results[0].status).toBe('FAILED');
expect(body.results[0].reason).toContain('disjoint');
});
it('should require keepAssetIds when partially trashing', async () => {
const [asset1, asset2] = await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
const duplicateId = '00000000-0000-4000-8000-000000000004';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId);
await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId);
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [{ duplicateId, keepAssetIds: [], trashAssetIds: [asset1.id] }],
});
expect(status).toBe(200);
expect(body.results[0].status).toBe('FAILED');
expect(body.results[0].reason).toContain('must cover all assets');
});
it('should reject partial resolution (not all assets covered)', async () => {
const [asset1, asset2, asset3] = await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
const duplicateId = '00000000-0000-4000-8000-000000000010';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId);
await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId);
await utils.setAssetDuplicateId(user1.accessToken, asset3.id, duplicateId);
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }],
});
expect(status).toBe(200);
expect(body.results[0].status).toBe('FAILED');
expect(body.results[0].reason).toContain('must cover all assets');
});
it('should reject asset not in duplicate group', async () => {
const [asset1, asset2, outsideAsset] = await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
const duplicateId = '00000000-0000-4000-8000-000000000011';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId);
await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId);
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [outsideAsset.id] }],
});
expect(status).toBe(200);
expect(body.results[0].status).toBe('FAILED');
expect(body.results[0].reason).toContain('not a member of duplicate group');
});
it('should allow trash-all without keepers', async () => {
const [asset1, asset2] = await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
const duplicateId = '00000000-0000-4000-8000-000000000012';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId);
await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId);
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [{ duplicateId, keepAssetIds: [], trashAssetIds: [asset1.id, asset2.id] }],
});
expect(status).toBe(200);
expect(body).toEqual({
status: 'COMPLETED',
results: [
{
duplicateId,
status: 'SUCCESS',
},
],
});
// Verify both assets are trashed
const [asset1Info, asset2Info] = await Promise.all([
utils.getAssetInfo(user1.accessToken, asset1.id),
utils.getAssetInfo(user1.accessToken, asset2.id),
]);
expect(asset1Info.isTrashed).toBe(true);
expect(asset1Info.duplicateId).toBeNull();
expect(asset2Info.isTrashed).toBe(true);
expect(asset2Info.duplicateId).toBeNull();
});
it('should reject cross-user duplicate group access', async () => {
const asset1 = await utils.createAsset(user1.accessToken);
const asset2 = await utils.createAsset(user2.accessToken);
const duplicateId = '00000000-0000-4000-8000-000000000013';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId);
await utils.setAssetDuplicateId(user2.accessToken, asset2.id, duplicateId);
// User1 tries to resolve a group containing user2's asset
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }],
});
expect(status).toBe(200);
expect(body.results[0].status).toBe('FAILED');
expect(body.results[0].reason).toContain('not a member of duplicate group');
});
it('should synchronize favorites when enabled', async () => {
const [asset1, asset2] = await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
// Mark one asset as favorite
await request(app)
.put('/assets')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ ids: [asset2.id], isFavorite: true });
const duplicateId = '00000000-0000-4000-8000-000000000020';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId);
await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId);
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }],
});
expect(status).toBe(200);
expect(body.results[0].status).toBe('SUCCESS');
// Verify favorite was synchronized to keeper
const keptAsset = await utils.getAssetInfo(user1.accessToken, asset1.id);
expect(keptAsset.isFavorite).toBe(true);
expect(keptAsset.duplicateId).toBeNull();
});
it('should synchronize visibility when enabled', async () => {
const [asset1, asset2] = await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
// Archive one asset
await utils.archiveAssets(user1.accessToken, [asset2.id]);
const duplicateId = '00000000-0000-4000-8000-000000000021';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId);
await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId);
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }],
});
expect(status).toBe(200);
expect(body.results[0].status).toBe('SUCCESS');
// Verify visibility was synchronized to keeper
const keptAsset = await utils.getAssetInfo(user1.accessToken, asset1.id);
expect(keptAsset.visibility).toBe('archive');
expect(keptAsset.duplicateId).toBeNull();
});
it('should synchronize rating when enabled', async () => {
const [asset1, asset2] = await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
// Set rating on one asset
await request(app)
.put('/assets')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ ids: [asset2.id], rating: 5 });
const duplicateId = '00000000-0000-4000-8000-000000000022';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId);
await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId);
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }],
});
expect(status).toBe(200);
expect(body.results[0].status).toBe('SUCCESS');
// Verify rating was synchronized to keeper
const keptAsset = await utils.getAssetInfo(user1.accessToken, asset1.id);
expect(keptAsset.exifInfo?.rating).toBe(5);
expect(keptAsset.duplicateId).toBeNull();
});
it('should synchronize description when enabled', async () => {
const [asset1, asset2] = await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
// Set description on one asset
await request(app)
.put('/assets')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ ids: [asset2.id], description: 'Test description for duplicate' });
const duplicateId = '00000000-0000-4000-8000-000000000023';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId);
await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId);
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }],
});
expect(status).toBe(200);
expect(body.results[0].status).toBe('SUCCESS');
// Verify description was synchronized to keeper
const keptAsset = await utils.getAssetInfo(user1.accessToken, asset1.id);
expect(keptAsset.exifInfo?.description).toBe('Test description for duplicate');
expect(keptAsset.duplicateId).toBeNull();
});
it('should synchronize location when enabled', async () => {
const [asset1, asset2] = await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
// Set location on one asset
await request(app)
.put('/assets')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ ids: [asset2.id], latitude: 40.7128, longitude: -74.006 });
const duplicateId = '00000000-0000-4000-8000-000000000024';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId);
await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId);
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }],
});
expect(status).toBe(200);
expect(body.results[0].status).toBe('SUCCESS');
// Verify location was synchronized to keeper
const keptAsset = await utils.getAssetInfo(user1.accessToken, asset1.id);
expect(keptAsset.exifInfo?.latitude).toBe(40.7128);
expect(keptAsset.exifInfo?.longitude).toBe(-74.006);
expect(keptAsset.duplicateId).toBeNull();
});
it('should synchronize albums when enabled', async () => {
const [asset1, asset2] = await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
// Create albums and add assets to different albums
const album1 = await utils.createAlbum(user1.accessToken, {
albumName: 'Album 1',
assetIds: [asset1.id],
});
const album2 = await utils.createAlbum(user1.accessToken, {
albumName: 'Album 2',
assetIds: [asset2.id],
});
const duplicateId = '00000000-0000-4000-8000-000000000025';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId);
await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId);
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }],
});
expect(status).toBe(200);
expect(body.results[0].status).toBe('SUCCESS');
// Verify keeper is now in both albums
const keptAsset = await utils.getAssetInfo(user1.accessToken, asset1.id);
expect(keptAsset.duplicateId).toBeNull();
// Check albums directly
const { status: album1Status, body: album1Body } = await request(app)
.get(`/albums/${album1.id}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
const { status: album2Status, body: album2Body } = await request(app)
.get(`/albums/${album2.id}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(album1Status).toBe(200);
expect(album2Status).toBe(200);
expect(album1Body.assets.map((a: any) => a.id)).toContain(asset1.id);
expect(album2Body.assets.map((a: any) => a.id)).toContain(asset1.id);
});
it('should synchronize tags when enabled', async () => {
const [asset1, asset2] = await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
// Wait for metadata extraction to complete before adding tags
// Otherwise, metadata jobs will race and overwrite our tags
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
// Create tags and tag assets differently
const tags = await utils.upsertTags(user1.accessToken, ['tag1', 'tag2']);
await utils.tagAssets(user1.accessToken, tags[0].id, [asset1.id]);
await utils.tagAssets(user1.accessToken, tags[1].id, [asset2.id]);
const duplicateId = '00000000-0000-4000-8000-000000000026';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId);
await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId);
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }],
});
expect(status).toBe(200);
expect(body.results[0].status).toBe('SUCCESS');
// Verify keeper has both tags
const keptAsset = await utils.getAssetInfo(user1.accessToken, asset1.id);
expect(keptAsset.duplicateId).toBeNull();
expect(keptAsset.tags).toBeDefined();
const tagIds = keptAsset.tags?.map((t) => t.id) || [];
expect(tagIds).toContain(tags[0].id);
expect(tagIds).toContain(tags[1].id);
});
it('should handle batch resolve with mixed success and failure', async () => {
// Create first group that will succeed
const [asset1, asset2] = await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
const duplicateId1 = '00000000-0000-4000-8000-000000000027';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId1);
await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId1);
// Create second group with non-existent duplicate ID (will fail)
const fakeId = '00000000-0000-4000-8000-000000000099';
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [
{ duplicateId: duplicateId1, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] },
{ duplicateId: fakeId, keepAssetIds: [], trashAssetIds: [] },
],
});
expect(status).toBe(200);
expect(body.status).toBe('COMPLETED');
expect(body.results).toHaveLength(2);
// First group should succeed
expect(body.results[0].duplicateId).toBe(duplicateId1);
expect(body.results[0].status).toBe('SUCCESS');
// Second group should fail
expect(body.results[1].duplicateId).toBe(fakeId);
expect(body.results[1].status).toBe('FAILED');
expect(body.results[1].reason).toContain('not found or access denied');
// Verify first group was actually resolved despite second failure
const asset1Info = await utils.getAssetInfo(user1.accessToken, asset1.id);
expect(asset1Info.duplicateId).toBeNull();
const asset2Info = await utils.getAssetInfo(user1.accessToken, asset2.id);
expect(asset2Info.isTrashed).toBe(true);
});
it('should trash assets when trash is enabled', async () => {
const [asset1, asset2] = await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
const duplicateId = '00000000-0000-4000-8000-000000000028';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId);
await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId);
// Ensure trash is enabled (default)
const config = await utils.getSystemConfig(admin.accessToken);
expect(config.trash.enabled).toBe(true);
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }],
});
expect(status).toBe(200);
expect(body.results[0].status).toBe('SUCCESS');
// Verify asset is trashed (not deleted)
const trashedAsset = await utils.getAssetInfo(user1.accessToken, asset2.id);
expect(trashedAsset.isTrashed).toBe(true);
});
it('should delete assets when trash is disabled', async () => {
const [asset1, asset2] = await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
const duplicateId = '00000000-0000-4000-8000-000000000029';
await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId);
await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId);
// Disable trash
await request(app)
.put('/system-config')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({
trash: { enabled: false, days: 30 },
});
const { status, body } = await request(app)
.post('/duplicates/resolve')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }],
});
expect(status).toBe(200);
expect(body.results[0].status).toBe('SUCCESS');
// Asset should be marked as deleted (force delete)
const { status: getStatus } = await request(app)
.get(`/assets/${asset2.id}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
// Asset should still be accessible (soft deleted) but marked as deleted
expect(getStatus).toBe(200);
// Re-enable trash for other tests
await utils.resetAdminConfig(admin.accessToken);
});
});
});
-2
View File
@@ -2,8 +2,6 @@ export const uuidDto = {
invalid: 'invalid-uuid', invalid: 'invalid-uuid',
// valid uuid v4 // valid uuid v4
notFound: '00000000-0000-4000-a000-000000000000', notFound: '00000000-0000-4000-a000-000000000000',
dummy: '00000000-0000-4000-a000-000000000001',
dummy2: '00000000-0000-4000-a000-000000000002',
}; };
const adminLoginDto = { const adminLoginDto = {
@@ -10,9 +10,7 @@ describe('/admin/database-backups', () => {
beforeAll(async () => { beforeAll(async () => {
await utils.resetDatabase(); await utils.resetDatabase();
admin = await utils.adminSetup({ admin = await utils.adminSetup();
onboarding: false,
});
await utils.resetBackups(admin.accessToken); await utils.resetBackups(admin.accessToken);
}); });
@@ -96,9 +94,7 @@ describe('/admin/database-backups', () => {
({ status, body }) => status === 200 && !body.maintenanceMode, ({ status, body }) => status === 200 && !body.maintenanceMode,
); );
admin = await utils.adminSetup({ admin = await utils.adminSetup();
onboarding: false,
});
}); });
it.sequential('should not work when the server is configured', async () => { it.sequential('should not work when the server is configured', async () => {
+3 -8
View File
@@ -524,19 +524,14 @@ describe('/albums', () => {
expect(body).toEqual(errorDto.badRequest('Not found or no album.update access')); expect(body).toEqual(errorDto.badRequest('Not found or no album.update access'));
}); });
it('should be able to update as an editor', async () => { it('should not be able to update as an editor', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.patch(`/albums/${user1Albums[0].id}`) .patch(`/albums/${user1Albums[0].id}`)
.set('Authorization', `Bearer ${user2.accessToken}`) .set('Authorization', `Bearer ${user2.accessToken}`)
.send({ albumName: 'New album name' }); .send({ albumName: 'New album name' });
expect(status).toBe(200); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest('Not found or no album.update access'));
expect.objectContaining({
id: user1Albums[0].id,
albumName: 'New album name',
}),
);
}); });
}); });
@@ -380,23 +380,4 @@ describe(`/oauth`, () => {
}); });
}); });
}); });
describe('idTokenClaims', () => {
it('should use claims from the ID token if IDP includes them', async () => {
await setupOAuth(admin.accessToken, {
enabled: true,
clientId: OAuthClient.DEFAULT,
clientSecret: OAuthClient.DEFAULT,
});
const callbackParams = await loginWithOAuth(OAuthUser.ID_TOKEN_CLAIMS);
const { status, body } = await request(app).post('/oauth/callback').send(callbackParams);
expect(status).toBe(201);
expect(body).toMatchObject({
accessToken: expect.any(String),
name: 'ID Token User',
userEmail: 'oauth-id-token-claims@immich.app',
userId: expect.any(String),
});
});
});
}); });
@@ -438,16 +438,6 @@ describe('/shared-links', () => {
expect(body).toEqual(errorDto.badRequest('Invalid shared link type')); expect(body).toEqual(errorDto.badRequest('Invalid shared link type'));
}); });
it('should reject guests removing assets from an individual shared link', async () => {
const { status, body } = await request(app)
.delete(`/shared-links/${linkWithAssets.id}/assets`)
.query({ key: linkWithAssets.key })
.send({ assetIds: [asset1.id] });
expect(status).toBe(403);
expect(body).toEqual(errorDto.forbidden);
});
it('should remove assets from a shared link (individual)', async () => { it('should remove assets from a shared link (individual)', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.delete(`/shared-links/${linkWithAssets.id}/assets`) .delete(`/shared-links/${linkWithAssets.id}/assets`)
+2 -40
View File
@@ -1,7 +1,6 @@
import { LoginResponseDto } from '@immich/sdk'; import { LoginResponseDto } from '@immich/sdk';
import { expect, test } from '@playwright/test'; import { test } from '@playwright/test';
import { readFileSync } from 'node:fs'; import { utils } from 'src/utils';
import { testAssetDir, utils } from 'src/utils';
test.describe('Album', () => { test.describe('Album', () => {
let admin: LoginResponseDto; let admin: LoginResponseDto;
@@ -23,41 +22,4 @@ test.describe('Album', () => {
await page.reload(); await page.reload();
await page.getByRole('button', { name: 'Select photos' }).waitFor(); await page.getByRole('button', { name: 'Select photos' }).waitFor();
}); });
test('should keep map view open after viewing an asset from the map and going back', async ({ context, page }) => {
await utils.setAuthCookies(context, admin.accessToken);
const imagePath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`;
const mapAsset = await utils.createAsset(admin.accessToken, {
assetData: {
bytes: readFileSync(imagePath),
filename: 'thompson-springs.jpg',
},
});
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
const mapAlbum = await utils.createAlbum(admin.accessToken, {
albumName: 'Map Test Album',
assetIds: [mapAsset.id],
});
await page.goto(`/albums/${mapAlbum.id}`);
const mapButton = page.getByRole('button', { name: 'Map' });
await expect(mapButton).toBeVisible();
await mapButton.click();
const mapModal = page.getByRole('dialog');
await expect(mapModal).toBeVisible();
const mapMarker = mapModal.getByRole('img', { name: /Map marker/i }).first();
await expect(mapMarker).toBeVisible();
await mapMarker.click();
await page.waitForSelector('#immich-asset-viewer');
await page.getByRole('button', { name: 'Go back' }).click();
await expect(page.locator('#immich-asset-viewer')).not.toBeVisible();
await expect(mapModal).toBeVisible();
});
}); });
@@ -0,0 +1,66 @@
import { AssetMediaResponseDto, LoginResponseDto } from '@immich/sdk';
import { expect, Page, test } from '@playwright/test';
import { utils } from 'src/utils';
async function ensureDetailPanelVisible(page: Page) {
await page.waitForSelector('#immich-asset-viewer');
const isVisible = await page.locator('#detail-panel').isVisible();
if (!isVisible) {
await page.keyboard.press('i');
await page.waitForSelector('#detail-panel');
}
}
test.describe('Asset Viewer stack', () => {
let admin: LoginResponseDto;
let assetOne: AssetMediaResponseDto;
let assetTwo: AssetMediaResponseDto;
test.beforeAll(async () => {
utils.initSdk();
await utils.resetDatabase();
admin = await utils.adminSetup();
await utils.updateMyPreferences(admin.accessToken, { tags: { enabled: true } });
assetOne = await utils.createAsset(admin.accessToken);
assetTwo = await utils.createAsset(admin.accessToken);
await utils.createStack(admin.accessToken, [assetOne.id, assetTwo.id]);
const tags = await utils.upsertTags(admin.accessToken, ['test/1', 'test/2']);
const tagOne = tags.find((tag) => tag.value === 'test/1')!;
const tagTwo = tags.find((tag) => tag.value === 'test/2')!;
await utils.tagAssets(admin.accessToken, tagOne.id, [assetOne.id]);
await utils.tagAssets(admin.accessToken, tagTwo.id, [assetTwo.id]);
});
test('stack slideshow is visible', async ({ page, context }) => {
await utils.setAuthCookies(context, admin.accessToken);
await page.goto(`/photos/${assetOne.id}`);
const stackAssets = page.locator('#stack-slideshow [data-asset]');
await expect(stackAssets.first()).toBeVisible();
await expect(stackAssets.nth(1)).toBeVisible();
});
test('tags of primary asset are visible', async ({ page, context }) => {
await utils.setAuthCookies(context, admin.accessToken);
await page.goto(`/photos/${assetOne.id}`);
await ensureDetailPanelVisible(page);
const tags = page.getByTestId('detail-panel-tags').getByRole('link');
await expect(tags.first()).toHaveText('test/1');
});
test('tags of second asset are visible', async ({ page, context }) => {
await utils.setAuthCookies(context, admin.accessToken);
await page.goto(`/photos/${assetOne.id}`);
await ensureDetailPanelVisible(page);
const stackAssets = page.locator('#stack-slideshow [data-asset]');
await stackAssets.nth(1).click();
const tags = page.getByTestId('detail-panel-tags').getByRole('link');
await expect(tags.first()).toHaveText('test/2');
});
});
-51
View File
@@ -1,51 +0,0 @@
import { AssetMediaResponseDto, LoginResponseDto, updateAssets } from '@immich/sdk';
import { expect, test } from '@playwright/test';
import crypto from 'node:crypto';
import { asBearerAuth, utils } from 'src/utils';
test.describe('Duplicates Utility', () => {
let admin: LoginResponseDto;
let firstAsset: AssetMediaResponseDto;
let secondAsset: AssetMediaResponseDto;
test.beforeAll(async () => {
utils.initSdk();
await utils.resetDatabase();
admin = await utils.adminSetup();
});
test.beforeEach(async ({ context }) => {
[firstAsset, secondAsset] = await Promise.all([
utils.createAsset(admin.accessToken, { deviceAssetId: 'duplicate-a' }),
utils.createAsset(admin.accessToken, { deviceAssetId: 'duplicate-b' }),
]);
await updateAssets(
{
assetBulkUpdateDto: {
ids: [firstAsset.id, secondAsset.id],
duplicateId: crypto.randomUUID(),
},
},
{ headers: asBearerAuth(admin.accessToken) },
);
await utils.setAuthCookies(context, admin.accessToken);
});
test('navigates with arrow keys between duplicate preview assets', async ({ page }) => {
await page.goto('/utilities/duplicates');
await page.getByRole('button', { name: 'View' }).first().click();
await page.waitForSelector('#immich-asset-viewer');
const getViewedAssetId = () => new URL(page.url()).pathname.split('/').at(-1) ?? '';
const initialAssetId = getViewedAssetId();
expect([firstAsset.id, secondAsset.id]).toContain(initialAssetId);
await page.keyboard.press('ArrowRight');
await expect.poll(getViewedAssetId).not.toBe(initialAssetId);
await page.keyboard.press('ArrowLeft');
await expect.poll(getViewedAssetId).toBe(initialAssetId);
});
});
+19 -57
View File
@@ -1,13 +1,14 @@
import { AssetMediaResponseDto, LoginResponseDto } from '@immich/sdk'; import { AssetMediaResponseDto, LoginResponseDto } from '@immich/sdk';
import { expect, test } from '@playwright/test'; import { Page, expect, test } from '@playwright/test';
import type { Socket } from 'socket.io-client';
import { utils } from 'src/utils'; import { utils } from 'src/utils';
function imageLocator(page: Page) {
return page.getByAltText('Image taken').locator('visible=true');
}
test.describe('Photo Viewer', () => { test.describe('Photo Viewer', () => {
let admin: LoginResponseDto; let admin: LoginResponseDto;
let asset: AssetMediaResponseDto; let asset: AssetMediaResponseDto;
let rawAsset: AssetMediaResponseDto; let rawAsset: AssetMediaResponseDto;
let websocket: Socket;
test.beforeAll(async () => { test.beforeAll(async () => {
utils.initSdk(); utils.initSdk();
@@ -15,11 +16,6 @@ test.describe('Photo Viewer', () => {
admin = await utils.adminSetup(); admin = await utils.adminSetup();
asset = await utils.createAsset(admin.accessToken); asset = await utils.createAsset(admin.accessToken);
rawAsset = await utils.createAsset(admin.accessToken, { assetData: { filename: 'test.arw' } }); rawAsset = await utils.createAsset(admin.accessToken, { assetData: { filename: 'test.arw' } });
websocket = await utils.connectWebsocket(admin.accessToken);
});
test.afterAll(() => {
utils.disconnectWebsocket(websocket);
}); });
test.beforeEach(async ({ context, page }) => { test.beforeEach(async ({ context, page }) => {
@@ -30,65 +26,31 @@ test.describe('Photo Viewer', () => {
test('loads original photo when zoomed', async ({ page }) => { test('loads original photo when zoomed', async ({ page }) => {
await page.goto(`/photos/${asset.id}`); await page.goto(`/photos/${asset.id}`);
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail');
const preview = page.getByTestId('preview').filter({ visible: true }); const box = await imageLocator(page).boundingBox();
await expect(preview).toHaveAttribute('src', /.+/); expect(box).toBeTruthy();
const { x, y, width, height } = box!;
const originalResponse = page.waitForResponse((response) => response.url().includes('/original')); await page.mouse.move(x + width / 2, y + height / 2);
const { width, height } = page.viewportSize()!;
await page.mouse.move(width / 2, height / 2);
await page.mouse.wheel(0, -1); await page.mouse.wheel(0, -1);
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('original');
await originalResponse;
const original = page.getByTestId('original').filter({ visible: true });
await expect(original).toHaveAttribute('src', /original/);
}); });
test('loads fullsize image when zoomed and original is web-incompatible', async ({ page }) => { test('loads fullsize image when zoomed and original is web-incompatible', async ({ page }) => {
await page.goto(`/photos/${rawAsset.id}`); await page.goto(`/photos/${rawAsset.id}`);
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail');
const preview = page.getByTestId('preview').filter({ visible: true }); const box = await imageLocator(page).boundingBox();
await expect(preview).toHaveAttribute('src', /.+/); expect(box).toBeTruthy();
const { x, y, width, height } = box!;
const fullsizeResponse = page.waitForResponse((response) => response.url().includes('fullsize')); await page.mouse.move(x + width / 2, y + height / 2);
const { width, height } = page.viewportSize()!;
await page.mouse.move(width / 2, height / 2);
await page.mouse.wheel(0, -1); await page.mouse.wheel(0, -1);
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('fullsize');
await fullsizeResponse;
const original = page.getByTestId('original').filter({ visible: true });
await expect(original).toHaveAttribute('src', /fullsize/);
});
test('right-click targets the img element', async ({ page }) => {
await page.goto(`/photos/${asset.id}`);
const preview = page.getByTestId('preview').filter({ visible: true });
await expect(preview).toHaveAttribute('src', /.+/);
const box = await preview.boundingBox();
const tagAtCenter = await page.evaluate(({ x, y }) => document.elementFromPoint(x, y)?.tagName, {
x: box!.x + box!.width / 2,
y: box!.y + box!.height / 2,
});
expect(tagAtCenter).toBe('IMG');
}); });
test('reloads photo when checksum changes', async ({ page }) => { test('reloads photo when checksum changes', async ({ page }) => {
await page.goto(`/photos/${asset.id}`); await page.goto(`/photos/${asset.id}`);
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail');
const preview = page.getByTestId('preview').filter({ visible: true }); const initialSrc = await imageLocator(page).getAttribute('src');
await expect(preview).toHaveAttribute('src', /.+/);
const initialSrc = await preview.getAttribute('src');
const websocketEvent = utils.waitForWebsocketEvent({ event: 'assetUpdate', id: asset.id });
await utils.replaceAsset(admin.accessToken, asset.id); await utils.replaceAsset(admin.accessToken, asset.id);
await websocketEvent; await expect.poll(async () => await imageLocator(page).getAttribute('src')).not.toBe(initialSrc);
await expect(preview).not.toHaveAttribute('src', initialSrc!);
}); });
}); });
+2 -25
View File
@@ -12,18 +12,15 @@ import { asBearerAuth, utils } from 'src/utils';
test.describe('Shared Links', () => { test.describe('Shared Links', () => {
let admin: LoginResponseDto; let admin: LoginResponseDto;
let asset: AssetMediaResponseDto; let asset: AssetMediaResponseDto;
let asset2: AssetMediaResponseDto;
let album: AlbumResponseDto; let album: AlbumResponseDto;
let sharedLink: SharedLinkResponseDto; let sharedLink: SharedLinkResponseDto;
let sharedLinkPassword: SharedLinkResponseDto; let sharedLinkPassword: SharedLinkResponseDto;
let individualSharedLink: SharedLinkResponseDto;
test.beforeAll(async () => { test.beforeAll(async () => {
utils.initSdk(); utils.initSdk();
await utils.resetDatabase(); await utils.resetDatabase();
admin = await utils.adminSetup(); admin = await utils.adminSetup();
asset = await utils.createAsset(admin.accessToken); asset = await utils.createAsset(admin.accessToken);
asset2 = await utils.createAsset(admin.accessToken);
album = await createAlbum( album = await createAlbum(
{ {
createAlbumDto: { createAlbumDto: {
@@ -42,17 +39,14 @@ test.describe('Shared Links', () => {
albumId: album.id, albumId: album.id,
password: 'test-password', password: 'test-password',
}); });
individualSharedLink = await utils.createSharedLink(admin.accessToken, {
type: SharedLinkType.Individual,
assetIds: [asset.id, asset2.id],
});
}); });
test('download from a shared link', async ({ page }) => { test('download from a shared link', async ({ page }) => {
await page.goto(`/share/${sharedLink.key}`); await page.goto(`/share/${sharedLink.key}`);
await page.getByRole('heading', { name: 'Test Album' }).waitFor(); await page.getByRole('heading', { name: 'Test Album' }).waitFor();
await page.locator(`[data-asset-id="${asset.id}"]`).hover(); await page.locator(`[data-asset-id="${asset.id}"]`).hover();
await page.waitForSelector(`[data-asset-id="${asset.id}"] [role="checkbox"]`); await page.waitForSelector('[data-group] svg');
await page.getByRole('checkbox').click();
await Promise.all([page.waitForEvent('download'), page.getByRole('button', { name: 'Download' }).click()]); await Promise.all([page.waitForEvent('download'), page.getByRole('button', { name: 'Download' }).click()]);
}); });
@@ -116,21 +110,4 @@ test.describe('Shared Links', () => {
await page.waitForURL('/photos'); await page.waitForURL('/photos');
await page.locator(`[data-asset-id="${asset.id}"]`).waitFor(); await page.locator(`[data-asset-id="${asset.id}"]`).waitFor();
}); });
test('owner can remove assets from an individual shared link', async ({ context, page }) => {
await utils.setAuthCookies(context, admin.accessToken);
await page.goto(`/share/${individualSharedLink.key}`);
await page.locator(`[data-asset="${asset.id}"]`).waitFor();
await expect(page.locator(`[data-asset]`)).toHaveCount(2);
await page.locator(`[data-asset="${asset.id}"]`).hover();
await page.locator(`[data-asset="${asset.id}"] [role="checkbox"]`).click();
await page.getByRole('button', { name: 'Remove from shared link' }).click();
await page.getByRole('button', { name: 'Remove', exact: true }).click();
await expect(page.locator(`[data-asset="${asset.id}"]`)).toHaveCount(0);
await expect(page.locator(`[data-asset="${asset2.id}"]`)).toHaveCount(1);
});
}); });
-1
View File
@@ -173,7 +173,6 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI
'.mpeg', '.mpeg',
'.mpg', '.mpg',
'.mts', '.mts',
'.ts',
'.vob', '.vob',
'.webm', '.webm',
'.wmv', '.wmv',
@@ -1,167 +0,0 @@
import { faker } from '@faker-js/faker';
import { AssetTypeEnum, AssetVisibility, type AssetResponseDto, type StackResponseDto } from '@immich/sdk';
import { BrowserContext } from '@playwright/test';
import { randomPreview, randomThumbnail } from 'src/ui/generators/timeline';
export type MockStack = {
id: string;
primaryAssetId: string;
assets: AssetResponseDto[];
brokenAssetIds: Set<string>;
assetMap: Map<string, AssetResponseDto>;
};
export const createMockStackAsset = (ownerId: string): AssetResponseDto => {
const assetId = faker.string.uuid();
const now = new Date().toISOString();
return {
id: assetId,
deviceAssetId: `device-${assetId}`,
ownerId,
owner: {
id: ownerId,
email: 'admin@immich.cloud',
name: 'Admin',
profileImagePath: '',
profileChangedAt: now,
avatarColor: 'blue' as never,
},
libraryId: `library-${ownerId}`,
deviceId: `device-${ownerId}`,
type: AssetTypeEnum.Image,
originalPath: `/original/${assetId}.jpg`,
originalFileName: `${assetId}.jpg`,
originalMimeType: 'image/jpeg',
thumbhash: null,
fileCreatedAt: now,
fileModifiedAt: now,
localDateTime: now,
updatedAt: now,
createdAt: now,
isFavorite: false,
isArchived: false,
isTrashed: false,
visibility: AssetVisibility.Timeline,
duration: '0:00:00.00000',
exifInfo: {
make: null,
model: null,
exifImageWidth: 3000,
exifImageHeight: 4000,
fileSizeInByte: null,
orientation: null,
dateTimeOriginal: now,
modifyDate: null,
timeZone: null,
lensModel: null,
fNumber: null,
focalLength: null,
iso: null,
exposureTime: null,
latitude: null,
longitude: null,
city: null,
country: null,
state: null,
description: null,
},
livePhotoVideoId: null,
tags: [],
people: [],
unassignedFaces: [],
stack: null,
isOffline: false,
hasMetadata: true,
duplicateId: null,
resized: true,
checksum: faker.string.alphanumeric({ length: 28 }),
width: 3000,
height: 4000,
isEdited: false,
};
};
export const createMockStack = (
primaryAssetDto: AssetResponseDto,
additionalAssets: AssetResponseDto[],
brokenAssetIds?: Set<string>,
): MockStack => {
const stackId = faker.string.uuid();
const allAssets = [primaryAssetDto, ...additionalAssets];
const resolvedBrokenIds = brokenAssetIds ?? new Set(additionalAssets.map((a) => a.id));
const assetMap = new Map(allAssets.map((a) => [a.id, a]));
primaryAssetDto.stack = {
id: stackId,
assetCount: allAssets.length,
primaryAssetId: primaryAssetDto.id,
};
return {
id: stackId,
primaryAssetId: primaryAssetDto.id,
assets: allAssets,
brokenAssetIds: resolvedBrokenIds,
assetMap,
};
};
export const setupBrokenAssetMockApiRoutes = async (context: BrowserContext, mockStack: MockStack) => {
await context.route('**/api/stacks/*', async (route, request) => {
if (request.method() !== 'GET') {
return route.fallback();
}
const stackResponse: StackResponseDto = {
id: mockStack.id,
primaryAssetId: mockStack.primaryAssetId,
assets: mockStack.assets,
};
return route.fulfill({
status: 200,
contentType: 'application/json',
json: stackResponse,
});
});
await context.route('**/api/assets/*', async (route, request) => {
if (request.method() !== 'GET') {
return route.fallback();
}
const url = new URL(request.url());
const segments = url.pathname.split('/');
const assetId = segments.at(-1);
if (assetId && mockStack.assetMap.has(assetId)) {
return route.fulfill({
status: 200,
contentType: 'application/json',
json: mockStack.assetMap.get(assetId),
});
}
return route.fallback();
});
await context.route('**/api/assets/*/thumbnail?size=*', async (route, request) => {
if (!route.request().serviceWorker()) {
return route.continue();
}
const pattern = /\/api\/assets\/(?<assetId>[^/]+)\/thumbnail\?size=(?<size>preview|thumbnail)/;
const match = request.url().match(pattern);
if (!match?.groups || !mockStack.assetMap.has(match.groups.assetId)) {
return route.fallback();
}
if (mockStack.brokenAssetIds.has(match.groups.assetId)) {
return route.fulfill({ status: 404 });
}
const asset = mockStack.assetMap.get(match.groups.assetId)!;
const ratio = (asset.exifInfo?.exifImageWidth ?? 3000) / (asset.exifInfo?.exifImageHeight ?? 4000);
const body =
match.groups.size === 'preview'
? await randomPreview(match.groups.assetId, ratio)
: await randomThumbnail(match.groups.assetId, ratio);
return route.fulfill({
status: 200,
headers: { 'content-type': 'image/jpeg' },
body,
});
});
};
@@ -1,127 +0,0 @@
import { BrowserContext } from '@playwright/test';
import { randomThumbnail } from 'src/ui/generators/timeline';
// Minimal valid H.264 MP4 (8x8px, 1 frame) that browsers can decode to get videoWidth/videoHeight
const MINIMAL_MP4_BASE64 =
'AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAr9tZGF0AAACoAYF//+c' +
'3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDEyNSAtIEguMjY0L01QRUctNCBBVkMgY29kZWMg' +
'LSBDb3B5bGVmdCAyMDAzLTIwMTIgLSBodHRwOi8vd3d3LnZpZGVvbGFuLm9yZy94MjY0Lmh0bWwg' +
'LSBvcHRpb25zOiBjYWJhYz0xIHJlZj0zIGRlYmxvY2s9MTowOjAgYW5hbHlzZT0weDM6MHgxMTMg' +
'bWU9aGV4IHN1Ym1lPTcgcHN5PTEgcHN5X3JkPTEuMDA6MC4wMCBtaXhlZF9yZWY9MSBtZV9yYW5n' +
'ZT0xNiBjaHJvbWFfbWU9MSB0cmVsbGlzPTEgOHg4ZGN0PTEgY3FtPTAgZGVhZHpvbmU9MjEsMTEg' +
'ZmFzdF9wc2tpcD0xIGNocm9tYV9xcF9vZmZzZXQ9LTIgdGhyZWFkcz02IGxvb2thaGVhZF90aHJl' +
'YWRzPTEgc2xpY2VkX3RocmVhZHM9MCBucj0wIGRlY2ltYXRlPTEgaW50ZXJsYWNlZD0wIGJsdXJh' +
'eV9jb21wYXQ9MCBjb25zdHJhaW5lZF9pbnRyYT0wIGJmcmFtZXM9MyBiX3B5cmFtaWQ9MiBiX2Fk' +
'YXB0PTEgYl9iaWFzPTAgZGlyZWN0PTEgd2VpZ2h0Yj0xIG9wZW5fZ29wPTAgd2VpZ2h0cD0yIGtl' +
'eWludD0yNTAga2V5aW50X21pbj0yNCBzY2VuZWN1dD00MCBpbnRyYV9yZWZyZXNoPTAgcmNfbG9v' +
'a2FoZWFkPTQwIHJjPWNyZiBtYnRyZWU9MSBjcmY9MjMuMCBxY29tcD0wLjYwIHFwbWluPTAgcXBt' +
'YXg9NjkgcXBzdGVwPTQgaXBfcmF0aW89MS40MCBhcT0xOjEuMDAAgAAAAA9liIQAV/0TAAYdeBTX' +
'zg8AAALvbW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAAACoAAQAAAQAAAAAAAAAAAAAAAAEAAAAA' +
'AAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAA' +
'Ahl0cmFrAAAAXHRraGQAAAAPAAAAAAAAAAAAAAABAAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAEAAAAA' +
'AAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAgAAAAIAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAA' +
'AAEAAAAqAAAAAAABAAAAAAGRbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAAAwAAAAAgBVxAAAAAAA' +
'LWhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAABWaWRlb0hhbmRsZXIAAAABPG1pbmYAAAAUdm1oZAAA' +
'AAEAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAPxzdGJsAAAAmHN0' +
'c2QAAAAAAAAAAQAAAIhhdmMxAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAgACABIAAAASAAAAAAAAAAB' +
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGP//AAAAMmF2Y0MBZAAK/+EAGWdkAAqs' +
'2V+WXAWyAAADAAIAAAMAYB4kSywBAAZo6+PLIsAAAAAYc3R0cwAAAAAAAAABAAAAAQAAAgAAAAAcc3Rz' +
'YwAAAAAAAAABAAAAAQAAAAEAAAABAAAAFHN0c3oAAAAAAAACtwAAAAEAAAAUc3RjbwAAAAAAAAABAAAA' +
'MAAAAGJ1ZHRhAAAAWm1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAALWls' +
'c3QAAAAlqXRvbwAAAB1kYXRhAAAAAQAAAABMYXZmNTQuNjMuMTA0';
export const MINIMAL_MP4_BUFFER = Buffer.from(MINIMAL_MP4_BASE64, 'base64');
export type MockPerson = {
id: string;
name: string;
birthDate: string | null;
isHidden: boolean;
thumbnailPath: string;
updatedAt: string;
};
export const createMockPeople = (count: number): MockPerson[] => {
const names = [
'Alice Johnson',
'Bob Smith',
'Charlie Brown',
'Diana Prince',
'Eve Adams',
'Frank Castle',
'Grace Lee',
'Hank Pym',
'Iris West',
'Jack Ryan',
];
return Array.from({ length: count }, (_, index) => ({
id: `person-${index}`,
name: names[index % names.length],
birthDate: null,
isHidden: false,
thumbnailPath: `/upload/thumbs/person-${index}.jpeg`,
updatedAt: '2025-01-01T00:00:00.000Z',
}));
};
export type FaceCreateCapture = {
requests: Array<{
assetId: string;
personId: string;
x: number;
y: number;
width: number;
height: number;
imageWidth: number;
imageHeight: number;
}>;
};
export const setupFaceEditorMockApiRoutes = async (
context: BrowserContext,
mockPeople: MockPerson[],
faceCreateCapture: FaceCreateCapture,
) => {
await context.route('**/api/people?*', async (route, request) => {
if (request.method() !== 'GET') {
return route.fallback();
}
return route.fulfill({
status: 200,
contentType: 'application/json',
json: {
hasNextPage: false,
hidden: 0,
people: mockPeople,
total: mockPeople.length,
},
});
});
await context.route('**/api/faces', async (route, request) => {
if (request.method() !== 'POST') {
return route.fallback();
}
const body = request.postDataJSON();
faceCreateCapture.requests.push(body);
return route.fulfill({
status: 201,
contentType: 'text/plain',
body: 'OK',
});
});
await context.route('**/api/people/*/thumbnail', async (route) => {
if (!route.request().serviceWorker()) {
return route.continue();
}
return route.fulfill({
status: 200,
headers: { 'content-type': 'image/jpeg' },
body: await randomThumbnail('person-thumb', 1),
});
});
};
-55
View File
@@ -1,55 +0,0 @@
import { faker } from '@faker-js/faker';
import type { AssetOcrResponseDto } from '@immich/sdk';
import { BrowserContext } from '@playwright/test';
export type MockOcrBox = {
text: string;
x1: number;
y1: number;
x2: number;
y2: number;
x3: number;
y3: number;
x4: number;
y4: number;
};
export const createMockOcrData = (assetId: string, boxes: MockOcrBox[]): AssetOcrResponseDto[] => {
return boxes.map((box) => ({
id: faker.string.uuid(),
assetId,
x1: box.x1,
y1: box.y1,
x2: box.x2,
y2: box.y2,
x3: box.x3,
y3: box.y3,
x4: box.x4,
y4: box.y4,
boxScore: 0.95,
textScore: 0.9,
text: box.text,
}));
};
export const setupOcrMockApiRoutes = async (
context: BrowserContext,
ocrDataByAssetId: Map<string, AssetOcrResponseDto[]>,
) => {
await context.route('**/assets/*/ocr', async (route, request) => {
if (request.method() !== 'GET') {
return route.fallback();
}
const url = new URL(request.url());
const segments = url.pathname.split('/');
const assetIdIndex = segments.indexOf('assets') + 1;
const assetId = segments[assetIdIndex];
const ocrData = ocrDataByAssetId.get(assetId) ?? [];
return route.fulfill({
status: 200,
contentType: 'application/json',
json: ocrData,
});
});
};
@@ -12,7 +12,6 @@ import {
TimelineData, TimelineData,
} from 'src/ui/generators/timeline'; } from 'src/ui/generators/timeline';
import { sleep } from 'src/ui/specs/timeline/utils'; import { sleep } from 'src/ui/specs/timeline/utils';
import { MINIMAL_MP4_BUFFER } from './face-editor-network';
export class TimelineTestContext { export class TimelineTestContext {
slowBucket = false; slowBucket = false;
@@ -136,14 +135,6 @@ export const setupTimelineMockApiRoutes = async (
return route.continue(); return route.continue();
}); });
await context.route('**/api/assets/*/video/playback*', async (route) => {
return route.fulfill({
status: 200,
headers: { 'content-type': 'video/mp4' },
body: MINIMAL_MP4_BUFFER,
});
});
await context.route('**/api/albums/**', async (route, request) => { await context.route('**/api/albums/**', async (route, request) => {
const albumsMatch = request.url().match(/\/api\/albums\/(?<albumId>[^/?]+)/); const albumsMatch = request.url().match(/\/api\/albums\/(?<albumId>[^/?]+)/);
if (albumsMatch) { if (albumsMatch) {
@@ -1,86 +0,0 @@
import { expect, test } from '@playwright/test';
import { toAssetResponseDto } from 'src/ui/generators/timeline';
import {
createMockStack,
createMockStackAsset,
MockStack,
setupBrokenAssetMockApiRoutes,
} from 'src/ui/mock-network/broken-asset-network';
import { assetViewerUtils } from '../timeline/utils';
import { setupAssetViewerFixture } from './utils';
test.describe.configure({ mode: 'parallel' });
test.describe('broken-asset responsiveness', () => {
const fixture = setupAssetViewerFixture(889);
let mockStack: MockStack;
test.beforeAll(async () => {
const primaryAssetDto = toAssetResponseDto(fixture.primaryAsset);
const brokenAssets = [
createMockStackAsset(fixture.adminUserId),
createMockStackAsset(fixture.adminUserId),
createMockStackAsset(fixture.adminUserId),
];
mockStack = createMockStack(primaryAssetDto, brokenAssets);
});
test.beforeEach(async ({ context }) => {
await setupBrokenAssetMockApiRoutes(context, mockStack);
});
test('broken asset in stack strip hides icon at small size', async ({ page }) => {
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await assetViewerUtils.waitForViewerLoad(page, fixture.primaryAsset);
const stackSlideshow = page.locator('#stack-slideshow');
await expect(stackSlideshow).toBeVisible();
const brokenAssets = stackSlideshow.locator('[data-broken-asset]');
await expect(brokenAssets.first()).toBeVisible();
await expect(brokenAssets).toHaveCount(mockStack.brokenAssetIds.size);
for (const brokenAsset of await brokenAssets.all()) {
await expect(brokenAsset.locator('svg')).not.toBeVisible();
}
});
test('broken asset in stack strip uses text-xs class', async ({ page }) => {
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await assetViewerUtils.waitForViewerLoad(page, fixture.primaryAsset);
const stackSlideshow = page.locator('#stack-slideshow');
await expect(stackSlideshow).toBeVisible();
const brokenAssets = stackSlideshow.locator('[data-broken-asset]');
await expect(brokenAssets.first()).toBeVisible();
for (const brokenAsset of await brokenAssets.all()) {
const messageSpan = brokenAsset.locator('span');
await expect(messageSpan).toHaveClass(/text-xs/);
}
});
test('broken asset in main viewer shows icon and uses text-base', async ({ context, page }) => {
await context.route(
(url) =>
url.pathname.includes(`/api/assets/${fixture.primaryAsset.id}/thumbnail`) ||
url.pathname.includes(`/api/assets/${fixture.primaryAsset.id}/original`),
async (route) => {
return route.fulfill({ status: 404 });
},
);
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await page.waitForSelector('#immich-asset-viewer');
const viewerBrokenAsset = page.locator('[data-viewer-content] [data-broken-asset]').first();
await expect(viewerBrokenAsset).toBeVisible();
await expect(viewerBrokenAsset.locator('svg')).toBeVisible();
const messageSpan = viewerBrokenAsset.locator('span');
await expect(messageSpan).toHaveClass(/text-base/);
});
});
@@ -1,285 +0,0 @@
import { expect, Page, test } from '@playwright/test';
import { SeededRandom, selectRandom, TimelineAssetConfig } from 'src/ui/generators/timeline';
import {
createMockPeople,
FaceCreateCapture,
MockPerson,
setupFaceEditorMockApiRoutes,
} from 'src/ui/mock-network/face-editor-network';
import { assetViewerUtils } from '../timeline/utils';
import { setupAssetViewerFixture } from './utils';
const waitForSelectorTransition = async (page: Page) => {
await page.waitForFunction(
() => {
const selector = document.querySelector('#face-selector') as HTMLElement | null;
if (!selector) {
return false;
}
return selector.getAnimations({ subtree: false }).every((animation) => animation.playState === 'finished');
},
undefined,
{ timeout: 1000, polling: 50 },
);
};
const openFaceEditor = async (page: Page, asset: TimelineAssetConfig) => {
await page.goto(`/photos/${asset.id}`);
await assetViewerUtils.waitForViewerLoad(page, asset);
await page.keyboard.press('i');
await page.locator('#detail-panel').waitFor({ state: 'visible' });
await page.getByLabel('Tag people').click();
await page.locator('#face-selector').waitFor({ state: 'visible' });
await waitForSelectorTransition(page);
};
test.describe.configure({ mode: 'parallel' });
test.describe('face-editor', () => {
const fixture = setupAssetViewerFixture(777);
const rng = new SeededRandom(777);
let mockPeople: MockPerson[];
let faceCreateCapture: FaceCreateCapture;
test.beforeAll(async () => {
mockPeople = createMockPeople(8);
});
test.beforeEach(async ({ context }) => {
faceCreateCapture = { requests: [] };
await setupFaceEditorMockApiRoutes(context, mockPeople, faceCreateCapture);
});
type ScreenRect = { top: number; left: number; width: number; height: number };
const getFaceBoxRect = async (page: Page): Promise<ScreenRect> => {
const dataEl = page.locator('#face-editor-data');
await expect(dataEl).toHaveAttribute('data-face-left', /^-?\d+/);
await expect(dataEl).toHaveAttribute('data-face-top', /^-?\d+/);
await expect(dataEl).toHaveAttribute('data-face-width', /^[1-9]/);
await expect(dataEl).toHaveAttribute('data-face-height', /^[1-9]/);
const canvasBox = await page.locator('#face-editor').boundingBox();
if (!canvasBox) {
throw new Error('Canvas element not found');
}
const left = Number(await dataEl.getAttribute('data-face-left'));
const top = Number(await dataEl.getAttribute('data-face-top'));
const width = Number(await dataEl.getAttribute('data-face-width'));
const height = Number(await dataEl.getAttribute('data-face-height'));
return {
top: canvasBox.y + top,
left: canvasBox.x + left,
width,
height,
};
};
const getSelectorRect = async (page: Page): Promise<ScreenRect> => {
const box = await page.locator('#face-selector').boundingBox();
if (!box) {
throw new Error('Face selector element not found');
}
return { top: box.y, left: box.x, width: box.width, height: box.height };
};
const computeOverlapArea = (a: ScreenRect, b: ScreenRect): number => {
const overlapX = Math.max(0, Math.min(a.left + a.width, b.left + b.width) - Math.max(a.left, b.left));
const overlapY = Math.max(0, Math.min(a.top + a.height, b.top + b.height) - Math.max(a.top, b.top));
return overlapX * overlapY;
};
const dragFaceBox = async (page: Page, deltaX: number, deltaY: number) => {
const faceBox = await getFaceBoxRect(page);
const centerX = faceBox.left + faceBox.width / 2;
const centerY = faceBox.top + faceBox.height / 2;
await page.mouse.move(centerX, centerY);
await page.mouse.down();
await page.mouse.move(centerX + deltaX, centerY + deltaY, { steps: 5 });
await page.mouse.up();
await page.waitForTimeout(300);
};
test('Face editor opens with person list', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
await expect(page.locator('#face-selector')).toBeVisible();
await expect(page.locator('#face-editor')).toBeVisible();
for (const person of mockPeople) {
await expect(page.locator('#face-selector').getByText(person.name)).toBeVisible();
}
});
test('Search filters people by name', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
const searchInput = page.locator('#face-selector input');
await searchInput.fill('Alice');
await expect(page.locator('#face-selector').getByText('Alice Johnson')).toBeVisible();
await expect(page.locator('#face-selector').getByText('Bob Smith')).toBeHidden();
await searchInput.clear();
for (const person of mockPeople) {
await expect(page.locator('#face-selector').getByText(person.name)).toBeVisible();
}
});
test('Search with no results shows empty message', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
const searchInput = page.locator('#face-selector input');
await searchInput.fill('Nonexistent Person XYZ');
for (const person of mockPeople) {
await expect(page.locator('#face-selector').getByText(person.name)).toBeHidden();
}
});
test('Selecting a person shows confirmation dialog', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
const personToTag = mockPeople[0];
await page.locator('#face-selector').getByText(personToTag.name).click();
await expect(page.getByRole('dialog')).toBeVisible();
});
test('Confirming tag calls createFace API and closes editor', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
const personToTag = mockPeople[0];
await page.locator('#face-selector').getByText(personToTag.name).click();
await expect(page.getByRole('dialog')).toBeVisible();
await page.getByRole('button', { name: /confirm/i }).click();
await expect(page.locator('#face-selector')).toBeHidden();
await expect(page.locator('#face-editor')).toBeHidden();
expect(faceCreateCapture.requests).toHaveLength(1);
expect(faceCreateCapture.requests[0].assetId).toBe(asset.id);
expect(faceCreateCapture.requests[0].personId).toBe(personToTag.id);
});
test('Cancel button closes face editor', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
await expect(page.locator('#face-selector')).toBeVisible();
await expect(page.locator('#face-editor')).toBeVisible();
await page.getByRole('button', { name: /cancel/i }).click();
await expect(page.locator('#face-selector')).toBeHidden();
await expect(page.locator('#face-editor')).toBeHidden();
});
test('Selector does not overlap face box on initial open', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
const faceBox = await getFaceBoxRect(page);
const selectorBox = await getSelectorRect(page);
const overlap = computeOverlapArea(faceBox, selectorBox);
expect(overlap).toBe(0);
});
test('Selector repositions without overlap after dragging face box down', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
await dragFaceBox(page, 0, 150);
const faceBox = await getFaceBoxRect(page);
const selectorBox = await getSelectorRect(page);
const overlap = computeOverlapArea(faceBox, selectorBox);
expect(overlap).toBe(0);
});
test('Selector repositions without overlap after dragging face box right', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
await dragFaceBox(page, 200, 0);
const faceBox = await getFaceBoxRect(page);
const selectorBox = await getSelectorRect(page);
const overlap = computeOverlapArea(faceBox, selectorBox);
expect(overlap).toBe(0);
});
test('Selector repositions without overlap after dragging face box to top-left corner', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
await dragFaceBox(page, -300, -300);
const faceBox = await getFaceBoxRect(page);
const selectorBox = await getSelectorRect(page);
const overlap = computeOverlapArea(faceBox, selectorBox);
expect(overlap).toBe(0);
});
test('Selector repositions without overlap after dragging face box to bottom-right', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
await dragFaceBox(page, 300, 300);
const faceBox = await getFaceBoxRect(page);
const selectorBox = await getSelectorRect(page);
const overlap = computeOverlapArea(faceBox, selectorBox);
expect(overlap).toBe(0);
});
test('Selector stays within viewport bounds', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
const viewportSize = page.viewportSize()!;
const selectorBox = await getSelectorRect(page);
expect(selectorBox.top).toBeGreaterThanOrEqual(0);
expect(selectorBox.left).toBeGreaterThanOrEqual(0);
expect(selectorBox.top + selectorBox.height).toBeLessThanOrEqual(viewportSize.height);
expect(selectorBox.left + selectorBox.width).toBeLessThanOrEqual(viewportSize.width);
});
test('Selector stays within viewport after dragging to edge', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
await dragFaceBox(page, -400, -400);
const viewportSize = page.viewportSize()!;
const selectorBox = await getSelectorRect(page);
expect(selectorBox.top).toBeGreaterThanOrEqual(0);
expect(selectorBox.left).toBeGreaterThanOrEqual(0);
expect(selectorBox.top + selectorBox.height).toBeLessThanOrEqual(viewportSize.height);
expect(selectorBox.left + selectorBox.width).toBeLessThanOrEqual(viewportSize.width);
});
test('Face box is draggable on the canvas', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
const beforeDrag = await getFaceBoxRect(page);
await dragFaceBox(page, 100, 50);
const afterDrag = await getFaceBoxRect(page);
expect(afterDrag.left).toBeGreaterThan(beforeDrag.left + 50);
expect(afterDrag.top).toBeGreaterThan(beforeDrag.top + 20);
});
});
@@ -1,300 +0,0 @@
import type { AssetOcrResponseDto, AssetResponseDto } from '@immich/sdk';
import { expect, test } from '@playwright/test';
import { toAssetResponseDto } from 'src/ui/generators/timeline';
import {
createMockStack,
createMockStackAsset,
MockStack,
setupBrokenAssetMockApiRoutes,
} from 'src/ui/mock-network/broken-asset-network';
import { createMockOcrData, setupOcrMockApiRoutes } from 'src/ui/mock-network/ocr-network';
import { assetViewerUtils } from '../timeline/utils';
import { setupAssetViewerFixture } from './utils';
test.describe.configure({ mode: 'parallel' });
const PRIMARY_OCR_BOXES = [
{ text: 'Hello World', x1: 0.1, y1: 0.1, x2: 0.4, y2: 0.1, x3: 0.4, y3: 0.15, x4: 0.1, y4: 0.15 },
{ text: 'Immich Photo', x1: 0.2, y1: 0.3, x2: 0.6, y2: 0.3, x3: 0.6, y3: 0.36, x4: 0.2, y4: 0.36 },
];
const SECONDARY_OCR_BOXES = [
{ text: 'Second Asset Text', x1: 0.15, y1: 0.2, x2: 0.55, y2: 0.2, x3: 0.55, y3: 0.26, x4: 0.15, y4: 0.26 },
];
test.describe('OCR bounding boxes', () => {
const fixture = setupAssetViewerFixture(920);
test.beforeEach(async ({ context }) => {
const primaryAssetDto = toAssetResponseDto(fixture.primaryAsset);
const ocrDataByAssetId = new Map<string, AssetOcrResponseDto[]>([
[primaryAssetDto.id, createMockOcrData(primaryAssetDto.id, PRIMARY_OCR_BOXES)],
]);
await setupOcrMockApiRoutes(context, ocrDataByAssetId);
});
test('OCR bounding boxes appear when clicking OCR button', async ({ page }) => {
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await assetViewerUtils.waitForViewerLoad(page, fixture.primaryAsset);
const ocrButton = page.getByLabel('Text recognition');
await expect(ocrButton).toBeVisible();
await ocrButton.click();
const ocrBoxes = page.locator('[data-viewer-content] [data-testid="ocr-box"]');
await expect(ocrBoxes).toHaveCount(2);
await expect(ocrBoxes.nth(0)).toContainText('Hello World');
await expect(ocrBoxes.nth(1)).toContainText('Immich Photo');
});
test('OCR bounding boxes toggle off on second click', async ({ page }) => {
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await assetViewerUtils.waitForViewerLoad(page, fixture.primaryAsset);
const ocrButton = page.getByLabel('Text recognition');
await ocrButton.click();
await expect(page.locator('[data-viewer-content] [data-testid="ocr-box"]').first()).toBeVisible();
await ocrButton.click();
await expect(page.locator('[data-viewer-content] [data-testid="ocr-box"]')).toHaveCount(0);
});
});
test.describe('OCR with stacked assets', () => {
const fixture = setupAssetViewerFixture(921);
let mockStack: MockStack;
let primaryAssetDto: AssetResponseDto;
let secondAssetDto: AssetResponseDto;
test.beforeAll(async () => {
primaryAssetDto = toAssetResponseDto(fixture.primaryAsset);
secondAssetDto = createMockStackAsset(fixture.adminUserId);
secondAssetDto.originalFileName = 'second-ocr-asset.jpg';
mockStack = createMockStack(primaryAssetDto, [secondAssetDto], new Set());
});
test.beforeEach(async ({ context }) => {
await setupBrokenAssetMockApiRoutes(context, mockStack);
const ocrDataByAssetId = new Map<string, AssetOcrResponseDto[]>([
[primaryAssetDto.id, createMockOcrData(primaryAssetDto.id, PRIMARY_OCR_BOXES)],
[secondAssetDto.id, createMockOcrData(secondAssetDto.id, SECONDARY_OCR_BOXES)],
]);
await setupOcrMockApiRoutes(context, ocrDataByAssetId);
});
test('different OCR boxes shown for different stacked assets', async ({ page }) => {
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await assetViewerUtils.waitForViewerLoad(page, fixture.primaryAsset);
const ocrButton = page.getByLabel('Text recognition');
await expect(ocrButton).toBeVisible();
await ocrButton.click();
const ocrBoxes = page.locator('[data-viewer-content] [data-testid="ocr-box"]');
await expect(ocrBoxes).toHaveCount(2);
await expect(ocrBoxes.nth(0)).toContainText('Hello World');
const stackThumbnails = page.locator('#stack-slideshow [data-asset]');
await expect(stackThumbnails).toHaveCount(2);
await stackThumbnails.nth(1).click();
// refreshOcr() clears showOverlay when switching assets, so re-enable it
await expect(ocrBoxes).toHaveCount(0);
await expect(ocrButton).toBeVisible();
await ocrButton.click();
await expect(ocrBoxes).toHaveCount(1);
await expect(ocrBoxes.first()).toContainText('Second Asset Text');
});
});
test.describe('OCR boxes and zoom', () => {
const fixture = setupAssetViewerFixture(922);
test.beforeEach(async ({ context }) => {
const primaryAssetDto = toAssetResponseDto(fixture.primaryAsset);
const ocrDataByAssetId = new Map<string, AssetOcrResponseDto[]>([
[primaryAssetDto.id, createMockOcrData(primaryAssetDto.id, PRIMARY_OCR_BOXES)],
]);
await setupOcrMockApiRoutes(context, ocrDataByAssetId);
});
test('OCR boxes scale with zoom', async ({ page }) => {
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await assetViewerUtils.waitForViewerLoad(page, fixture.primaryAsset);
const ocrButton = page.getByLabel('Text recognition');
await expect(ocrButton).toBeVisible();
await ocrButton.click();
const ocrBox = page.locator('[data-viewer-content] [data-testid="ocr-box"]').first();
await expect(ocrBox).toBeVisible();
const initialBox = await ocrBox.boundingBox();
expect(initialBox).toBeTruthy();
const { width, height } = page.viewportSize()!;
await page.mouse.move(width / 2, height / 2);
await page.mouse.wheel(0, -3);
await expect(async () => {
const zoomedBox = await ocrBox.boundingBox();
expect(zoomedBox).toBeTruthy();
expect(zoomedBox!.width).toBeGreaterThan(initialBox!.width);
expect(zoomedBox!.height).toBeGreaterThan(initialBox!.height);
}).toPass({ timeout: 2000 });
});
});
test.describe('OCR text interaction', () => {
const fixture = setupAssetViewerFixture(923);
test.beforeEach(async ({ context }) => {
const primaryAssetDto = toAssetResponseDto(fixture.primaryAsset);
const ocrDataByAssetId = new Map<string, AssetOcrResponseDto[]>([
[primaryAssetDto.id, createMockOcrData(primaryAssetDto.id, PRIMARY_OCR_BOXES)],
]);
await setupOcrMockApiRoutes(context, ocrDataByAssetId);
});
test('OCR text box has data-overlay-interactive attribute', async ({ page }) => {
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await assetViewerUtils.waitForViewerLoad(page, fixture.primaryAsset);
await page.getByLabel('Text recognition').click();
const ocrBox = page.locator('[data-viewer-content] [data-testid="ocr-box"]').first();
await expect(ocrBox).toBeVisible();
await expect(ocrBox).toHaveAttribute('data-overlay-interactive');
});
test('OCR text box receives focus on click', async ({ page }) => {
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await assetViewerUtils.waitForViewerLoad(page, fixture.primaryAsset);
await page.getByLabel('Text recognition').click();
const ocrBox = page.locator('[data-viewer-content] [data-testid="ocr-box"]').first();
await expect(ocrBox).toBeVisible();
await ocrBox.click();
await expect(ocrBox).toBeFocused();
});
test('dragging on OCR text box does not trigger image pan', async ({ page }) => {
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await assetViewerUtils.waitForViewerLoad(page, fixture.primaryAsset);
await page.getByLabel('Text recognition').click();
const ocrBox = page.locator('[data-viewer-content] [data-testid="ocr-box"]').first();
await expect(ocrBox).toBeVisible();
const imgLocator = page.locator('[data-viewer-content] img[draggable="false"]');
const initialTransform = await imgLocator.evaluate((element) => {
return getComputedStyle(element.closest('[style*="transform"]') ?? element).transform;
});
const box = await ocrBox.boundingBox();
expect(box).toBeTruthy();
const centerX = box!.x + box!.width / 2;
const centerY = box!.y + box!.height / 2;
await page.mouse.move(centerX, centerY);
await page.mouse.down();
await page.mouse.move(centerX + 50, centerY + 30, { steps: 5 });
await page.mouse.up();
const afterTransform = await imgLocator.evaluate((element) => {
return getComputedStyle(element.closest('[style*="transform"]') ?? element).transform;
});
expect(afterTransform).toBe(initialTransform);
});
test('split touch gesture across zoom container does not trigger zoom', async ({ page }) => {
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await assetViewerUtils.waitForViewerLoad(page, fixture.primaryAsset);
await page.getByLabel('Text recognition').click();
const ocrBox = page.locator('[data-viewer-content] [data-testid="ocr-box"]').first();
await expect(ocrBox).toBeVisible();
const imgLocator = page.locator('[data-viewer-content] img[draggable="false"]');
const initialTransform = await imgLocator.evaluate((element) => {
return getComputedStyle(element.closest('[style*="transform"]') ?? element).transform;
});
const viewerContent = page.locator('[data-viewer-content]');
const viewerBox = await viewerContent.boundingBox();
expect(viewerBox).toBeTruthy();
// Dispatch a synthetic split gesture: one touch inside the viewer, one outside
await page.evaluate(
({ viewerCenterX, viewerCenterY, outsideY }) => {
const viewer = document.querySelector('[data-viewer-content]');
if (!viewer) {
return;
}
const createTouch = (id: number, x: number, y: number) => {
return new Touch({
identifier: id,
target: viewer,
clientX: x,
clientY: y,
});
};
const insideTouch = createTouch(0, viewerCenterX, viewerCenterY);
const outsideTouch = createTouch(1, viewerCenterX, outsideY);
const touchStartEvent = new TouchEvent('touchstart', {
touches: [insideTouch, outsideTouch],
targetTouches: [insideTouch],
changedTouches: [insideTouch, outsideTouch],
bubbles: true,
cancelable: true,
});
const touchMoveEvent = new TouchEvent('touchmove', {
touches: [createTouch(0, viewerCenterX, viewerCenterY - 30), createTouch(1, viewerCenterX, outsideY + 30)],
targetTouches: [createTouch(0, viewerCenterX, viewerCenterY - 30)],
changedTouches: [
createTouch(0, viewerCenterX, viewerCenterY - 30),
createTouch(1, viewerCenterX, outsideY + 30),
],
bubbles: true,
cancelable: true,
});
const touchEndEvent = new TouchEvent('touchend', {
touches: [],
targetTouches: [],
changedTouches: [insideTouch, outsideTouch],
bubbles: true,
cancelable: true,
});
viewer.dispatchEvent(touchStartEvent);
viewer.dispatchEvent(touchMoveEvent);
viewer.dispatchEvent(touchEndEvent);
},
{
viewerCenterX: viewerBox!.x + viewerBox!.width / 2,
viewerCenterY: viewerBox!.y + viewerBox!.height / 2,
outsideY: 10, // near the top of the page, outside the viewer
},
);
const afterTransform = await imgLocator.evaluate((element) => {
return getComputedStyle(element.closest('[style*="transform"]') ?? element).transform;
});
expect(afterTransform).toBe(initialTransform);
});
});
@@ -1,84 +0,0 @@
import { faker } from '@faker-js/faker';
import type { AssetResponseDto } from '@immich/sdk';
import { expect, test } from '@playwright/test';
import { toAssetResponseDto } from 'src/ui/generators/timeline';
import {
createMockStack,
createMockStackAsset,
MockStack,
setupBrokenAssetMockApiRoutes,
} from 'src/ui/mock-network/broken-asset-network';
import { assetViewerUtils } from '../timeline/utils';
import { enableTagsPreference, ensureDetailPanelVisible, setupAssetViewerFixture } from './utils';
test.describe.configure({ mode: 'parallel' });
test.describe('asset-viewer stack', () => {
const fixture = setupAssetViewerFixture(888);
let mockStack: MockStack;
let primaryAssetDto: AssetResponseDto;
let secondAssetDto: AssetResponseDto;
test.beforeAll(async () => {
primaryAssetDto = toAssetResponseDto(fixture.primaryAsset);
primaryAssetDto.tags = [
{
id: faker.string.uuid(),
name: '1',
value: 'test/1',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
];
secondAssetDto = createMockStackAsset(fixture.adminUserId);
secondAssetDto.tags = [
{
id: faker.string.uuid(),
name: '2',
value: 'test/2',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
];
mockStack = createMockStack(primaryAssetDto, [secondAssetDto], new Set());
});
test.beforeEach(async ({ context }) => {
await setupBrokenAssetMockApiRoutes(context, mockStack);
});
test('stack slideshow is visible', async ({ page }) => {
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await assetViewerUtils.waitForViewerLoad(page, fixture.primaryAsset);
const stackSlideshow = page.locator('#stack-slideshow');
await expect(stackSlideshow).toBeVisible();
const stackAssets = stackSlideshow.locator('[data-asset]');
await expect(stackAssets).toHaveCount(mockStack.assets.length);
});
test('tags of primary asset are visible', async ({ context, page }) => {
await enableTagsPreference(context);
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await ensureDetailPanelVisible(page);
const tags = page.getByTestId('detail-panel-tags').getByRole('link');
await expect(tags.first()).toHaveText('test/1');
});
test('tags of second asset are visible', async ({ context, page }) => {
await enableTagsPreference(context);
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await ensureDetailPanelVisible(page);
const stackAssets = page.locator('#stack-slideshow [data-asset]');
await stackAssets.nth(1).click();
const tags = page.getByTestId('detail-panel-tags').getByRole('link');
await expect(tags.first()).toHaveText('test/2');
});
});
-116
View File
@@ -1,116 +0,0 @@
import { faker } from '@faker-js/faker';
import type { AssetResponseDto } from '@immich/sdk';
import { BrowserContext, Page, test } from '@playwright/test';
import {
Changes,
createDefaultTimelineConfig,
generateTimelineData,
SeededRandom,
selectRandom,
TimelineAssetConfig,
TimelineData,
toAssetResponseDto,
} from 'src/ui/generators/timeline';
import { setupBaseMockApiRoutes } from 'src/ui/mock-network/base-network';
import { setupTimelineMockApiRoutes, TimelineTestContext } from 'src/ui/mock-network/timeline-network';
import { utils } from 'src/utils';
export type AssetViewerTestFixture = {
adminUserId: string;
timelineRestData: TimelineData;
assets: TimelineAssetConfig[];
testContext: TimelineTestContext;
changes: Changes;
primaryAsset: TimelineAssetConfig;
primaryAssetDto: AssetResponseDto;
};
export function setupAssetViewerFixture(seed: number): AssetViewerTestFixture {
const rng = new SeededRandom(seed);
const testContext = new TimelineTestContext();
const fixture: AssetViewerTestFixture = {
adminUserId: undefined!,
timelineRestData: undefined!,
assets: [],
testContext,
changes: {
albumAdditions: [],
assetDeletions: [],
assetArchivals: [],
assetFavorites: [],
},
primaryAsset: undefined!,
primaryAssetDto: undefined!,
};
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();
fixture.adminUserId = faker.string.uuid();
testContext.adminId = fixture.adminUserId;
fixture.timelineRestData = generateTimelineData({
...createDefaultTimelineConfig(),
ownerId: fixture.adminUserId,
});
for (const timeBucket of fixture.timelineRestData.buckets.values()) {
fixture.assets.push(...timeBucket);
}
fixture.primaryAsset = selectRandom(
fixture.assets.filter((a) => a.isImage),
rng,
);
fixture.primaryAssetDto = toAssetResponseDto(fixture.primaryAsset);
});
test.beforeEach(async ({ context }) => {
await setupBaseMockApiRoutes(context, fixture.adminUserId);
await setupTimelineMockApiRoutes(context, fixture.timelineRestData, fixture.changes, fixture.testContext);
});
test.afterEach(() => {
fixture.testContext.slowBucket = false;
fixture.changes.albumAdditions = [];
fixture.changes.assetDeletions = [];
fixture.changes.assetArchivals = [];
fixture.changes.assetFavorites = [];
});
return fixture;
}
export async function ensureDetailPanelVisible(page: Page) {
await page.waitForSelector('#immich-asset-viewer');
const isVisible = await page.locator('#detail-panel').isVisible();
if (!isVisible) {
await page.keyboard.press('i');
await page.waitForSelector('#detail-panel');
}
}
export async function enableTagsPreference(context: BrowserContext) {
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: true, 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 },
},
});
});
}
@@ -6,7 +6,6 @@ import {
generateTimelineData, generateTimelineData,
TimelineAssetConfig, TimelineAssetConfig,
TimelineData, TimelineData,
toAssetResponseDto,
} from 'src/ui/generators/timeline'; } from 'src/ui/generators/timeline';
import { setupBaseMockApiRoutes } from 'src/ui/mock-network/base-network'; import { setupBaseMockApiRoutes } from 'src/ui/mock-network/base-network';
import { setupTimelineMockApiRoutes, TimelineTestContext } from 'src/ui/mock-network/timeline-network'; import { setupTimelineMockApiRoutes, TimelineTestContext } from 'src/ui/mock-network/timeline-network';
@@ -31,10 +30,6 @@ test.describe('search gallery-viewer', () => {
}; };
test.beforeAll(async () => { 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',
);
adminUserId = faker.string.uuid(); adminUserId = faker.string.uuid();
testContext.adminId = adminUserId; testContext.adminId = adminUserId;
timelineRestData = generateTimelineData({ ...createDefaultTimelineConfig(), ownerId: adminUserId }); timelineRestData = generateTimelineData({ ...createDefaultTimelineConfig(), ownerId: adminUserId });
@@ -49,10 +44,7 @@ test.describe('search gallery-viewer', () => {
await context.route('**/api/search/metadata', async (route, request) => { await context.route('**/api/search/metadata', async (route, request) => {
if (request.method() === 'POST') { if (request.method() === 'POST') {
const searchAssets = assets const searchAssets = assets.slice(0, 5).filter((asset) => !changes.assetDeletions.includes(asset.id));
.slice(0, 5)
.filter((asset) => !changes.assetDeletions.includes(asset.id))
.map((asset) => toAssetResponseDto(asset));
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
contentType: 'application/json', contentType: 'application/json',
@@ -438,7 +438,7 @@ test.describe('Timeline', () => {
const asset = getAsset(timelineRestData, album.assetIds[0])!; const asset = getAsset(timelineRestData, album.assetIds[0])!;
await pageUtils.goToAsset(page, asset.fileCreatedAt); await pageUtils.goToAsset(page, asset.fileCreatedAt);
await thumbnailUtils.expectInViewport(page, asset.id); await thumbnailUtils.expectInViewport(page, asset.id);
await thumbnailUtils.expectSelectedDisabled(page, asset.id); await thumbnailUtils.expectSelectedReadonly(page, asset.id);
}); });
test('Add photos to album', async ({ page }) => { test('Add photos to album', async ({ page }) => {
const album = timelineRestData.album; const album = timelineRestData.album;
@@ -447,7 +447,7 @@ test.describe('Timeline', () => {
const asset = getAsset(timelineRestData, album.assetIds[0])!; const asset = getAsset(timelineRestData, album.assetIds[0])!;
await pageUtils.goToAsset(page, asset.fileCreatedAt); await pageUtils.goToAsset(page, asset.fileCreatedAt);
await thumbnailUtils.expectInViewport(page, asset.id); await thumbnailUtils.expectInViewport(page, asset.id);
await thumbnailUtils.expectSelectedDisabled(page, asset.id); await thumbnailUtils.expectSelectedReadonly(page, asset.id);
await pageUtils.selectDay(page, 'Tue, Feb 27, 2024'); await pageUtils.selectDay(page, 'Tue, Feb 27, 2024');
const put = pageRoutePromise(page, `**/api/albums/${album.id}/assets`, async (route, request) => { const put = pageRoutePromise(page, `**/api/albums/${album.id}/assets`, async (route, request) => {
const requestJson = request.postDataJSON(); const requestJson = request.postDataJSON();
+5 -6
View File
@@ -62,7 +62,7 @@ export const thumbnailUtils = {
return page.locator(`[data-thumbnail-focus-container][data-asset="${assetId}"]`); return page.locator(`[data-thumbnail-focus-container][data-asset="${assetId}"]`);
}, },
selectButton(page: Page, assetId: string) { selectButton(page: Page, assetId: string) {
return page.locator(`[data-thumbnail-focus-container][data-asset="${assetId}"] button[role="checkbox"]`); return page.locator(`[data-thumbnail-focus-container][data-asset="${assetId}"] button`);
}, },
selectedAsset(page: Page) { selectedAsset(page: Page) {
return page.locator('[data-thumbnail-focus-container][data-selected]'); return page.locator('[data-thumbnail-focus-container][data-selected]');
@@ -102,9 +102,9 @@ export const thumbnailUtils = {
async expectThumbnailIsNotArchive(page: Page, assetId: string) { async expectThumbnailIsNotArchive(page: Page, assetId: string) {
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(0); await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(0);
}, },
async expectSelectedDisabled(page: Page, assetId: string) { async expectSelectedReadonly(page: Page, assetId: string) {
await expect( await expect(
page.locator(`[data-thumbnail-focus-container][data-asset="${assetId}"][data-selected][data-disabled]`), page.locator(`[data-thumbnail-focus-container][data-asset="${assetId}"][data-selected]`),
).toBeVisible(); ).toBeVisible();
}, },
async expectTimelineHasOnScreenAssets(page: Page) { async expectTimelineHasOnScreenAssets(page: Page) {
@@ -215,9 +215,8 @@ export const pageUtils = {
await page.getByText('Confirm').click(); await page.getByText('Confirm').click();
}, },
async selectDay(page: Page, day: string) { async selectDay(page: Page, day: string) {
const section = page.getByTitle(day).locator('xpath=ancestor::section[@data-group]'); await page.getByTitle(day).hover();
await section.hover(); await page.locator('[data-group] .w-8').click();
await section.locator('.w-8').click();
}, },
async pauseTestDebug() { async pauseTestDebug() {
console.log('NOTE: pausing test indefinately for debug'); console.log('NOTE: pausing test indefinately for debug');
+29 -43
View File
@@ -177,51 +177,40 @@ export const utils = {
}, },
resetDatabase: async (tables?: string[]) => { resetDatabase: async (tables?: string[]) => {
client = await utils.connectDatabase(); try {
client = await utils.connectDatabase();
tables = tables || [ tables = tables || [
// TODO e2e test for deleting a stack, since it is quite complex // TODO e2e test for deleting a stack, since it is quite complex
'stack', 'stack',
'library', 'library',
'shared_link', 'shared_link',
'person', 'person',
'album', 'album',
'asset', 'asset',
'asset_face', 'asset_face',
'activity', 'activity',
'api_key', 'api_key',
'session', 'session',
'user', 'user',
'system_metadata', 'system_metadata',
'tag', 'tag',
]; ];
const truncateTables = tables.filter((table) => table !== 'system_metadata'); const sql: string[] = [];
const sql: string[] = [];
if (truncateTables.length > 0) { for (const table of tables) {
sql.push(`TRUNCATE "${truncateTables.join('", "')}" CASCADE;`); if (table === 'system_metadata') {
} sql.push(`DELETE FROM "system_metadata" where "key" NOT IN ('reverse-geocoding-state', 'system-flags');`);
} else {
if (tables.includes('system_metadata')) { sql.push(`DELETE FROM "${table}" CASCADE;`);
sql.push(`DELETE FROM "system_metadata" where "key" NOT IN ('reverse-geocoding-state', 'system-flags');`);
}
const query = sql.join('\n');
const maxRetries = 3;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await client.query(query);
return;
} catch (error: any) {
if (error?.code === '40P01' && attempt < maxRetries) {
await new Promise((resolve) => setTimeout(resolve, 250 * attempt));
continue;
} }
console.error('Failed to reset database', error);
throw error;
} }
await client.query(sql.join('\n'));
} catch (error) {
console.error('Failed to reset database', error);
throw error;
} }
}, },
@@ -510,9 +499,6 @@ export const utils = {
createStack: (accessToken: string, assetIds: string[]) => createStack: (accessToken: string, assetIds: string[]) =>
createStack({ stackCreateDto: { assetIds } }, { headers: asBearerAuth(accessToken) }), createStack({ stackCreateDto: { assetIds } }, { headers: asBearerAuth(accessToken) }),
setAssetDuplicateId: (accessToken: string, assetId: string, duplicateId: string | null) =>
updateAssets({ assetBulkUpdateDto: { ids: [assetId], duplicateId } }, { headers: asBearerAuth(accessToken) }),
upsertTags: (accessToken: string, tags: string[]) => upsertTags: (accessToken: string, tags: string[]) =>
upsertTags({ tagUpsertDto: { tags } }, { headers: asBearerAuth(accessToken) }), upsertTags({ tagUpsertDto: { tags } }, { headers: asBearerAuth(accessToken) }),
+1 -1
View File
@@ -17,6 +17,6 @@
"esModuleInterop": true, "esModuleInterop": true,
"baseUrl": "./" "baseUrl": "./"
}, },
"include": ["src/**/*.ts", "vitest*.config.ts"], "include": ["src/**/*.ts"],
"exclude": ["dist", "node_modules"] "exclude": ["dist", "node_modules"]
} }
+5 -5
View File
@@ -1,4 +1,3 @@
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config'; import { defineConfig } from 'vitest/config';
const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true'; const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true';
@@ -15,14 +14,15 @@ if (!skipDockerSetup) {
export default defineConfig({ export default defineConfig({
test: { test: {
name: 'e2e:server',
retry: process.env.CI ? 4 : 0, retry: process.env.CI ? 4 : 0,
include: ['src/specs/server/**/*.e2e-spec.ts'], include: ['src/specs/server/**/*.e2e-spec.ts'],
globalSetup, globalSetup,
testTimeout: 15_000, testTimeout: 15_000,
pool: 'threads', pool: 'threads',
maxWorkers: 1, poolOptions: {
isolate: false, threads: {
singleThread: true,
},
},
}, },
plugins: [tsconfigPaths()],
}); });
+5 -5
View File
@@ -1,4 +1,3 @@
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config'; import { defineConfig } from 'vitest/config';
const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true'; const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true';
@@ -15,14 +14,15 @@ if (!skipDockerSetup) {
export default defineConfig({ export default defineConfig({
test: { test: {
name: 'e2e:maintenance',
retry: process.env.CI ? 4 : 0, retry: process.env.CI ? 4 : 0,
include: ['src/specs/maintenance/server/**/*.e2e-spec.ts'], include: ['src/specs/maintenance/server/**/*.e2e-spec.ts'],
globalSetup, globalSetup,
testTimeout: 15_000, testTimeout: 15_000,
pool: 'threads', pool: 'threads',
maxWorkers: 1, poolOptions: {
isolate: false, threads: {
singleThread: true,
},
},
}, },
plugins: [tsconfigPaths()],
}); });
+125 -224
View File
@@ -2,147 +2,147 @@
"about": "Oor", "about": "Oor",
"account": "Rekening", "account": "Rekening",
"account_settings": "Rekeninginstellings", "account_settings": "Rekeninginstellings",
"acknowledge": "Neem kennis", "acknowledge": "Erken",
"action": "Aksie", "action": "Aksie",
"action_common_update": "Werk by", "action_common_update": "Opdateur",
"actions": "Aksies", "actions": "Aksies",
"active": "Aktief", "active": "Aktief",
"activity": "Aktiwiteite", "activity": "Aktiwiteite",
"activity_changed": "Aktiwiteit is {enabled, select, true {geaktiveer} other {gedeaktiveer}}", "activity_changed": "Aktiwiteit is {enabled, select, true {aangeskakel} other {afgeskakel}}",
"add": "Voeg toe", "add": "Voegby",
"add_a_description": "Voeg ’n beskrywing toe", "add_a_description": "Voeg 'n beskrywing by",
"add_a_location": "Voeg ’n ligging toe", "add_a_location": "Voeg 'n ligging by",
"add_a_name": "Voeg ’n naam toe", "add_a_name": "Voeg 'n naam by",
"add_a_title": "Voeg ’n titel toe", "add_a_title": "Voeg 'n titel by",
"add_birthday": "Voeg ’n verjaarsdag toe", "add_birthday": "Voeg 'n verjaarsdag by",
"add_endpoint": "Voeg eindpunt toe", "add_endpoint": "Voeg Koppelvlakpunt by",
"add_exclusion_pattern": "Voeg uitsluitingspatroon toe", "add_exclusion_pattern": "Voeg uitsgluitingspatrone by",
"add_location": "Voeg ligging toe", "add_location": "Voeg ligging by",
"add_more_users": "Voeg meer gebruikers toe", "add_more_users": "Voeg meer gebruikers by",
"add_partner": "Voeg vennoot toe", "add_partner": "Voeg vennoot by",
"add_path": "Voeg pad toe", "add_path": "Voeg pad by",
"add_photos": "Voeg foto’s toe", "add_photos": "Voeg foto's by",
"add_tag": "Voeg etiket toe", "add_tag": "Voeg tag by",
"add_to": "Voeg toe totâ€Ļ", "add_to": "Voeg byâ€Ļ",
"add_to_album": "Voeg toe tot album", "add_to_album": "Voeg na album",
"add_to_album_bottom_sheet_added": "Tot {album} toegevoeg", "add_to_album_bottom_sheet_added": "By {album} bygevoeg",
"add_to_album_bottom_sheet_already_exists": "Reeds in {album}", "add_to_album_bottom_sheet_already_exists": "Reeds in {album}",
"add_to_albums": "Voeg toe tot albums", "add_to_albums": "Voeg by albums",
"add_to_albums_count": "Voeg toe tot albums ({count})", "add_to_albums_count": "Voeg by ({count}) albums",
"add_to_shared_album": "Voeg toe tot gedeelde album", "add_to_shared_album": "Voeg toe aan gedeelde album",
"add_url": "Voeg bronadres toe", "add_url": "Voeg URL by",
"added_to_archive": "Tot argief toegevoeg", "added_to_archive": "By argief toegevoegd",
"added_to_favorites": "Tot gunstelinge toegevoeg", "added_to_favorites": "By gunstelinge toegevoegd",
"added_to_favorites_count": "{count, number} tot gunstelinge toegevoeg", "added_to_favorites_count": "Het {count, number} by gunstelinge toegevoegd",
"admin": { "admin": {
"add_exclusion_pattern_description": "Voeg uitsluitingspatrone toe. Plekhouers met *, ** en ? word ondersteun. Om alle lÃĒers in enige vouer genaamd “Raw” te ignoreer, gebruik “**/Raw/**”. Om alle lÃĒers wat op “.tif” eindig, te ignoreer, gebruik “**/*.tif”. Om ’n absolute pad te ignoreer, gebruik “/path/to/ignore/**”.", "add_exclusion_pattern_description": "Voeg uitsluitingspatrone by. Globbing met *, ** en ? word ondersteun. Om alle lÃĒers in enige lÃĒergids genaamd \"Raw\" te ignoreer, gebruik \"**/Raw/**\". Om alle lÃĒers wat op \".tif\" eindig, te ignoreer, gebruik \"**/*.tif\". Om 'n absolute pad te ignoreer, gebruik \"/path/to/ignore/**\".",
"admin_user": "Admingebruiker", "admin_user": "Admin gebruiker",
"asset_offline_description": "Hierdie eksterne biblioteekitem word nie meer op skyf gevind nie en is na die asblik geskuif. As die lÃĒer binne die biblioteek geskuif is, gaan u tydlyn na vir die nuwe ooreenstemmende item. Om hierdie item te herstel, maak asseblief seker dat die lÃĒerpad hieronder deur Immich verkry kan word en skandeer die biblioteek.", "asset_offline_description": "Hierdie eksterne biblioteekbate word nie meer op skyf gevind nie en is na die asblik geskuif. As die lÃĒer binne die biblioteek geskuif is, gaan jou tydlyn na vir die nuwe ooreenstemmende bate. Om hierdie bate te herstel, maak asseblief seker dat die lÃĒerpad hieronder deur Immich verkry kan word en skandeer die biblioteek.",
"authentication_settings": "Waarmerkinstellings", "authentication_settings": "Verifikasie instellings",
"authentication_settings_description": "Bestuur wagwoord, OAuth en ander waarmerkinstellings", "authentication_settings_description": "Bestuur wagwoord, OAuth en ander verifikasie instellings",
"authentication_settings_disable_all": "Is u seker u wil alle aantekenmetodes deaktiveer? Aantekening sal heeltemal gedeaktiveer word.", "authentication_settings_disable_all": "Is jy seker jy wil alle aanmeldmetodes deaktiveer? Aanmelding sal heeltemal gedeaktiveer word.",
"authentication_settings_reenable": "Gebruik ’n <link>bedienerbevel</link> om te heraktiveer.", "authentication_settings_reenable": "Om te heraktiveer, gebruik 'n <link>Server Command</link>.",
"background_task_job": "Agtergrondtake", "background_task_job": "Agtergrondtake",
"backup_database": "Skep DatabasisstortlÃĒer", "backup_database": "Skep DatastortlÃĒer",
"backup_database_enable_description": "Aktiveer databasisstortlÃĒers", "backup_database_enable_description": "Aktiveer databasisrugsteun",
"backup_keep_last_amount": "Aantal vorige stortlÃĒers om te hou", "backup_keep_last_amount": "Aantal vorige rugsteune om te hou",
"backup_onboarding_3_description": "totale kopieÃĢ van u data, insluitend die oorspronklike lÃĒers. Dit sluit 1 kopie op ’n ander perseel en 2 lokale kopieÃĢ in.", "backup_onboarding_3_description": "totale kopieÃĢ van jou data, insluitende die oorspronklikke lÃĒers. Dit sluit in 1 kopie op 'n ander perseel en 2 kopieÃĢ om die huidige rekenaar.",
"backup_onboarding_description": "’n <backblaze-link>3-2-1-rugsteunstrategie</backblaze-link> word sterk aanbeveel om u data veilig te hou. Hou kopieÃĢ van u foto’s/video’s sowel as die Immich-databasis vir ’n volledige rugsteunoplossing.", "backup_onboarding_description": "'N <backblaze-link>3-2-1 rugsteun strategie</backblaze-link> word sterk aanbeveel om jou data veilig te hou. Hou kopieÃĢ van jou fotos/videos so wel as die Immich databasis vir 'n volledige rugsteun oplossing.",
"backup_onboarding_footer": "Lees hierdie <link>dokument</link> vir meer inligting oor hoe om ’n rugsteunkopie van Immich te maak.", "backup_onboarding_footer": "Vir meer inligting oor hoe om 'n rugsteun kopie van Immich te maak, gaan lees asseblief hierdie <link>dokument</link>.",
"backup_onboarding_parts_title": "’n 3-2-1-rugsteun sluit in:", "backup_onboarding_parts_title": "'N 3-2-1 rugsteun sluit in:",
"backup_onboarding_title": "RugsteunkopieÃĢ", "backup_onboarding_title": "Rugsteun kopieÃĢ",
"backup_settings": "Databasisstortinstellings", "backup_settings": "Rugsteun instellings",
"backup_settings_description": "Bestuur databasisrugsteuninstellings.", "backup_settings_description": "Bestuur databasis rugsteun instellings.",
"cleared_jobs": "Take gewis vir: {job}", "cleared_jobs": "Poste gevee vir: {job}",
"config_set_by_file": "Config word tans deur ’n konfigurasielÃĒer gestel", "config_set_by_file": "Config word tans deur 'n konfigurasielÃĒer gestel",
"confirm_delete_library": "Is u seker u wil {library}-biblioteek skrap?", "confirm_delete_library": "Is jy seker jy wil {library}-biblioteek uitvee?",
"confirm_delete_library_assets": "Is u seker u wil hierdie biblioteek skrap? Dit sal {count, plural, one {# bevatte item} other {# bevatte items}} uit Immich skrap en kan nie ongedaan gemaak word nie. LÃĒers sal op skyf bly.", "confirm_delete_library_assets": "Is jy seker jy wil hierdie biblioteek uitvee? Dit sal {count, plural, one {# bevatte base} other {# bevatte bates}} uit Immich uitvee en kan nie ongedaan gemaak word nie. LÃĒers sal op skyf bly.",
"confirm_email_below": "Tik “{email}” hieronder ter bevestiging", "confirm_email_below": "Om te bevestig, tik \"{email}\" hieronder",
"confirm_reprocess_all_faces": "Is u seker u wil alle gesigte herverwerk? Dit sal ook genoemde mense skoonmaak.", "confirm_reprocess_all_faces": "Is jy seker jy wil alle gesigte herverwerk? Dit sal ook genoemde mense skoonmaak.",
"confirm_user_password_reset": "Is u seker u wil {user} se wagwoord terugstel?", "confirm_user_password_reset": "Is jy seker jy wil {user} se wagwoord terugstel?",
"confirm_user_pin_code_reset": "Is u seker u wil {user} se PIN-kode herstel?", "confirm_user_pin_code_reset": "Is jy seker jy wil {user} se PIN kode herstel?",
"create_job": "Skep taak", "create_job": "Skep werk",
"cron_expression": "Cron-uitdrukking", "cron_expression": "Cron uitdrukking",
"cron_expression_description": "Stel die skanderingsinterval in met die cron-formaat. Kyk gerus na bv. <link>Crontab Guru</link> vir meer inligting", "cron_expression_description": "Stel die skanderingsinterval in met die cron-formaat. Vir meer inligting verwys asseblief na bv. <link>Crontab Guru</link>",
"cron_expression_presets": "Cron-uitdrukking voorafinstellings", "cron_expression_presets": "Cron uitdrukking voorafinstellings",
"disable_login": "Deaktiveer aantekening", "disable_login": "Deaktiveer aanmelding",
"duplicate_detection_job_description": "Begin masjienleer op items om soortgelyke beelde op te spoor. Maak staat op Slimsoek", "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 u lÃĒers en vouers ignoreer wanneer u u biblioteek skandeer. Dit is nuttig as u vouers het wat lÃĒers bevat wat u nie wil invoer nie, soos RAW-lÃĒers.", "exclusion_pattern_description": "Met uitsluitingspatrone kan jy lÃĒers en vouers ignoreer wanneer jy jou biblioteek skandeer. Dit is nuttig as jy vouers het wat lÃĒers bevat wat jy nie wil invoer nie, soos RAW-lÃĒers.",
"face_detection": "Gesigherkenning", "face_detection": "Gesig herkenning",
"face_detection_description": "Identifiseer die gesigte in media d.m.v. masjienleer. Vir video’s word slegs die duimnael oorweeg. “Herlaai” (ver)werk al die media weer. “Stel terug” verwyder alle huidige gesigdata. “Onverwerk” plaas items in die ry wat nog nie verwerk is nie. Geïdentifiseerde gesigte sal nÃĄ voltooiing van Gesigidentifikasie vir Gesigherkenning in die ry geplaas word om hulle in bestaande of nuwe persone te groepeer.", "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. Die stap is vinniger nadat Gesigherkenning klaar is. “Herstel” (her-)groepeer alle gesigte. “Vermiste” plaas gesigte in ry wat nie ’n persoon gekoppel het nie.", "facial_recognition_job_description": "Groepeer gesigte in mense in. Die stap is vinniger nadat Gesig Deteksie klaar is. \"Herstel\" (her-)groepeer alle gesigte. \"Vermiste\" plaas gesigte in ry wat nie 'n persoon gekoppel het nie.",
"failed_job_command": "Bevel {command} het misluk vir taak: {job}", "failed_job_command": "Opdrag {command} het misluk vir werk: {job}",
"force_delete_user_warning": "WAARSKUWING: Dit sal onmiddellik die gebruiker en alle items verwyder. Dit kan nie ontdaan word nie en die lÃĒers kan nie herstel word nie.", "force_delete_user_warning": "WAARSKUWING: Dit sal onmiddellik die gebruiker en alle bates verwyder. Dit kan nie ontdoen word nie en die lÃĒers kan nie herstel word nie.",
"image_format": "Formaat", "image_format": "Formaat",
"image_format_description": "WebP lewer kleiner lÃĒers as JPEG, maar is stadiger om te enkodeer.", "image_format_description": "WebP produseer kleiner lÃĒers as JPEG, maar is stadiger om te enkodeer.",
"image_fullsize_description": "Volgrootte prent met geen metadata, gebruik wanner ingezoem", "image_fullsize_description": "Vol grote prent met geen metadata, gebruik wanner ingezoem",
"image_fullsize_enabled": "Aktiveer spek van volgrootte prent", "image_fullsize_enabled": "Skakel aan vol grote prent generasie",
"image_prefer_embedded_preview": "Verkies ingebedde voorskou", "image_prefer_embedded_preview": "Verkies ingebedde voorskou",
"image_prefer_wide_gamut": "Verkies breÃĢspektrum", "image_prefer_wide_gamut": "Verkies wide gamut",
"image_prefer_wide_gamut_setting_description": "Gebruik Display P3 vir duimnaels. Dit behou die lewendheid van beelde met wye kleurruimtes beter, maar beelde kan anders verskyn op ou toestelle met ’n ou blaaierweergawe. sRGB-beelde gebruik steeds sRGB om kleurverskuiwings te voorkom.", "image_prefer_wide_gamut_setting_description": "Gebruik Display P3 vir kleinkiekies. Dit behou die lewendheid van beelde met wye kleurruimtes beter, maar beelde kan anders verskyn op ou apparate met 'n ou blaaierweergawe. sRGB-beelde gebruik steeds sRGB om kleurverskuiwings te voorkom.",
"image_preview_description": "Mediumgrootte prent met gestroopte metadata, wat gebruik word wanneer ’n enkele item bekyk word en vir masjienleer", "image_preview_description": "Mediumgrootte prent met gestroopte metadata, wat gebruik word wanneer 'n enkele bate bekyk word en vir masjienleer",
"image_preview_quality_description": "Voorskoukwaliteit van 1-100. HoÃĢr is beter, maar lewer groter lÃĒers en kan die toep vertraag. Die stel van ’n lae waarde kan masjienleerkwaliteit beïnvloed.", "image_preview_quality_description": "Voorskou kwaliteit van 1-100. HoÃĢr is beter, maar produseer groter lÃĒers en kan app-reaksie verminder. Die stel van 'n lae waarde kan masjienleerkwaliteit beïnvloed.",
"image_preview_title": "Voorskou-instellings", "image_preview_title": "Voorskou Instellings",
"image_quality": "Kwaliteit", "image_quality": "Kwaliteit",
"image_resolution": "Resolusie", "image_resolution": "Resolusie",
"image_resolution_description": "HoÃĢr resolusies kan meer detail bewaar, maar neem langer om te enkodeer, het groter lÃĒergroottes en kan die toep vertraag.", "image_resolution_description": "HoÃĢr resolusies kan meer detail bewaar, maar neem langer om te enkodeer, het groter lÃĒergroottes en kan app-reaksie verminder.",
"image_settings": "Prentinstellings", "image_settings": "Prent Instellings",
"image_settings_description": "Bestuur die kwaliteit en resolusie van gegenereerde beelde", "image_settings_description": "Bestuur die kwaliteit en resolusie van gegenereerde beelde",
"image_thumbnail_description": "Klein duimnaels sonder metadata, gebruik om groepe foto’s soos die tydlyn te bekyk", "image_thumbnail_description": "Klein kleinkiekies sonder metadata, gebruik om groepe foto's soos die tydlyn te bekyk",
"image_thumbnail_quality_description": "Duinmaelkwaliteit van 1-100. HoÃĢr is beter, maar lewer groter lÃĒers en kan die toep vertraag.", "image_thumbnail_quality_description": "Kleinkiekiekwaliteit van 1-100. HoÃĢr is beter, maar produseer groter lÃĒers en kan die toepassing vertraag.",
"image_thumbnail_title": "Duimnaelinstellings", "image_thumbnail_title": "Kleinkiekie-instellings",
"job_concurrency": "{job} gelyktydigheid", "job_concurrency": "{job} gelyktydigheid",
"job_created": "Taak geskep", "job_created": "Taak gemaak",
"job_not_concurrency_safe": "Hierdie taak kan nie gelyktydig uitgevoer word nie.", "job_not_concurrency_safe": "Hierdie taak kan nie gelyktydig uitgevoer word nie.",
"job_settings": "Taakinstellings", "job_settings": "Agtergrondtaakinstellings",
"job_settings_description": "Bestuur taakgelyktydigheid", "job_settings_description": "Bestuur werkgelyktydigheid",
"library_created": "Biblioteek geskep: {library}", "library_created": "Biblioteek geskep: {library}",
"library_deleted": "Biblioteek geskrap", "library_deleted": "Biblioteek verwyder",
"library_scanning": "Periodieke skandering", "library_scanning": "Periodieke Soek",
"library_scanning_description": "Stel periodieke skandering van biblioteek in", "library_scanning_description": "Stel periodieke deursoek van biblioteek in",
"library_scanning_enable_description": "Aktiveer periodieke biblioteekskandering", "library_scanning_enable_description": "Aktiveer periodieke biblioteekskandering",
"library_settings": "Eksterne biblioteek", "library_settings": "Eksterne Biblioteek",
"library_settings_description": "Eksternebiblioteekinstellings", "library_settings_description": "Eksterne biblioteek verstellings",
"library_tasks_description": "Skandeer eksterne biblioteke vir nuwe of veranderde items", "library_tasks_description": "Deursoek eksterne biblioteke vir nuwe of veranderde bates",
"library_watching_enable_description": "Hou eksterne biblioteke dop vir lÃĒerveranderinge", "library_watching_enable_description": "Hou eksterne biblioteke dop vir leer veranderinge",
"library_watching_settings": "Biblioteekdophou [EKSPERIMENTEEL]", "library_watching_settings": "Biblioteek dop hou (EKSPERIMENTEEL)",
"library_watching_settings_description": "Hou automaties dop vir veranderinge", "library_watching_settings_description": "Hou automaties dop vir veranderinge",
"logging_enable_description": "Aktiveer logboekbyhouding", "logging_enable_description": "Aktifeer \"logging\"",
"logging_level_description": "Wanneer aktief, welke logboekvlak om te gebruik.", "logging_level_description": "Wanneer aktief, watter vlak van \"logs\" om te skep.",
"logging_settings": "Logboek", "logging_settings": "\"Logs\"",
"machine_learning_clip_model": "CLIP-model", "machine_learning_clip_model": "CLIP model",
"machine_learning_duplicate_detection": "Duplikaatbespeuring", "machine_learning_duplicate_detection": "Duplikaat herkenning",
"machine_learning_duplicate_detection_enabled": "Aktiveer duplikaatbespeuring", "machine_learning_duplicate_detection_enabled": "Aktifeer duplikaat herkenning",
"machine_learning_enabled": "Aktiveer masjienleer", "machine_learning_enabled": "Aktifeer masjienleer",
"machine_learning_facial_recognition": "Gesigherkenning", "machine_learning_facial_recognition": "Gesigsherkenning",
"machine_learning_facial_recognition_description": "Bespeur, identifiseer en groepeer gesigte in foto’s", "machine_learning_facial_recognition_description": "Herken, identifiseer en groepeer gesigte in fotos",
"machine_learning_facial_recognition_model": "Gesigherkenningsmodel", "machine_learning_facial_recognition_model": "Gesigsherkennings model",
"machine_learning_facial_recognition_setting": "Aktiveer gesigherkenning", "machine_learning_facial_recognition_setting": "Aktifeer gesigsherkenning",
"machine_learning_max_detection_distance": "Maksimum herkenningsafstand", "machine_learning_max_detection_distance": "Maksimum herkennings afstand",
"map_settings": "Kaart", "map_settings": "Kaart",
"migration_job": "Migrasie", "migration_job": "Migrasie",
"oauth_settings": "OAuth", "oauth_settings": "OAuth",
"transcoding_acceleration_vaapi": "VAAPI", "transcoding_acceleration_vaapi": "VAAPI",
"transcoding_preferred_hardware_device": "Voorkeurapparatuur" "transcoding_preferred_hardware_device": "Verkiesde hardeware"
}, },
"administration": "Administrasie", "administration": "Administrasie",
"advanced": "Gevorderd", "advanced": "Gevorderde",
"albums": "Albums", "albums": "Albums",
"all": "Alle", "all": "Alle",
"anti_clockwise": "Linksom", "anti_clockwise": "Anti-kloksgewys",
"archive": "Argief", "archive": "Argief",
"asset_skipped": "Oorgeslaan", "asset_skipped": "Oorgeslaan",
"asset_uploaded": "Opgelaai", "asset_uploaded": "Opgelaai",
"asset_uploading": "Laai tans opâ€Ļ", "asset_uploading": "Oplaaiâ€Ļ",
"assets": "Items", "assets": "Bates",
"back": "Terug", "back": "Terug",
"backward": "Agteruit", "backward": "Agteruit",
"build": "Bou", "build": "Bou",
"camera": "Kamera", "camera": "Kamera",
"cancel": "Kanselleer", "cancel": "Kanselleer",
"city": "Stad", "city": "Stad",
"clockwise": "Regsom", "clockwise": "Kloksgewys",
"close": "Sluit", "close": "Maak toe",
"color": "Kleur", "color": "Kleur",
"confirm": "Bevestig", "confirm": "Bevestig",
"contain": "Bevat", "contain": "Bevat",
@@ -154,153 +154,54 @@
"created": "Geskep", "created": "Geskep",
"dark": "Donker", "dark": "Donker",
"day": "Dag", "day": "Dag",
"delete": "Skrap", "delete": "Verwyder",
"description": "Beskrywing", "description": "Beskrywing",
"details": "Besonderhede", "details": "Besonderhede",
"direction": "Rigting", "direction": "Rigting",
"discover": "Ontdek", "discover": "Ontdek",
"documentation": "Dokumentasie", "documentation": "Dokumentasie",
"done": "Gereed", "done": "Klaar",
"download": "Laai af", "download": "Aflaai",
"download_settings": "Laai af", "download_settings": "Aflaai",
"duplicates": "Duplikate", "duplicates": "Duplikate",
"duration": "Duur", "duration": "Duur",
"edit": "Wysig", "edit": "Wysig",
"search_by_description": "Soek op beskrywing", "search_by_description": "Soek by beskrywing",
"search_by_description_example": "Stapdag in Sapa", "search_by_description_example": "Stapdag in Sapa",
"stacktrace": "Stapelnasporing",
"start": "Begin",
"start_date": "Begindatum",
"start_date_before_end_date": "Begindatum moet voor einddatum wees",
"state": "Staat",
"status": "Status",
"stop_casting": "Stop sending",
"stop_motion_photo": "Stop bewegingsfoto",
"stop_photo_sharing": "Staak die deel van u foto’s?",
"stop_photo_sharing_description": "{partner} sal nie meer toegang tot u foto’s hÃĒ nie.",
"unnamed_share": "Naamlose deelskakel",
"unsaved_change": "Onbewaarde verandering",
"unselect_all": "Ontkies alles",
"unselect_all_duplicates": "Ontkies alle duplikate",
"unselect_all_in": "Ontkies alles in {group}",
"unstack": "Ontstapel",
"unstack_action_prompt": "{count} ongestapel",
"unstacked_assets_count": "{count, plural, one {# item} other {# items}} ontstapel",
"unsupported_field_type": "Onondersteunde veldtipe",
"unsupported_file_type": "LÃĒer {file} kan nie opgelaai word nie omdat die lÃĒertipe {type} nie ondersteun word nie.",
"untagged": "Sonder etiket",
"untitled_workflow": "Naamlose werkvloei",
"up_next": "Volgende",
"update_location_action_prompt": "Werk die ligging van {count} gekose items by met:",
"updated_at": "Bygewerk",
"updated_password": "Wagwoord bygewerk",
"upload": "Laai op",
"upload_concurrency": "Aantal gelyktydige oplaaie",
"upload_details": "Oplaaidetails",
"upload_dialog_info": "Wil u ’n rugsteun maak van die gekose item(s) op die bediener?",
"upload_dialog_title": "Laai item op",
"upload_error_with_count": "Oplaaifout vir {count, plural, one {# item} other {# items}}",
"upload_errors": "Oplaai voltooi met {count, plural, one {# fout} other {# foute}}, verfris die blad om die nuwe items te sien.",
"upload_finished": "Klaar opgelaai",
"upload_progress": "Oorblywend {remaining, number} - Verwerk {processed, number}/{total, number}",
"upload_skipped_duplicates": "{count, plural, one {# duplikaat item} other {# duplikaat items}} oorgeslaan",
"upload_status_duplicates": "Duplikate",
"upload_status_errors": "Foute",
"upload_status_uploaded": "Opgelaai",
"upload_success": "Oplaai suksesvol, verfris die blad om nuut opgelaaide items te sien.",
"upload_to_immich": "Laai op na Immich ({count})",
"uploading": "Word opgelaai",
"uploading_media": "Media word opgelaai",
"url": "URL",
"usage": "Gebruik",
"use_biometric": "Gebruik biometrie",
"use_browser_locale": "Gebruik blaaier se landinstelling",
"use_browser_locale_description": "Formatteer datums, tye en getalle gebaseer op u blaaier se landinstelling",
"use_current_connection": "Gebruik huidige verbinding",
"use_custom_date_range": "Gebruik eerder pasgemaakte datumomvang",
"user": "Gebruiker",
"user_has_been_deleted": "Hierdie gebruiker is geskrap.",
"user_id": "Gebruiker ID",
"user_liked": "{user} het van {type, select, photo {hierdie foto} video {hierdie video} asset {} other {hierdie item}} gehou",
"user_pin_code_settings": "PIN-kode",
"user_pin_code_settings_description": "Bestuur u PIN-kode",
"user_privacy": "Gebruikersprivaatheid",
"user_purchase_settings": "Koop",
"user_purchase_settings_description": "Bestuur u aankoop",
"user_role_set": "Stel {user} in as {role}",
"user_usage_detail": "Gedetailleerde gebruik van gebruikers",
"user_usage_stats": "Statistieke vir rekeninggebruik",
"user_usage_stats_description": "Bekyk statistieke van rekeninggebruik",
"username": "Gebruikersnaam",
"users": "Gebruikers",
"users_added_to_album_count": "{count, plural, one {# Gebruiker} other {# Gebruikers}} tot album toegevoeg",
"utilities": "Gereedskap",
"validate": "Valideer",
"validate_endpoint_error": "Voer asb. ’n geldige bronadres in",
"validation_error": "Valideerfout",
"variables": "Veranderlikes",
"version": "Weergawe", "version": "Weergawe",
"version_announcement_closing": "Jou friend, Alex", "version_announcement_closing": "Jou friend, Alex",
"version_announcement_message": "Hallo! Daar is ’n nuwe weergawe van Immich beskikbaar. Neem gerus bietjie tyd om die <link>vrystellingsnotas</link> te lees en maak seker u opstelling is op datum om wanopstellings te voorkom, veral as u WatchTower of ’n ander bywerkmeganisme gebruik.",
"version_history": "Weergawegeskiedenis", "version_history": "Weergawegeskiedenis",
"version_history_item": "{version} geïnstaleer op {date}", "version_history_item": "{version} geinstaleerd op {date}",
"video": "Video", "video": "Video",
"video_hover_setting": "Speel videoduimnael by muishang", "videos": "Video's",
"video_hover_setting_description": "Speel videoduimnael wanneer muis oor item hang. Selfs indien gedeaktiveer kan afspeel begin deur oor die afspeelknop te hang.",
"videos": "Video’s",
"videos_count": "{count, plural, one {# video} other {# video’s}}",
"videos_only": "Slegs video’s",
"view": "Bekyk", "view": "Bekyk",
"view_album": "Bekyk album", "view_album": "Bekyk Album",
"view_all": "Bekyk alle", "view_all": "Bekyk alle",
"view_all_users": "Bekyk alle gebruikers", "view_all_users": "Bekyk alle gebruikers",
"view_asset_owners": "Bekyk itemeienaars",
"view_details": "Bekyk detail",
"view_in_timeline": "Bekyk in tydlyn", "view_in_timeline": "Bekyk in tydlyn",
"view_link": "Bekyk skakel", "view_link": "Bekyk skakel",
"view_links": "Bekyk skakels", "view_links": "Bekyk skakels",
"view_name": "Bekyk", "view_name": "Bekyk",
"view_next_asset": "Bekyk volgende item", "view_next_asset": "Bekyk volgende bate",
"view_previous_asset": "Bekyk vorige item", "view_previous_asset": "Bekyk vorige bate",
"view_qr_code": "Bekyk QR-kode", "view_qr_code": "Bekyk QR-kode",
"view_similar_photos": "Bekyk soortgelyke foto’s",
"view_stack": "Bekyk stapel", "view_stack": "Bekyk stapel",
"view_user": "Bekyk gebruiker", "view_user": "Bekyk gebruiker",
"viewer_remove_from_stack": "Verwyder van stapel", "viewer_remove_from_stack": "Verwyder van stapel",
"viewer_stack_use_as_main_asset": "Gebruik as hoofitem", "viewer_stack_use_as_main_asset": "Gebruik as hoofbate",
"viewer_unstack": "Ontstapel", "viewer_unstack": "Ontstapel",
"visibility": "Sigbaarheid", "visibility_changed": "Sigbaarheid verander voor {count, plural, one {# person} other {# people}}",
"visibility_changed": "Sigbaarheid verander vir {count, plural, one {# mens} other {# mense}}",
"visual": "Visueel",
"visual_builder": "Visuele bouer",
"waiting": "Wag", "waiting": "Wag",
"waiting_count": "Wagtend: {count}", "warning": "Waaskuwing",
"warning": "Waarskuwing",
"week": "Week", "week": "Week",
"welcome": "Welkom", "welcome": "Welkom",
"welcome_to_immich": "Welkom by Immich", "welcome_to_immich": "Welkom by Immich",
"width": "Breedte", "wifi_name": "Wi-Fi Naam",
"wifi_name": "Wi-Fi-naam",
"workflow_delete_prompt": "Is u seker u wil hierdie werkvloei skrap?",
"workflow_deleted": "Werkvloei geskrap",
"workflow_description": "Werkvloeibeskrywing",
"workflow_info": "Werkvloei-inligting",
"workflow_json": "Werkvloei-JSON",
"workflow_json_help": "Wysig die werkvloei-opstelling in JSON-formaat. Veranderinge sal na die visuele bouer sinchroniseer.",
"workflow_name": "Werkvloeinaam",
"workflow_navigation_prompt": "Is u seker u wil verlaat sonder om u veranderinge te bewaar?",
"workflow_summary": "Werkvloei-opsomming",
"workflow_update_success": "Werkvloei suksesvol bygewerk",
"workflow_updated": "Werkvloei bygewerk",
"workflows": "Werkvloeie",
"workflows_help_text": "Werkvloeie outomatiseer aksies op u items gebaseer op snellers en filters",
"wrong_pin_code": "Verkeerde PIN-kode", "wrong_pin_code": "Verkeerde PIN-kode",
"year": "Jaar", "year": "Jaar",
"years_ago": "{years, plural, one {# jaar} other {# jaar}} gelede", "years_ago": "{years, plural, one {# year} other {# years}} gelede",
"yes": "Ja", "yes": "Ja",
"you_dont_have_any_shared_links": "U het geen gedeelde skakels nie", "you_dont_have_any_shared_links": "Jy het geen gedeelde skakels",
"your_wifi_name": "U Wi-Fi-naam", "your_wifi_name": "Jou Wi-Fi naam",
"zero_to_clear_rating": "druk 0 om itemgradering te wis", "zoom_image": "Vergroot Prent"
"zoom_image": "Zoem in",
"zoom_to_bounds": "Zoem na rande"
} }
+15 -33
View File
@@ -311,7 +311,7 @@
"search_jobs": "Ø§Ų„Ø¨Ø­ØĢ ØšŲ† ŲˆØ¸Ø§ØĻ؁â€Ļ", "search_jobs": "Ø§Ų„Ø¨Ø­ØĢ ØšŲ† ŲˆØ¸Ø§ØĻ؁â€Ļ",
"send_welcome_email": "ØĨØąØŗØ§Ų„ Ø¨ØąŲŠØ¯ ØĒØąØ­ŲŠØ¨ŲŠ", "send_welcome_email": "ØĨØąØŗØ§Ų„ Ø¨ØąŲŠØ¯ ØĒØąØ­ŲŠØ¨ŲŠ",
"server_external_domain_settings": "ØĨØŗŲ… Ø§Ų„Ų†ØˇØ§Ų‚ Ø§Ų„ØŽØ§ØąØŦ؊", "server_external_domain_settings": "ØĨØŗŲ… Ø§Ų„Ų†ØˇØ§Ų‚ Ø§Ų„ØŽØ§ØąØŦ؊",
"server_external_domain_settings_description": "Ø§Ų„Ų†ØˇØ§Ų‚ Ų…ØŗØĒØŽØ¯Ų… Ų„ØąŲˆØ§Ø¨Øˇ ØŽØ§ØąØŦŲŠØŠ", "server_external_domain_settings_description": "ØĨØŗŲ… Ø§Ų„Ų†ØˇØ§Ų‚ Ų„ØąŲˆØ§Ø¨Øˇ Ø§Ų„Ų…Ø´Ø§ØąŲƒØŠ Ø§Ų„ØšØ§Ų…ØŠØŒ Ø¨Ų…Ø§ ؁؊ Ø°Ų„Ųƒ http(s)://",
"server_public_users": "Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ŲˆŲ† Ø§Ų„ØšØ§Ų…ŲˆŲ†", "server_public_users": "Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ŲˆŲ† Ø§Ų„ØšØ§Ų…ŲˆŲ†",
"server_public_users_description": "؊ØĒŲ… ØĨØ¯ØąØ§ØŦ ØŦŲ…ŲŠØš Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ŲŠŲ† (Ø§Ų„Ø§ØŗŲ… ŲˆØ§Ų„Ø¨ØąŲŠØ¯ Ø§Ų„ØĨŲ„ŲƒØĒØąŲˆŲ†ŲŠ) ØšŲ†Ø¯ ØĨØļØ§ŲØŠ Ų…ØŗØĒØŽØ¯Ų… ØĨŲ„Ų‰ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ Ø§Ų„Ų…Ø´ØĒØąŲƒØŠ. ØšŲ†Ø¯ ØĒØšØˇŲŠŲ„ Ų‡Ø°Ų‡ Ø§Ų„Ų…ŲŠØ˛ØŠØŒ ØŗØĒŲƒŲˆŲ† Ų‚Ø§ØĻŲ…ØŠ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ŲŠŲ† Ų…ØĒاح؊ ŲŲ‚Øˇ Ų„Ų…ØŗØĒØŽØ¯Ų…ŲŠ Ø§Ų„ØĨØ¯Ø§ØąØŠ.", "server_public_users_description": "؊ØĒŲ… ØĨØ¯ØąØ§ØŦ ØŦŲ…ŲŠØš Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ŲŠŲ† (Ø§Ų„Ø§ØŗŲ… ŲˆØ§Ų„Ø¨ØąŲŠØ¯ Ø§Ų„ØĨŲ„ŲƒØĒØąŲˆŲ†ŲŠ) ØšŲ†Ø¯ ØĨØļØ§ŲØŠ Ų…ØŗØĒØŽØ¯Ų… ØĨŲ„Ų‰ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ Ø§Ų„Ų…Ø´ØĒØąŲƒØŠ. ØšŲ†Ø¯ ØĒØšØˇŲŠŲ„ Ų‡Ø°Ų‡ Ø§Ų„Ų…ŲŠØ˛ØŠØŒ ØŗØĒŲƒŲˆŲ† Ų‚Ø§ØĻŲ…ØŠ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ŲŠŲ† Ų…ØĒاح؊ ŲŲ‚Øˇ Ų„Ų…ØŗØĒØŽØ¯Ų…ŲŠ Ø§Ų„ØĨØ¯Ø§ØąØŠ.",
"server_settings": "ØĨؚداداØĒ Ø§Ų„ØŽØ§Ø¯Ų…", "server_settings": "ØĨؚداداØĒ Ø§Ų„ØŽØ§Ø¯Ų…",
@@ -411,7 +411,7 @@
"transcoding_tone_mapping": "ØąØŗŲ… Ø§Ų„ØŽØąØ§ØĻØˇ Ø§Ų„Ų†ØēŲ…ŲŠØŠ", "transcoding_tone_mapping": "ØąØŗŲ… Ø§Ų„ØŽØąØ§ØĻØˇ Ø§Ų„Ų†ØēŲ…ŲŠØŠ",
"transcoding_tone_mapping_description": "ØĒØ­Ø§ŲˆŲ„ Ø§Ų„Ø­ŲØ§Ø¸ ØšŲ„Ų‰ Ų…Ø¸Ų‡Øą Ų…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ HDR ØšŲ†Ø¯ ØĒØ­ŲˆŲŠŲ„Ų‡Ø§ ØĨŲ„Ų‰ SDR. ŲŠŲ‚Ø¯Ų… ŲƒŲ„ ØŽŲˆØ§ØąØ˛Ų…ŲŠØŠ ØĒŲ†Ø§Ø˛Ų„Ø§ØĒ Ų…ØŽØĒŲ„ŲØŠ Ø¨ŲŠŲ† Ø§Ų„Ų„ŲˆŲ† ŲˆØ§Ų„ØĒŲØ§ØĩŲŠŲ„ ŲˆØ§Ų„ØŗØˇŲˆØš. Hable ØĒØ­Ø§ŲØ¸ ØšŲ„Ų‰ Ø§Ų„ØĒŲØ§ØĩŲŠŲ„ØŒ Mobius ØĒØ­Ø§ŲØ¸ ØšŲ„Ų‰ Ø§Ų„ØŖŲ„ŲˆØ§Ų†ØŒ ؈ Reinhard ØĒØ­Ø§ŲØ¸ ØšŲ„Ų‰ Ø§Ų„ØŗØˇŲˆØš.", "transcoding_tone_mapping_description": "ØĒØ­Ø§ŲˆŲ„ Ø§Ų„Ø­ŲØ§Ø¸ ØšŲ„Ų‰ Ų…Ø¸Ų‡Øą Ų…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ HDR ØšŲ†Ø¯ ØĒØ­ŲˆŲŠŲ„Ų‡Ø§ ØĨŲ„Ų‰ SDR. ŲŠŲ‚Ø¯Ų… ŲƒŲ„ ØŽŲˆØ§ØąØ˛Ų…ŲŠØŠ ØĒŲ†Ø§Ø˛Ų„Ø§ØĒ Ų…ØŽØĒŲ„ŲØŠ Ø¨ŲŠŲ† Ø§Ų„Ų„ŲˆŲ† ŲˆØ§Ų„ØĒŲØ§ØĩŲŠŲ„ ŲˆØ§Ų„ØŗØˇŲˆØš. Hable ØĒØ­Ø§ŲØ¸ ØšŲ„Ų‰ Ø§Ų„ØĒŲØ§ØĩŲŠŲ„ØŒ Mobius ØĒØ­Ø§ŲØ¸ ØšŲ„Ų‰ Ø§Ų„ØŖŲ„ŲˆØ§Ų†ØŒ ؈ Reinhard ØĒØ­Ø§ŲØ¸ ØšŲ„Ų‰ Ø§Ų„ØŗØˇŲˆØš.",
"transcoding_transcode_policy": "ØŗŲŠØ§ØŗØŠ Ø§Ų„ØĒØąŲ…ŲŠØ˛", "transcoding_transcode_policy": "ØŗŲŠØ§ØŗØŠ Ø§Ų„ØĒØąŲ…ŲŠØ˛",
"transcoding_transcode_policy_description": "ØŗŲŠØ§ØŗØŠ ØĒØ­Ø¯ŲŠØ¯ Ų…ØĒŲ‰ ؊ØŦب ØĒØąŲ…ŲŠØ˛ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ. ØŗŲŠØĒŲ… داØĻŲ…Ų‹Ø§ ØĒØąŲ…ŲŠØ˛ Ų…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ HDR ؈ Ų…Ų‚Ø§ØˇØš Ø§Ų„ŲØ¯ŲŠŲˆ Ø§Ų„Ų„ØĒ؊ ØĒØŗØĒØ¯ØŽŲ… ØĒŲ†ØŗŲŠŲ‚ ØēŲŠØą YUV 4:2:0. (Ų…Ø§ Ų„Ų… ؊ØĒŲ… ØĒØšØˇŲŠŲ„ Ø§Ų„ØĒØąŲ…ŲŠØ˛).", "transcoding_transcode_policy_description": "ØŗŲŠØ§ØŗØŠ ØĒØ­Ø¯ŲŠØ¯ Ų…ØĒŲ‰ ؊ØŦب ØĒØąŲ…ŲŠØ˛ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ. ØŗŲŠØĒŲ… داØĻŲ…Ų‹Ø§ ØĒØąŲ…ŲŠØ˛ Ų…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ HDR (Ų…Ø§ Ų„Ų… ؊ØĒŲ… ØĒØšØˇŲŠŲ„ Ø§Ų„ØĒØąŲ…ŲŠØ˛).",
"transcoding_two_pass_encoding": "Ø§Ų„ØĒØąŲ…ŲŠØ˛ Ø¨Ų…ØąŲˆØąŲŠŲ†", "transcoding_two_pass_encoding": "Ø§Ų„ØĒØąŲ…ŲŠØ˛ Ø¨Ų…ØąŲˆØąŲŠŲ†",
"transcoding_two_pass_encoding_setting_description": "ØĒØąŲ…ŲŠØ˛ Ø¨Ų…ØąŲˆØąŲŠŲ† Ų„ØĨŲ†ØĒاØŦ Ų…Ų‚Ø§ØˇØš ŲŲŠØ¯ŲŠŲˆ بØĒØąŲ…ŲŠØ˛ ØŖŲØļŲ„. ØšŲ†Ø¯ ØĒŲ…ŲƒŲŠŲ† Ø§Ų„Ø­Ø¯ Ø§Ų„ØŖŲ‚ØĩŲ‰ Ų„Ų…ØšØ¯Ų„ Ø§Ų„Ø¨ØĒ (Ų…ØˇŲ„ŲˆØ¨ Ų„ŲƒŲŠ ŲŠØšŲ…Ų„ Ų…Øš H.264 ؈ HEVC)، ŲŠØŗØĒØŽØ¯Ų… Ų‡Ø°Ø§ Ø§Ų„ŲˆØļØš Ų†ØˇØ§Ų‚ Ų…ØšØ¯Ų„ Ø§Ų„Ø¨ØĒ Ø§ØŗØĒŲ†Ø§Ø¯Ų‹Ø§ ØĨŲ„Ų‰ Ø§Ų„Ø­Ø¯ Ø§Ų„ØŖŲ‚ØĩŲ‰ Ų„Ų…ØšØ¯Ų„ Ø§Ų„Ø¨ØĒ ؈؊ØĒØŦØ§Ų‡Ų„ CRF. Ø¨Ø§Ų„Ų†ØŗØ¨ØŠ Ų„Ų€ VP9، ŲŠŲ…ŲƒŲ† Ø§ØŗØĒØŽØ¯Ø§Ų… CRF ØĨذا ØĒŲ… ØĒØšØˇŲŠŲ„ Ø§Ų„Ø­Ø¯ Ø§Ų„ØŖŲ‚ØĩŲ‰ Ų„Ų…ØšØ¯Ų„ Ø§Ų„Ø¨ØĒ.", "transcoding_two_pass_encoding_setting_description": "ØĒØąŲ…ŲŠØ˛ Ø¨Ų…ØąŲˆØąŲŠŲ† Ų„ØĨŲ†ØĒاØŦ Ų…Ų‚Ø§ØˇØš ŲŲŠØ¯ŲŠŲˆ بØĒØąŲ…ŲŠØ˛ ØŖŲØļŲ„. ØšŲ†Ø¯ ØĒŲ…ŲƒŲŠŲ† Ø§Ų„Ø­Ø¯ Ø§Ų„ØŖŲ‚ØĩŲ‰ Ų„Ų…ØšØ¯Ų„ Ø§Ų„Ø¨ØĒ (Ų…ØˇŲ„ŲˆØ¨ Ų„ŲƒŲŠ ŲŠØšŲ…Ų„ Ų…Øš H.264 ؈ HEVC)، ŲŠØŗØĒØŽØ¯Ų… Ų‡Ø°Ø§ Ø§Ų„ŲˆØļØš Ų†ØˇØ§Ų‚ Ų…ØšØ¯Ų„ Ø§Ų„Ø¨ØĒ Ø§ØŗØĒŲ†Ø§Ø¯Ų‹Ø§ ØĨŲ„Ų‰ Ø§Ų„Ø­Ø¯ Ø§Ų„ØŖŲ‚ØĩŲ‰ Ų„Ų…ØšØ¯Ų„ Ø§Ų„Ø¨ØĒ ؈؊ØĒØŦØ§Ų‡Ų„ CRF. Ø¨Ø§Ų„Ų†ØŗØ¨ØŠ Ų„Ų€ VP9، ŲŠŲ…ŲƒŲ† Ø§ØŗØĒØŽØ¯Ø§Ų… CRF ØĨذا ØĒŲ… ØĒØšØˇŲŠŲ„ Ø§Ų„Ø­Ø¯ Ø§Ų„ØŖŲ‚ØĩŲ‰ Ų„Ų…ØšØ¯Ų„ Ø§Ų„Ø¨ØĒ.",
"transcoding_video_codec": "ØĒØąŲ…ŲŠØ˛ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ", "transcoding_video_codec": "ØĒØąŲ…ŲŠØ˛ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ",
@@ -441,7 +441,7 @@
"user_successfully_removed": "Ø§Ų„Ų…ØŗØĒØŽØ¯Ų… {email} ØĒŲ…ØĒ Ø§Ø˛Ø§Ų„ØĒŲ‡ Ø¨Ų†ØŦاح.", "user_successfully_removed": "Ø§Ų„Ų…ØŗØĒØŽØ¯Ų… {email} ØĒŲ…ØĒ Ø§Ø˛Ø§Ų„ØĒŲ‡ Ø¨Ų†ØŦاح.",
"users_page_description": "ØĩŲØ­ØŠ Ø§Ø¯Ø§ØąØŠ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ŲŠŲ†", "users_page_description": "ØĩŲØ­ØŠ Ø§Ø¯Ø§ØąØŠ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ŲŠŲ†",
"version_check_enabled_description": "ØĒŲØšŲŠŲ„ Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„ØĨØĩØ¯Ø§ØąØ§ØĒ Ø§Ų„ØŦØ¯ŲŠØ¯ØŠ", "version_check_enabled_description": "ØĒŲØšŲŠŲ„ Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„ØĨØĩØ¯Ø§ØąØ§ØĒ Ø§Ų„ØŦØ¯ŲŠØ¯ØŠ",
"version_check_implications": "ØĒØšØĒŲ…Ø¯ Ų…ŲŠØ˛ØŠ Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„ØĨØĩØ¯Ø§Øą ØšŲ„Ų‰ Ø§Ų„ØĒŲˆØ§ØĩŲ„ Ø§Ų„Ø¯ŲˆØąŲŠ Ų…Øš {server}", "version_check_implications": "ØĒØšØĒŲ…Ø¯ Ų…ŲŠØ˛ØŠ Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„ØĨØĩØ¯Ø§Øą ØšŲ„Ų‰ Ø§Ų„ØĒŲˆØ§ØĩŲ„ Ø§Ų„Ø¯ŲˆØąŲŠ Ų…Øš github.com",
"version_check_settings": "Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„ØĨØĩØ¯Ø§Øą", "version_check_settings": "Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„ØĨØĩØ¯Ø§Øą",
"version_check_settings_description": "ØĒŲØšŲŠŲ„/ØĒØšØˇŲŠŲ„ Ø§Ų„ØĨØ´ØšØ§Øą Ų„ØĨØĩØ¯Ø§Øą ØŦØ¯ŲŠØ¯", "version_check_settings_description": "ØĒŲØšŲŠŲ„/ØĒØšØˇŲŠŲ„ Ø§Ų„ØĨØ´ØšØ§Øą Ų„ØĨØĩØ¯Ø§Øą ØŦØ¯ŲŠØ¯",
"video_conversion_job": "ØĒØ­ŲˆŲŠŲ„ ØŖØ´ØąØˇØŠ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ", "video_conversion_job": "ØĒØ­ŲˆŲŠŲ„ ØŖØ´ØąØˇØŠ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ",
@@ -794,11 +794,6 @@
"color": "Ø§Ų„Ų„ŲˆŲ†", "color": "Ø§Ų„Ų„ŲˆŲ†",
"color_theme": "Ų†Ų…Øˇ Ø§Ų„ØŖŲ„ŲˆØ§Ų†", "color_theme": "Ų†Ų…Øˇ Ø§Ų„ØŖŲ„ŲˆØ§Ų†",
"command": "Ø§Ų…Øą", "command": "Ø§Ų…Øą",
"command_palette_prompt": "اؚØĢØą Ø¨ØŗØąØšØŠ ØšŲ„Ų‰ Ø§Ų„ØĩŲØ­Ø§ØĒ ØŖŲˆ Ø§Ų„ØĨØŦØąØ§ØĄØ§ØĒ ØŖŲˆ Ø§Ų„ØŖŲˆØ§Ų…Øą",
"command_palette_to_close": "Ų„Ų„Ø§ØēŲ„Ø§Ų‚",
"command_palette_to_navigate": "Ų„Ų„Ø¯ØŽŲˆŲ„",
"command_palette_to_select": "Ų„Ų„Ø§ØŽØĒŲŠØ§Øą",
"command_palette_to_show_all": "Ų„ØšØąØļ Ø§Ų„ŲƒŲ„",
"comment_deleted": "ØĒŲ… Ø­Ø°Ų Ø§Ų„ØĒØšŲ„ŲŠŲ‚", "comment_deleted": "ØĒŲ… Ø­Ø°Ų Ø§Ų„ØĒØšŲ„ŲŠŲ‚",
"comment_options": "ØŽŲŠØ§ØąØ§ØĒ Ø§Ų„ØĒØšŲ„ŲŠŲ‚", "comment_options": "ØŽŲŠØ§ØąØ§ØĒ Ø§Ų„ØĒØšŲ„ŲŠŲ‚",
"comments_and_likes": "Ø§Ų„ØĒØšŲ„ŲŠŲ‚Ø§ØĒ ŲˆØ§Ų„ØĨØšØŦاباØĒ", "comments_and_likes": "Ø§Ų„ØĒØšŲ„ŲŠŲ‚Ø§ØĒ ŲˆØ§Ų„ØĨØšØŦاباØĒ",
@@ -866,14 +861,13 @@
"crop_aspect_ratio_fixed": "ØĒŲ… Ø§Ų„Ø§ØĩŲ„Ø§Ø­", "crop_aspect_ratio_fixed": "ØĒŲ… Ø§Ų„Ø§ØĩŲ„Ø§Ø­",
"crop_aspect_ratio_free": "Ø­Øą", "crop_aspect_ratio_free": "Ø­Øą",
"crop_aspect_ratio_original": "اØĩŲ„ŲŠ", "crop_aspect_ratio_original": "اØĩŲ„ŲŠ",
"crop_aspect_ratio_square": "Ų…ØąØ¨Øš",
"curated_object_page_title": "ØŖØ´ŲŠØ§ØĄ", "curated_object_page_title": "ØŖØ´ŲŠØ§ØĄ",
"current_device": "Ø§Ų„ØŦŲ‡Ø§Ø˛ Ø§Ų„Ø­Ø§Ų„ŲŠ", "current_device": "Ø§Ų„ØŦŲ‡Ø§Ø˛ Ø§Ų„Ø­Ø§Ų„ŲŠ",
"current_pin_code": "ØąŲ…Ø˛ PIN Ø§Ų„Ø­Ø§Ų„ŲŠ", "current_pin_code": "ØąŲ…Ø˛ PIN Ø§Ų„Ø­Ø§Ų„ŲŠ",
"current_server_address": "ØšŲ†ŲˆØ§Ų† Ø§Ų„ØŽØ§Ø¯Ų… Ø§Ų„Ø­Ø§Ų„ŲŠ", "current_server_address": "ØšŲ†ŲˆØ§Ų† Ø§Ų„ØŽØ§Ø¯Ų… Ø§Ų„Ø­Ø§Ų„ŲŠ",
"custom_date": "ØĒØ§ØąŲŠØŽ Ų…ØŽØĩØĩ", "custom_date": "ØĒØ§ØąŲŠØŽ Ų…ØŽØĩØĩ",
"custom_locale": "Ų„ØēØŠ Ų…ØŽØĩØĩØŠ", "custom_locale": "Ų„ØēØŠ Ų…ØŽØĩØĩØŠ",
"custom_locale_description": "ØĒŲ†ØŗŲŠŲ‚ Ø§Ų„ØĒŲˆØ§ØąŲŠØŽ, Ø§Ų„ØŖŲˆŲ‚Ø§ØĒ ŲˆØ§Ų„ØŖØąŲ‚Ø§Ų… Ø¨Ų†Ø§ØĄŲ‹ ØšŲ„Ų‰ Ø§Ų„Ų„ØēØŠ ŲˆØ§Ų„Ų…Ų†ØˇŲ‚ØŠ Ø§Ų„Ų…ØŽØĒØ§ØąŲ‡", "custom_locale_description": "ØĒŲ†ØŗŲŠŲ‚ Ø§Ų„ØĒŲˆØ§ØąŲŠØŽ ŲˆØ§Ų„ØŖØąŲ‚Ø§Ų… Ø¨Ų†Ø§ØĄŲ‹ ØšŲ„Ų‰ Ø§Ų„Ų„ØēØŠ ŲˆØ§Ų„Ų…Ų†ØˇŲ‚ØŠ",
"custom_url": "ØąØ§Ø¨Øˇ Ų…ØŽØĩØĩ", "custom_url": "ØąØ§Ø¨Øˇ Ų…ØŽØĩØĩ",
"cutoff_date_description": "احØĒŲØ¸ Ø¨Ø§Ų„ØĩŲˆØą Ų…Ų† ØĸØŽØąâ€Ļ", "cutoff_date_description": "احØĒŲØ¸ Ø¨Ø§Ų„ØĩŲˆØą Ų…Ų† ØĸØŽØąâ€Ļ",
"cutoff_day": "{count, plural, one {ŲŠŲˆŲ…} other {Ø§ŲŠØ§Ų…}}", "cutoff_day": "{count, plural, one {ŲŠŲˆŲ…} other {Ø§ŲŠØ§Ų…}}",
@@ -881,7 +875,7 @@
"daily_title_text_date": "E ، MMM DD", "daily_title_text_date": "E ، MMM DD",
"daily_title_text_date_year": "E ، MMM DD ، yyyy", "daily_title_text_date_year": "E ، MMM DD ، yyyy",
"dark": "Ų…ØšØĒŲ…", "dark": "Ų…ØšØĒŲ…",
"dark_theme": "ØĒØ¨Ø¯ŲŠŲ„ Ø§Ų„Ų…Ø¸Ų‡Øą ØĨŲ„Ų‰ Ø§Ų„Ø¯Ø§ŲƒŲ†", "dark_theme": "ØĒØ¨Ø¯ŲŠŲ„ Ø§Ų„Ų…Ø¸Ų‡Øą Ø§Ų„Ø¯Ø§ŲƒŲ†",
"date": "ØĒØ§ØąŲŠØŽ", "date": "ØĒØ§ØąŲŠØŽ",
"date_after": "Ø§Ų„ØĒØ§ØąØŽ بؚد", "date_after": "Ø§Ų„ØĒØ§ØąØŽ بؚد",
"date_and_time": "Ø§Ų„ØĒØ§ØąŲŠØŽ ؈ Ø§Ų„ŲˆŲ‚ØĒ", "date_and_time": "Ø§Ų„ØĒØ§ØąŲŠØŽ ؈ Ø§Ų„ŲˆŲ‚ØĒ",
@@ -892,6 +886,12 @@
"day": "ŲŠŲˆŲ…", "day": "ŲŠŲˆŲ…",
"days": "Ø§ŲŠØ§Ų…", "days": "Ø§ŲŠØ§Ų…",
"deduplicate_all": "ØĨŲ„ØēØ§ØĄ ØĒŲƒØąØ§Øą Ø§Ų„ŲƒŲ„", "deduplicate_all": "ØĨŲ„ØēØ§ØĄ ØĒŲƒØąØ§Øą Ø§Ų„ŲƒŲ„",
"deduplication_criteria_1": "Ø­ØŦŲ… Ø§Ų„ØĩŲˆØąØŠ Ø¨ŲˆØ­Ø¯Ø§ØĒ Ø§Ų„Ø¨Ø§ŲŠØĒ",
"deduplication_criteria_2": "ؚدد Ø¨ŲŠØ§Ų†Ø§ØĒ EXIF",
"deduplication_info": "Ų…ØšŲ„ŲˆŲ…Ø§ØĒ ØĨŲ„ØēØ§ØĄ Ø§Ų„Ø¨ŲŠØ§Ų†Ø§ØĒ Ø§Ų„Ų…ŲƒØąØąØŠ",
"deduplication_info_description": "Ų„ØĒØ­Ø¯ŲŠØ¯ Ø§Ų„ØŖØĩŲˆŲ„ Ų…ØŗØ¨Ų‚Ø§ ØĒŲ„Ų‚Ø§ØĻŲŠØ§ ؈ØĨØ˛Ø§Ų„ØŠ Ø§Ų„ØĒŲƒØąØ§ØąØ§ØĒ Ø¨ŲƒŲ…ŲŠØ§ØĒ ŲƒØ¨ŲŠØąØŠØŒ Ų†Ų†Ø¸Øą ØĨŲ„Ų‰:",
"default_locale": "Ø§Ų„Ų„ØēØŠ Ø§Ų„Ø§ŲØĒØąØ§ØļŲŠØŠ",
"default_locale_description": "ØĒŲ†ØŗŲŠŲ‚ Ø§Ų„ØĒŲˆØ§ØąŲŠØŽ ŲˆØ§Ų„ØŖØąŲ‚Ø§Ų… Ø¨Ų†Ø§ØĄŲ‹ ØšŲ„Ų‰ Ų„ØēØŠ Ø§Ų„Ų…ØĒØĩŲØ­ Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ",
"delete": "Ø­Ø°Ų", "delete": "Ø­Ø°Ų",
"delete_action_confirmation_message": "Ų‡Ų„ Ø§Ų†ØĒ Ų…ØĒØŖŲƒØ¯ Ų…Ų† Ø­Ø°Ų Ų‡Ø°Ø§ Ø§Ų„Ų…Ų„ŲØŸ Ų‡Ø°Ø§ ØŗØ¤Ø¯ŲŠ Ø§Ų„Ų‰ Ų†Ų‚Ų„ Ø§Ų„Ų…Ų„Ų Ø§Ų„Ų‰ ØŗŲ„ØŠ Ų…Ų‡Ų…Ų„Ø§ØĒ Ø§Ų„ØŽØ§Ø¯Ų… ŲˆØŗŲŠØĒŲ… Ø§Ø´ØšØ§ØąŲƒ Ø§Ų† ŲƒŲ†ØĒ ØĒØąŲŠØ¯ Ø­Ø°ŲŲ‡ ØšŲ„Ų‰ Ø§Ų„ØŦŲ‡Ø§Ø˛", "delete_action_confirmation_message": "Ų‡Ų„ Ø§Ų†ØĒ Ų…ØĒØŖŲƒØ¯ Ų…Ų† Ø­Ø°Ų Ų‡Ø°Ø§ Ø§Ų„Ų…Ų„ŲØŸ Ų‡Ø°Ø§ ØŗØ¤Ø¯ŲŠ Ø§Ų„Ų‰ Ų†Ų‚Ų„ Ø§Ų„Ų…Ų„Ų Ø§Ų„Ų‰ ØŗŲ„ØŠ Ų…Ų‡Ų…Ų„Ø§ØĒ Ø§Ų„ØŽØ§Ø¯Ų… ŲˆØŗŲŠØĒŲ… Ø§Ø´ØšØ§ØąŲƒ Ø§Ų† ŲƒŲ†ØĒ ØĒØąŲŠØ¯ Ø­Ø°ŲŲ‡ ØšŲ„Ų‰ Ø§Ų„ØŦŲ‡Ø§Ø˛",
"delete_action_prompt": "ØĒŲ… Ø­Ø°Ų {count}", "delete_action_prompt": "ØĒŲ… Ø­Ø°Ų {count}",
@@ -967,7 +967,7 @@
"downloading_media": "ØĒŲ†Ø˛ŲŠŲ„ Ø§Ų„ŲˆØŗØ§ØĻØˇ", "downloading_media": "ØĒŲ†Ø˛ŲŠŲ„ Ø§Ų„ŲˆØŗØ§ØĻØˇ",
"drop_files_to_upload": "Ų‚Ų… بØĨØŗŲ‚Ø§Øˇ Ø§Ų„Ų…Ų„ŲØ§ØĒ ؁؊ ØŖŲŠ Ų…ŲƒØ§Ų† Ų„ØąŲØšŲ‡Ø§", "drop_files_to_upload": "Ų‚Ų… بØĨØŗŲ‚Ø§Øˇ Ø§Ų„Ų…Ų„ŲØ§ØĒ ؁؊ ØŖŲŠ Ų…ŲƒØ§Ų† Ų„ØąŲØšŲ‡Ø§",
"duplicates": "Ø§Ų„ØĒŲƒØąØ§ØąØ§ØĒ", "duplicates": "Ø§Ų„ØĒŲƒØąØ§ØąØ§ØĒ",
"duplicates_description": "Ų‚Ų… Ø¨Ø­Ų„ ŲƒŲ„ Ų…ØŦŲ…ŲˆØšØŠ Ų…Ų† ØŽŲ„Ø§Ų„ Ø§Ų„ØĨØ´Ø§ØąØŠ ØĨŲ„Ų‰ Ø§Ų„ØĒŲƒØąØ§ØąØ§ØĒ، ØĨŲ† ؈ØŦدØĒ.", "duplicates_description": "Ų‚Ų… Ø¨Ø­Ų„ ŲƒŲ„ Ų…ØŦŲ…ŲˆØšØŠ Ų…Ų† ØŽŲ„Ø§Ų„ Ø§Ų„ØĨØ´Ø§ØąØŠ ØĨŲ„Ų‰ Ø§Ų„ØĒŲƒØąØ§ØąØ§ØĒ، ØĨŲ† ؈ØŦدØĒ",
"duration": "Ø§Ų„Ų…Ø¯ØŠ", "duration": "Ø§Ų„Ų…Ø¯ØŠ",
"edit": "ØĒØšØ¯ŲŠŲ„", "edit": "ØĒØšØ¯ŲŠŲ„",
"edit_album": "ØĒØšØ¯ŲŠŲ„ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", "edit_album": "ØĒØšØ¯ŲŠŲ„ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…",
@@ -1004,8 +1004,6 @@
"editor_edits_applied_success": "ØĒŲ… ØĒØˇØ¨ŲŠŲ‚ Ø§Ų„ØĒØšØ¯ŲŠŲ„Ø§ØĒ Ø¨Ų†ØŦاح", "editor_edits_applied_success": "ØĒŲ… ØĒØˇØ¨ŲŠŲ‚ Ø§Ų„ØĒØšØ¯ŲŠŲ„Ø§ØĒ Ø¨Ų†ØŦاح",
"editor_flip_horizontal": "Ø§Ų‚Ų„Ø¨ ØŖŲŲ‚ŲŠŲ‹Ø§", "editor_flip_horizontal": "Ø§Ų‚Ų„Ø¨ ØŖŲŲ‚ŲŠŲ‹Ø§",
"editor_flip_vertical": "Ø§Ų‚Ų„Ø¨ ØšŲ…ŲˆØ¯ŲŠŲ‹Ø§", "editor_flip_vertical": "Ø§Ų‚Ų„Ø¨ ØšŲ…ŲˆØ¯ŲŠŲ‹Ø§",
"editor_handle_corner": "{corner, select, top_left {ØŖØšŲ„Ų‰ Ø§Ų„ŲŠØŗØ§Øą} top_right {ØŖØšŲ„Ų‰ Ø§Ų„ŲŠŲ…ŲŠŲ†} bottom_left {ØŖØŗŲŲ„ Ø§Ų„ŲŠØŗØ§Øą} bottom_right {ØŖØŗŲŲ„ Ø§Ų„ŲŠŲ…ŲŠŲ†} other {ØŖØŽØąŲŠ}} corner handle",
"editor_handle_edge": "{edge, select, top {ØŖØšŲ„ŲŠ} bottom {ØŖØŗŲŲ„} left {ŲŠØŗØ§Øą} right {ŲŠŲ…ŲŠŲ†} other {ØŖØŽØąŲŠ}} edge handle",
"editor_orientation": "اØĒØŦØ§Ų‡", "editor_orientation": "اØĒØŦØ§Ų‡",
"editor_reset_all_changes": "اؚاد؊ Ø¸Ø¨Øˇ Ø§Ų„ØĒØēŲŠŲŠØąØ§ØĒ", "editor_reset_all_changes": "اؚاد؊ Ø¸Ø¨Øˇ Ø§Ų„ØĒØēŲŠŲŠØąØ§ØĒ",
"editor_rotate_left": "ØŖØ¯Øą 90° ØšŲƒØŗ اØĒØŦØ§Ų‡ ØšŲ‚Ø§ØąØ¨ Ø§Ų„ØŗØ§ØšØŠ", "editor_rotate_left": "ØŖØ¯Øą 90° ØšŲƒØŗ اØĒØŦØ§Ų‡ ØšŲ‚Ø§ØąØ¨ Ø§Ų„ØŗØ§ØšØŠ",
@@ -1071,7 +1069,6 @@
"failed_to_update_notification_status": "ŲØ´Ų„ ؁؊ ØĒØ­Ø¯ŲŠØĢ Ø­Ø§Ų„ØŠ Ø§Ų„ØĨØ´ØšØ§Øą", "failed_to_update_notification_status": "ŲØ´Ų„ ؁؊ ØĒØ­Ø¯ŲŠØĢ Ø­Ø§Ų„ØŠ Ø§Ų„ØĨØ´ØšØ§Øą",
"incorrect_email_or_password": "Ø¨ØąŲŠØ¯ ØŖŲˆ ŲƒŲ„Ų…ØŠ Ų…ØąŲˆØą ØēŲŠØą ØĩØ­ŲŠØ­ØŠ", "incorrect_email_or_password": "Ø¨ØąŲŠØ¯ ØŖŲˆ ŲƒŲ„Ų…ØŠ Ų…ØąŲˆØą ØēŲŠØą ØĩØ­ŲŠØ­ØŠ",
"library_folder_already_exists": "Ų…ØŗØ§Øą Ø§Ų„Ø§ØŗØĒŲŠØąØ§Ø¯ Ų…ŲˆØŦŲˆØ¯ Ø¨Ø§Ų„ŲØšŲ„.", "library_folder_already_exists": "Ų…ØŗØ§Øą Ø§Ų„Ø§ØŗØĒŲŠØąØ§Ø¯ Ų…ŲˆØŦŲˆØ¯ Ø¨Ø§Ų„ŲØšŲ„.",
"page_not_found": "Ø§Ų„ØĩŲØ­ØŠ ØēŲŠØą Ų…ŲˆØŦŲˆØ¯ØŠ",
"paths_validation_failed": "ŲØ´Ų„ ؁؊ Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† {paths, plural, one {# Ų…ØŗØ§Øą} other {# Ų…ØŗØ§ØąØ§ØĒ}}", "paths_validation_failed": "ŲØ´Ų„ ؁؊ Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† {paths, plural, one {# Ų…ØŗØ§Øą} other {# Ų…ØŗØ§ØąØ§ØĒ}}",
"profile_picture_transparent_pixels": "Ų„Ø§ ŲŠŲ…ŲƒŲ† ØŖŲ† ØĒØ­ØĒ؈؊ ØĩŲˆØą Ø§Ų„Ų…Ų„Ų Ø§Ų„Ø´ØŽØĩ؊ ØšŲ„Ų‰ ØŖØŦØ˛Ø§ØĄ/Ø¨ŲƒØŗŲ„Ø§ØĒ Ø´ŲØ§ŲØŠ. ŲŠØąØŦŲ‰ Ø§Ų„ØĒŲƒØ¨ŲŠØą ؈/ØŖŲˆ ØĒØ­ØąŲŠŲƒ Ø§Ų„ØĩŲˆØąØŠ.", "profile_picture_transparent_pixels": "Ų„Ø§ ŲŠŲ…ŲƒŲ† ØŖŲ† ØĒØ­ØĒ؈؊ ØĩŲˆØą Ø§Ų„Ų…Ų„Ų Ø§Ų„Ø´ØŽØĩ؊ ØšŲ„Ų‰ ØŖØŦØ˛Ø§ØĄ/Ø¨ŲƒØŗŲ„Ø§ØĒ Ø´ŲØ§ŲØŠ. ŲŠØąØŦŲ‰ Ø§Ų„ØĒŲƒØ¨ŲŠØą ؈/ØŖŲˆ ØĒØ­ØąŲŠŲƒ Ø§Ų„ØĩŲˆØąØŠ.",
"quota_higher_than_disk_size": "Ų„Ų‚Ø¯ Ų‚Ų…ØĒ بØĒØšŲŠŲŠŲ† Ø­ØĩØŠ Ų†ØŗØ¨ŲŠØŠ ØŖØšŲ„Ų‰ Ų…Ų† Ø­ØŦŲ… Ø§Ų„Ų‚ØąØĩ", "quota_higher_than_disk_size": "Ų„Ų‚Ø¯ Ų‚Ų…ØĒ بØĒØšŲŠŲŠŲ† Ø­ØĩØŠ Ų†ØŗØ¨ŲŠØŠ ØŖØšŲ„Ų‰ Ų…Ų† Ø­ØŦŲ… Ø§Ų„Ų‚ØąØĩ",
@@ -1171,7 +1168,6 @@
"exif_bottom_sheet_people": "Ø§Ų„Ų†Ø§Øŗ", "exif_bottom_sheet_people": "Ø§Ų„Ų†Ø§Øŗ",
"exif_bottom_sheet_person_add_person": "اØļ؁ Ø§ØŗŲ…Ø§", "exif_bottom_sheet_person_add_person": "اØļ؁ Ø§ØŗŲ…Ø§",
"exit_slideshow": "ØŽØąŲˆØŦ Ų…Ų† Ø§Ų„ØšØąØļ Ø§Ų„ØĒŲ‚Ø¯ŲŠŲ…ŲŠ", "exit_slideshow": "ØŽØąŲˆØŦ Ų…Ų† Ø§Ų„ØšØąØļ Ø§Ų„ØĒŲ‚Ø¯ŲŠŲ…ŲŠ",
"expand": "ØĒŲˆØŗØšØŠ",
"expand_all": "ØĒŲˆØŗŲŠØš Ø§Ų„ŲƒŲ„", "expand_all": "ØĒŲˆØŗŲŠØš Ø§Ų„ŲƒŲ„",
"experimental_settings_new_asset_list_subtitle": "ØŖØšŲ…Ø§Ų„ ØŦØ§ØąŲŠØŠ", "experimental_settings_new_asset_list_subtitle": "ØŖØšŲ…Ø§Ų„ ØŦØ§ØąŲŠØŠ",
"experimental_settings_new_asset_list_title": "ØĒŲ…ŲƒŲŠŲ† Ø´Ø¨ŲƒØŠ Ø§Ų„ØĩŲˆØą Ø§Ų„ØĒØŦØąŲŠØ¨ŲŠØŠ", "experimental_settings_new_asset_list_title": "ØĒŲ…ŲƒŲŠŲ† Ø´Ø¨ŲƒØŠ Ø§Ų„ØĩŲˆØą Ø§Ų„ØĒØŦØąŲŠØ¨ŲŠØŠ",
@@ -1216,7 +1212,6 @@
"filter_description": "Ø´ØąŲˆØˇ ØĒØĩŲŲŠØŠ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ų…ØŗØĒŲ‡Ø¯ŲØŠ", "filter_description": "Ø´ØąŲˆØˇ ØĒØĩŲŲŠØŠ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ų…ØŗØĒŲ‡Ø¯ŲØŠ",
"filter_people": "ØĒØĩŲŲŠØŠ Ø§Ų„Ø§Ø´ØŽØ§Øĩ", "filter_people": "ØĒØĩŲŲŠØŠ Ø§Ų„Ø§Ø´ØŽØ§Øĩ",
"filter_places": "ØĒØĩŲŲŠØŠ Ø§Ų„Ø§Ų…Ø§ŲƒŲ†", "filter_places": "ØĒØĩŲŲŠØŠ Ø§Ų„Ø§Ų…Ø§ŲƒŲ†",
"filter_tags": "ØĒØĩŲŲŠØŠ Ø§Ų„ØšŲ„Ø§Ų…Ø§ØĒ",
"filters": "Ø§Ų„ØĒØĩŲŲŠØ§ØĒ", "filters": "Ø§Ų„ØĒØĩŲŲŠØ§ØĒ",
"find_them_fast": "ŲŠŲ…ŲƒŲ†Ųƒ Ø§Ų„ØšØĢŲˆØą ØšŲ„ŲŠŲ‡Ø§ Ø¨ØŗØąØšØŠ Ø¨Ø§Ų„Ø§ØŗŲ… Ų…Ų† ØŽŲ„Ø§Ų„ Ø§Ų„Ø¨Ø­ØĢ", "find_them_fast": "ŲŠŲ…ŲƒŲ†Ųƒ Ø§Ų„ØšØĢŲˆØą ØšŲ„ŲŠŲ‡Ø§ Ø¨ØŗØąØšØŠ Ø¨Ø§Ų„Ø§ØŗŲ… Ų…Ų† ØŽŲ„Ø§Ų„ Ø§Ų„Ø¨Ø­ØĢ",
"first": "Ø§Ų„Ø§ŲˆŲ„", "first": "Ø§Ų„Ø§ŲˆŲ„",
@@ -1647,8 +1642,6 @@
"online": "Ų…ØĒØĩŲ„", "online": "Ų…ØĒØĩŲ„",
"only_favorites": "Ø§Ų„Ų…ŲØļŲ„ØŠ ŲŲ‚Øˇ", "only_favorites": "Ø§Ų„Ų…ŲØļŲ„ØŠ ŲŲ‚Øˇ",
"open": "؁ØĒØ­", "open": "؁ØĒØ­",
"open_calendar": "Ø§ŲØĒØ­ Ø§Ų„ØąØ˛Ų†Ø§Ų…ØŠ",
"open_in_browser": "؁ØĒØ­ ؁؊ Ų…ØĒØĩŲØ­",
"open_in_map_view": "؁ØĒØ­ ؁؊ ØšØąØļ Ø§Ų„ØŽØąŲŠØˇØŠ", "open_in_map_view": "؁ØĒØ­ ؁؊ ØšØąØļ Ø§Ų„ØŽØąŲŠØˇØŠ",
"open_in_openstreetmap": "؁ØĒØ­ ؁؊ OpenStreetMap", "open_in_openstreetmap": "؁ØĒØ­ ؁؊ OpenStreetMap",
"open_the_search_filters": "Ø§ŲØĒØ­ Ų…ØąØ´Ø­Ø§ØĒ Ø§Ų„Ø¨Ø­ØĢ", "open_the_search_filters": "Ø§ŲØĒØ­ Ų…ØąØ´Ø­Ø§ØĒ Ø§Ų„Ø¨Ø­ØĢ",
@@ -1808,8 +1801,9 @@
"rate_asset": "ØĒŲ‚ŲŠŲŠŲ… Ø§Ų„Ø§ØĩŲ„", "rate_asset": "ØĒŲ‚ŲŠŲŠŲ… Ø§Ų„Ø§ØĩŲ„",
"rating": "ØĒŲ‚ŲŠŲŠŲ… Ų†ØŦŲ…ŲŠ", "rating": "ØĒŲ‚ŲŠŲŠŲ… Ų†ØŦŲ…ŲŠ",
"rating_clear": "Ų…ØŗØ­ Ø§Ų„ØĒŲ‚ŲŠŲŠŲ…", "rating_clear": "Ų…ØŗØ­ Ø§Ų„ØĒŲ‚ŲŠŲŠŲ…",
"rating_count": "{count, plural, =0 {Unrated} one {# Ų†ØŦŲ…ØŠ} other {# Ų†ØŦŲˆŲ…}}", "rating_count": "{count, plural, one {# Ų†ØŦŲ…ØŠ} other {# Ų†ØŦŲˆŲ…}}",
"rating_description": "â€Ģâ€ŒØ§ØšØąØļ ØĒŲ‚ŲŠŲŠŲ… EXIF ؁؊ Ų„ŲˆØ­ØŠ Ø§Ų„Ų…ØšŲ„ŲˆŲ…Ø§ØĒ", "rating_description": "â€Ģâ€ŒØ§ØšØąØļ ØĒŲ‚ŲŠŲŠŲ… EXIF ؁؊ Ų„ŲˆØ­ØŠ Ø§Ų„Ų…ØšŲ„ŲˆŲ…Ø§ØĒ",
"rating_set": "ØĒŲ… ØĒØ­Ø¯ŲŠØ¯ Ø§Ų„ØĒØĩŲ†ŲŠŲ {rating, plural, one {# Ų†ØŦŲ…ØŠ} other {# Ų†ØŦŲˆŲ…}}",
"reaction_options": "ØŽŲŠØ§ØąØ§ØĒ ØąØ¯ Ø§Ų„ŲØšŲ„", "reaction_options": "ØŽŲŠØ§ØąØ§ØĒ ØąØ¯ Ø§Ų„ŲØšŲ„",
"read_changelog": "Ų‚ØąØ§ØĄØŠ ØŗØŦŲ„ Ø§Ų„ØĒØēŲŠŲŠØą", "read_changelog": "Ų‚ØąØ§ØĄØŠ ØŗØŦŲ„ Ø§Ų„ØĒØēŲŠŲŠØą",
"readonly_mode_disabled": "ØĒŲ… ØĒØšØˇŲŠŲ„ ؈ØļØš Ø§Ų„Ų‚ØąØ§ØĄØŠ ŲŲ‚Øˇ", "readonly_mode_disabled": "ØĒŲ… ØĒØšØˇŲŠŲ„ ؈ØļØš Ø§Ų„Ų‚ØąØ§ØĄØŠ ŲŲ‚Øˇ",
@@ -1881,10 +1875,7 @@
"reset_pin_code_success": "ØĒŲ… اؚاد؊ ØĒØšŲŠŲŠŲ† ØąŲ…Ø˛ Ø§Ų„PIN Ø¨Ų†ØŦاح", "reset_pin_code_success": "ØĒŲ… اؚاد؊ ØĒØšŲŠŲŠŲ† ØąŲ…Ø˛ Ø§Ų„PIN Ø¨Ų†ØŦاح",
"reset_pin_code_with_password": "ŲŠŲ…ŲƒŲ†Ųƒ داØĻŲ…Ø§ اؚاد؊ ØĒØšŲŠŲŠŲ† ØąŲ…Ø˛ Ø§Ų„PIN Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ ØšŲ† ØˇØąŲŠŲ‚ ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą Ø§Ų„ØŽØ§ØĩØŠ Ø¨Ųƒ", "reset_pin_code_with_password": "ŲŠŲ…ŲƒŲ†Ųƒ داØĻŲ…Ø§ اؚاد؊ ØĒØšŲŠŲŠŲ† ØąŲ…Ø˛ Ø§Ų„PIN Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ ØšŲ† ØˇØąŲŠŲ‚ ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą Ø§Ų„ØŽØ§ØĩØŠ Ø¨Ųƒ",
"reset_sqlite": "ØĨؚاد؊ ØĒØšŲŠŲŠŲ† Ų‚Ø§ØšØ¯ØŠ Ø¨ŲŠØ§Ų†Ø§ØĒ SQLite", "reset_sqlite": "ØĨؚاد؊ ØĒØšŲŠŲŠŲ† Ų‚Ø§ØšØ¯ØŠ Ø¨ŲŠØ§Ų†Ø§ØĒ SQLite",
"reset_sqlite_clear_app_data": "Ų…ØŗØ­ Ø§Ų„Ø¨ŲŠØ§Ų†Ø§ØĒ", "reset_sqlite_confirmation": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ Ų…Ų† ØąØēبØĒ؃ ؁؊ ØĨؚاد؊ ØļØ¨Øˇ Ų‚Ø§ØšØ¯ØŠ Ø¨ŲŠØ§Ų†Ø§ØĒ SQLite؟ ØŗØĒØ­ØĒاØŦ ØĨŲ„Ų‰ ØĒØŗØŦŲŠŲ„ Ø§Ų„ØŽØąŲˆØŦ ØĢŲ… ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„ Ų…ØąØŠ ØŖØŽØąŲ‰ Ų„ØĨؚاد؊ Ų…Ø˛Ø§Ų…Ų†ØŠ Ø§Ų„Ø¨ŲŠØ§Ų†Ø§ØĒ",
"reset_sqlite_confirmation": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ Ų…Ų† ØąØēبØĒ؃ ؁؊ Ø­Ø°Ų ØļØ¨Øˇ Ø¨ŲŠØ§Ų†Ø§ØĒ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚ØŸ ØŗŲŠØ¤Ø¯ŲŠ Ų‡Ø°Ø§ ØĨŲ„Ų‰ ØĨØ˛Ø§Ų„ØŠ ØŦŲ…ŲŠØš Ø§Ų„ØĨؚداداØĒ ؈ØĒØŗØŦŲŠŲ„ ØŽØąŲˆØŦ؃.",
"reset_sqlite_confirmation_note": "Ų…Ų„Ø§Ø­Ø¸ØŠ: ØŗŲŠØĒØšŲŠŲ† ØšŲ„ŲŠŲƒ ØĨؚاد؊ ØĒØ´ØēŲŠŲ„ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚ بؚد Ø§Ų„Ų…ØŗØ­.",
"reset_sqlite_done": "ØĒŲ… Ų…ØŗØ­ Ø¨ŲŠØ§Ų†Ø§ØĒ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚. ŲŠØąØŦŲ‰ ØĨؚاد؊ ØĒØ´ØēŲŠŲ„ ØĒØˇØ¨ŲŠŲ‚ Immich ؈ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„ Ų…ØąØŠ ØŖØŽØąŲ‰.",
"reset_sqlite_success": "ØĒŲ… ØĨؚاد؊ ØĒØšŲŠŲŠŲ† Ų‚Ø§ØšØ¯ØŠ Ø¨ŲŠØ§Ų†Ø§ØĒ SQLite Ø¨Ų†ØŦاح", "reset_sqlite_success": "ØĒŲ… ØĨؚاد؊ ØĒØšŲŠŲŠŲ† Ų‚Ø§ØšØ¯ØŠ Ø¨ŲŠØ§Ų†Ø§ØĒ SQLite Ø¨Ų†ØŦاح",
"reset_to_default": "ØĨؚاد؊ Ø§Ų„ØĒØšŲŠŲŠŲ† ØĨŲ„Ų‰ Ø§Ų„Ø§ŲØĒØąØ§Øļ؊", "reset_to_default": "ØĨؚاد؊ Ø§Ų„ØĒØšŲŠŲŠŲ† ØĨŲ„Ų‰ Ø§Ų„Ø§ŲØĒØąØ§Øļ؊",
"resolution": "Ø¯Ų‚ØŠ", "resolution": "Ø¯Ų‚ØŠ",
@@ -1912,7 +1903,6 @@
"saved_settings": "ØĒŲ… Ø­ŲØ¸ Ø§Ų„ØĨؚداداØĒ", "saved_settings": "ØĒŲ… Ø­ŲØ¸ Ø§Ų„ØĨؚداداØĒ",
"say_something": "Ų‚Ų„ Ø´ŲŠØĻŲ‹Ø§", "say_something": "Ų‚Ų„ Ø´ŲŠØĻŲ‹Ø§",
"scaffold_body_error_occurred": "حدØĢ ØŽØˇØŖ", "scaffold_body_error_occurred": "حدØĢ ØŽØˇØŖ",
"scaffold_body_error_unrecoverable": "حدØĢ ØŽØˇØŖ Ų„Ø§ ŲŠŲ…ŲƒŲ† ØĨØĩŲ„Ø§Ø­Ų‡. ŲŠØąØŦŲ‰ Ų…Ø´Ø§ØąŲƒØŠ ØĒŲØ§ØĩŲŠŲ„ Ø§Ų„ØŽØˇØŖ ؈ØĒØŗŲ„ØŗŲ„ Ø§Ų„ØŖØŽØˇØ§ØĄ ØšŲ„Ų‰ Discord ØŖŲˆ GitHub Ø­ØĒŲ‰ Ų†ØĒŲ…ŲƒŲ† Ų…Ų† Ų…ØŗØ§ØšØ¯ØĒ؃. ØĨذا ØˇŲŲ„Ø¨ Ų…Ų†Ųƒ Ø°Ų„ŲƒØŒ ŲŠŲ…ŲƒŲ†Ųƒ Ų…ØŗØ­ Ø¨ŲŠØ§Ų†Ø§ØĒ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚ ØŖØ¯Ų†Ø§Ų‡.",
"scan": "بحØĢ", "scan": "بحØĢ",
"scan_all_libraries": "ŲØ­Øĩ ŲƒŲ„ Ø§Ų„Ų…ŲƒØĒباØĒ", "scan_all_libraries": "ŲØ­Øĩ ŲƒŲ„ Ø§Ų„Ų…ŲƒØĒباØĒ",
"scan_library": "Ų…ØŗØ­", "scan_library": "Ų…ØŗØ­",
@@ -1948,7 +1938,6 @@
"search_filter_ocr": "Ø§Ų„Ø¨Ø­ØĢ ØšŲ† ØˇØąŲŠŲ‚ Ø§Ų„ØĒØšØąŲ Ø§Ų„Ø¨ØĩØąŲŠ ØšŲ„Ų‰ Ø§Ų„Ø­ØąŲˆŲ", "search_filter_ocr": "Ø§Ų„Ø¨Ø­ØĢ ØšŲ† ØˇØąŲŠŲ‚ Ø§Ų„ØĒØšØąŲ Ø§Ų„Ø¨ØĩØąŲŠ ØšŲ„Ų‰ Ø§Ų„Ø­ØąŲˆŲ",
"search_filter_people_title": "ا؎ØĒØą Ø§Ų„Ø§Ø´ØŽØ§Øĩ", "search_filter_people_title": "ا؎ØĒØą Ø§Ų„Ø§Ø´ØŽØ§Øĩ",
"search_filter_star_rating": "ØĒŲ‚ŲŠŲŠŲ… Ø§Ų„Ų†ØŦŲˆŲ…", "search_filter_star_rating": "ØĒŲ‚ŲŠŲŠŲ… Ø§Ų„Ų†ØŦŲˆŲ…",
"search_filter_tags_title": "â€ĒØĒØ­Ø¯ŲŠØ¯ Ø§Ų„ØšŲ„Ø§Ų…Ø§ØĒ",
"search_for": "Ø§Ų„Ø¨Ø­ØĢ ØšŲ†", "search_for": "Ø§Ų„Ø¨Ø­ØĢ ØšŲ†",
"search_for_existing_person": "Ø§Ų„Ø¨Ø­ØĢ ØšŲ† Ø´ØŽØĩ Ų…ŲˆØŦŲˆØ¯", "search_for_existing_person": "Ø§Ų„Ø¨Ø­ØĢ ØšŲ† Ø´ØŽØĩ Ų…ŲˆØŦŲˆØ¯",
"search_no_more_result": "Ų„Ø§ ØĒ؈ØŦد Ų†ØĒاØĻØŦ اØļØ§ŲŲŠØŠ", "search_no_more_result": "Ų„Ø§ ØĒ؈ØŦد Ų†ØĒاØĻØŦ اØļØ§ŲŲŠØŠ",
@@ -2028,9 +2017,6 @@
"set_profile_picture": "ØĒØ­Ø¯ŲŠØ¯ ØĩŲˆØąØŠ Ø§Ų„Ų…Ų„Ų Ø§Ų„Ø´ØŽØĩ؊", "set_profile_picture": "ØĒØ­Ø¯ŲŠØ¯ ØĩŲˆØąØŠ Ø§Ų„Ų…Ų„Ų Ø§Ų„Ø´ØŽØĩ؊",
"set_slideshow_to_fullscreen": "ØĒØ­Ø¯ŲŠØ¯ ØšØąØļ Ø§Ų„Ø´ØąØ§ØĻØ­ ØšŲ„Ų‰ ؈ØļØš Ų…Ų„ØĄ Ø§Ų„Ø´Ø§Ø´ØŠ", "set_slideshow_to_fullscreen": "ØĒØ­Ø¯ŲŠØ¯ ØšØąØļ Ø§Ų„Ø´ØąØ§ØĻØ­ ØšŲ„Ų‰ ؈ØļØš Ų…Ų„ØĄ Ø§Ų„Ø´Ø§Ø´ØŠ",
"set_stack_primary_asset": "ØĒØšŲŠŲŠŲ† ŲƒØŖØĩŲ„ Ø§ØŗØ§ØŗŲŠ", "set_stack_primary_asset": "ØĒØšŲŠŲŠŲ† ŲƒØŖØĩŲ„ Ø§ØŗØ§ØŗŲŠ",
"setting_image_navigation_enable_subtitle": "؁؊ Ø­Ø§Ų„ ØĒŲ… Ø§Ų„ØĒŲØšŲŠŲ„ØŒ ŲŠŲ…ŲƒŲ†Ųƒ Ø§Ų„Ø§Ų†ØĒŲ‚Ø§Ų„ ØĨŲ„Ų‰ Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„ØŗØ§Ø¨Ų‚ØŠ ØŖŲˆ Ø§Ų„ØĒØ§Ų„ŲŠØŠ ØšŲ† ØˇØąŲŠŲ‚ Ø§Ų„Ų†Ų‚Øą ØšŲ„Ų‰ Ø§Ų„ØąØ¨Øš Ø§Ų„ØŖŲŠØŗØą ØŖŲˆ Ø§Ų„ØąØ¨Øš Ø§Ų„ØŖŲŠŲ…Ų† Ų…Ų† Ø§Ų„Ø´Ø§Ø´ØŠ.",
"setting_image_navigation_enable_title": "Ø§Ų„Ų†Ų‚Øą Ų„Ų„ØĒŲ†Ų‚Ų„",
"setting_image_navigation_title": "Ø§Ų„ØĒŲ†Ų‚Ų„ Ø¨ŲŠŲ† Ø§Ų„ØĩŲˆØą",
"setting_image_viewer_help": "ŲŠŲ‚ŲˆŲ… ØšØ§ØąØļ Ø§Ų„ØĒŲØ§ØĩŲŠŲ„ بØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„Ų…ØĩØēØąØŠ Ø§Ų„ØĩØēŲŠØąØŠ ØŖŲˆŲ„Ø§Ų‹ ، ØĢŲ… ŲŠŲ‚ŲˆŲ… بØĒØ­Ų…ŲŠŲ„ Ø§Ų„Ų…ØšØ§ŲŠŲ†ØŠ Ų…ØĒŲˆØŗØˇØŠ Ø§Ų„Ø­ØŦŲ… (ØĨذا ØĒŲ… ØĒŲ…ŲƒŲŠŲ†Ų‡Ø§) ، ŲˆŲŠŲ‚ŲˆŲ… ØŖØŽŲŠØąŲ‹Ø§ بØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØŖØĩŲ„ (ØĨذا ØĒŲ… ØĒŲ…ŲƒŲŠŲ†Ų‡).", "setting_image_viewer_help": "ŲŠŲ‚ŲˆŲ… ØšØ§ØąØļ Ø§Ų„ØĒŲØ§ØĩŲŠŲ„ بØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„Ų…ØĩØēØąØŠ Ø§Ų„ØĩØēŲŠØąØŠ ØŖŲˆŲ„Ø§Ų‹ ، ØĢŲ… ŲŠŲ‚ŲˆŲ… بØĒØ­Ų…ŲŠŲ„ Ø§Ų„Ų…ØšØ§ŲŠŲ†ØŠ Ų…ØĒŲˆØŗØˇØŠ Ø§Ų„Ø­ØŦŲ… (ØĨذا ØĒŲ… ØĒŲ…ŲƒŲŠŲ†Ų‡Ø§) ، ŲˆŲŠŲ‚ŲˆŲ… ØŖØŽŲŠØąŲ‹Ø§ بØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØŖØĩŲ„ (ØĨذا ØĒŲ… ØĒŲ…ŲƒŲŠŲ†Ų‡).",
"setting_image_viewer_original_subtitle": "ØĒŲ…ŲƒŲŠŲ† ØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„ŲƒØ§Ų…Ų„ØŠ Ø§Ų„Ø¯Ų‚ØŠ Ø§Ų„ØŖØĩŲ„ŲŠØŠ (ŲƒØ¨ŲŠØąØŠ!).ØĒØšØˇŲŠŲ„ Ų„ØĒŲ‚Ų„ŲŠŲ„ Ø§ØŗØĒØŽØ¯Ø§Ų… Ø§Ų„Ø¨ŲŠØ§Ų†Ø§ØĒ (ŲƒŲ„ Ų…Ų† Ø§Ų„Ø´Ø¨ŲƒØŠ ŲˆØšŲ„Ų‰ Ø°Ø§ŲƒØąØŠ Ø§Ų„ØĒØŽØ˛ŲŠŲ† Ø§Ų„Ų…Ø¤Ų‚ØĒ Ų„Ų„ØŦŲ‡Ø§Ø˛).", "setting_image_viewer_original_subtitle": "ØĒŲ…ŲƒŲŠŲ† ØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„ŲƒØ§Ų…Ų„ØŠ Ø§Ų„Ø¯Ų‚ØŠ Ø§Ų„ØŖØĩŲ„ŲŠØŠ (ŲƒØ¨ŲŠØąØŠ!).ØĒØšØˇŲŠŲ„ Ų„ØĒŲ‚Ų„ŲŠŲ„ Ø§ØŗØĒØŽØ¯Ø§Ų… Ø§Ų„Ø¨ŲŠØ§Ų†Ø§ØĒ (ŲƒŲ„ Ų…Ų† Ø§Ų„Ø´Ø¨ŲƒØŠ ŲˆØšŲ„Ų‰ Ø°Ø§ŲƒØąØŠ Ø§Ų„ØĒØŽØ˛ŲŠŲ† Ø§Ų„Ų…Ø¤Ų‚ØĒ Ų„Ų„ØŦŲ‡Ø§Ø˛).",
"setting_image_viewer_original_title": "ØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„ØŖØĩŲ„ŲŠØŠ", "setting_image_viewer_original_title": "ØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„ØŖØĩŲ„ŲŠØŠ",
@@ -2197,7 +2183,6 @@
"support": "Ø§Ų„Ø¯ØšŲ…", "support": "Ø§Ų„Ø¯ØšŲ…",
"support_and_feedback": "Ø§Ų„Ø¯ØšŲ… ŲˆØ§Ų„ØĒØšŲ„ŲŠŲ‚Ø§ØĒ", "support_and_feedback": "Ø§Ų„Ø¯ØšŲ… ŲˆØ§Ų„ØĒØšŲ„ŲŠŲ‚Ø§ØĒ",
"support_third_party_description": "ØĒŲ… Ø­Ø˛Ų… ØĒØĢØ¨ŲŠØĒ immich Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ Ø¨ŲˆØ§ØŗØˇØŠ ØŦŲ‡ØŠ ØŽØ§ØąØŦŲŠØŠ. Ų‚Ø¯ ØĒŲƒŲˆŲ† Ø§Ų„Ų…Ø´ŲƒŲ„Ø§ØĒ Ø§Ų„ØĒ؊ ØĒŲˆØ§ØŦŲ‡Ų‡Ø§ Ų†Ø§ØŦŲ…ØŠ ØšŲ† Ų‡Ø°Ų‡ Ø§Ų„Ø­Ø˛Ų…ØŠØŒ Ų„Ø°Ø§ ŲŠØąØŦŲ‰ ØˇØąØ­ Ø§Ų„Ų…Ø´ŲƒŲ„Ø§ØĒ Ų…ØšŲ‡Ų… ؁؊ Ø§Ų„Ų…Ų‚Ø§Ų… Ø§Ų„ØŖŲˆŲ„ Ø¨Ø§ØŗØĒØŽØ¯Ø§Ų… Ø§Ų„ØąŲˆØ§Ø¨Øˇ ØŖØ¯Ų†Ø§Ų‡.", "support_third_party_description": "ØĒŲ… Ø­Ø˛Ų… ØĒØĢØ¨ŲŠØĒ immich Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ Ø¨ŲˆØ§ØŗØˇØŠ ØŦŲ‡ØŠ ØŽØ§ØąØŦŲŠØŠ. Ų‚Ø¯ ØĒŲƒŲˆŲ† Ø§Ų„Ų…Ø´ŲƒŲ„Ø§ØĒ Ø§Ų„ØĒ؊ ØĒŲˆØ§ØŦŲ‡Ų‡Ø§ Ų†Ø§ØŦŲ…ØŠ ØšŲ† Ų‡Ø°Ų‡ Ø§Ų„Ø­Ø˛Ų…ØŠØŒ Ų„Ø°Ø§ ŲŠØąØŦŲ‰ ØˇØąØ­ Ø§Ų„Ų…Ø´ŲƒŲ„Ø§ØĒ Ų…ØšŲ‡Ų… ؁؊ Ø§Ų„Ų…Ų‚Ø§Ų… Ø§Ų„ØŖŲˆŲ„ Ø¨Ø§ØŗØĒØŽØ¯Ø§Ų… Ø§Ų„ØąŲˆØ§Ø¨Øˇ ØŖØ¯Ų†Ø§Ų‡.",
"supporter": "Ø¯Ø§ØšŲ…",
"swap_merge_direction": "ØĒØ¨Ø¯ŲŠŲ„ اØĒØŦØ§Ų‡ Ø§Ų„Ø¯Ų…ØŦ", "swap_merge_direction": "ØĒØ¨Ø¯ŲŠŲ„ اØĒØŦØ§Ų‡ Ø§Ų„Ø¯Ų…ØŦ",
"sync": "Ų…Ø˛Ø§Ų…Ų†ØŠ", "sync": "Ų…Ø˛Ø§Ų…Ų†ØŠ",
"sync_albums": "Ų…Ø˛Ø§Ų…Ų†ØŠ Ø§Ų„Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ", "sync_albums": "Ų…Ø˛Ø§Ų…Ų†ØŠ Ø§Ų„Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ",
@@ -2309,7 +2294,6 @@
"unstack_action_prompt": "ØĒŲ… Ø§Ø˛Ø§Ų„ØŠ ØĒŲƒØ¯ŲŠØŗ {count}", "unstack_action_prompt": "ØĒŲ… Ø§Ø˛Ø§Ų„ØŠ ØĒŲƒØ¯ŲŠØŗ {count}",
"unstacked_assets_count": "ØĒŲ… ØĨØŽØąØ§ØŦ {count, plural, one {# Ø§Ų„ØŖØĩŲ„} other {# Ø§Ų„ØŖØĩŲˆŲ„}} Ų…Ų† Ø§Ų„ØĒŲƒØ¯ŲŠØŗ", "unstacked_assets_count": "ØĒŲ… ØĨØŽØąØ§ØŦ {count, plural, one {# Ø§Ų„ØŖØĩŲ„} other {# Ø§Ų„ØŖØĩŲˆŲ„}} Ų…Ų† Ø§Ų„ØĒŲƒØ¯ŲŠØŗ",
"unsupported_field_type": "Ų†ŲˆØš Ø­Ų‚Ų„ ØēŲŠØą Ų…Ø¯ØšŲˆŲ…", "unsupported_field_type": "Ų†ŲˆØš Ø­Ų‚Ų„ ØēŲŠØą Ų…Ø¯ØšŲˆŲ…",
"unsupported_file_type": "Ų„Ø§ ŲŠŲ…ŲƒŲ† ØąŲØš Ø§Ų„Ų…Ų„Ų {file} Ų„ØŖŲ† Ų†ŲˆØš Ø§Ų„Ų…Ų„Ų {type} ØēŲŠØą Ų…Ø¯ØšŲˆŲ….",
"untagged": "ØēŲŠØą Ų…ŲØšŲŽŲ„ŲŽŲ‘Ų…", "untagged": "ØēŲŠØą Ų…ŲØšŲŽŲ„ŲŽŲ‘Ų…",
"untitled_workflow": "ØŽØˇØŠ ØŗŲŠØą ØšŲ…Ų„ Ø¨Ø¯ŲˆŲ† ØšŲ†ŲˆØ§Ų†", "untitled_workflow": "ØŽØˇØŠ ØŗŲŠØą ØšŲ…Ų„ Ø¨Ø¯ŲˆŲ† ØšŲ†ŲˆØ§Ų†",
"up_next": "Ø§Ų„ØĒØ§Ų„ŲŠ", "up_next": "Ø§Ų„ØĒØ§Ų„ŲŠ",
@@ -2336,8 +2320,6 @@
"url": "ØšŲ†ŲˆØ§Ų† URL", "url": "ØšŲ†ŲˆØ§Ų† URL",
"usage": "Ø§Ų„Ø§ØŗØĒØŽØ¯Ø§Ų…", "usage": "Ø§Ų„Ø§ØŗØĒØŽØ¯Ø§Ų…",
"use_biometric": "Ø§ØŗØĒØŽØ¯Ų… Ø§Ų„Ø¨Ø§ŲŠŲˆŲ…ØĒØąŲŠ", "use_biometric": "Ø§ØŗØĒØŽØ¯Ų… Ø§Ų„Ø¨Ø§ŲŠŲˆŲ…ØĒØąŲŠ",
"use_browser_locale": "Ø§ØŗØĒØŽØ¯Ų… Ų„ØēŲ‡ Ų„Ų„Ų…ØĒØĩŲØ­",
"use_browser_locale_description": "ØĒŲ†ØŗŲŠŲ‚ Ø§Ų„ØĒŲˆØ§ØąŲŠØŽ ŲˆØ§Ų„ØŖŲˆŲ‚Ø§ØĒ ŲˆØ§Ų„ØŖØąŲ‚Ø§Ų… ŲˆŲŲ‚Ų‹Ø§ Ų„ØĨؚداداØĒ Ø§Ų„Ų„ØēØŠ ؁؊ Ų…ØĒØĩŲØ­Ųƒ",
"use_current_connection": "Ø§ØŗØĒØŽØ¯Ų… Ø§Ų„Ø§ØĒØĩØ§Ų„ Ø§Ų„Ø­Ø§Ų„ŲŠ", "use_current_connection": "Ø§ØŗØĒØŽØ¯Ų… Ø§Ų„Ø§ØĒØĩØ§Ų„ Ø§Ų„Ø­Ø§Ų„ŲŠ",
"use_custom_date_range": "Ø§ØŗØĒØŽØ¯Ų… Ø§Ų„Ų†ØˇØ§Ų‚ Ø§Ų„Ø˛Ų…Ų†ŲŠ Ø§Ų„Ų…ØŽØĩØĩ Ø¨Ø¯Ų„Ø§Ų‹ Ų…Ų† Ø°Ų„Ųƒ", "use_custom_date_range": "Ø§ØŗØĒØŽØ¯Ų… Ø§Ų„Ų†ØˇØ§Ų‚ Ø§Ų„Ø˛Ų…Ų†ŲŠ Ø§Ų„Ų…ØŽØĩØĩ Ø¨Ø¯Ų„Ø§Ų‹ Ų…Ų† Ø°Ų„Ųƒ",
"user": "Ų…ØŗØĒØŽØ¯Ų…", "user": "Ų…ØŗØĒØŽØ¯Ų…",
+1 -24
View File
@@ -104,8 +104,6 @@
"image_preview_description": "Đ’Ņ–Đ´Đ°Ņ€Ņ‹Ņ ŅŅŅ€ŅĐ´ĐŊŅĐŗĐ° ĐŋаĐŧĐĩŅ€Ņƒ С Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ĐŧŅ– ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊŅ‹ĐŧŅ–, Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐĩŅ†Ņ†Đ° ĐŋҀҋ ĐŋŅ€Đ°ĐŗĐģŅĐ´ĐˇĐĩ Đ°ŅĐžĐąĐŊĐ°ĐŗĐ° Ņ€ŅŅŅƒŅ€ŅŅƒ Ņ– Đ´ĐģŅ ĐŧĐ°ŅˆŅ‹ĐŊĐŊĐ°ĐŗĐ° ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊŅ", "image_preview_description": "Đ’Ņ–Đ´Đ°Ņ€Ņ‹Ņ ŅŅŅ€ŅĐ´ĐŊŅĐŗĐ° ĐŋаĐŧĐĩŅ€Ņƒ С Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ĐŧŅ– ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊŅ‹ĐŧŅ–, Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐĩŅ†Ņ†Đ° ĐŋҀҋ ĐŋŅ€Đ°ĐŗĐģŅĐ´ĐˇĐĩ Đ°ŅĐžĐąĐŊĐ°ĐŗĐ° Ņ€ŅŅŅƒŅ€ŅŅƒ Ņ– Đ´ĐģŅ ĐŧĐ°ŅˆŅ‹ĐŊĐŊĐ°ĐŗĐ° ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊŅ",
"image_preview_quality_description": "Đ¯ĐēĐ°ŅŅ†ŅŒ ĐŋŅ€Đ°ŅĐ˛Ņ‹ ад 1 да 100. Đ§Ņ‹Đŧ Đ˛Ņ‹ŅˆŅĐš, ҂ҋĐŧ ĐģĐĩĐŋ҈, аĐģĐĩ ĐŋҀҋ ĐŗŅŅ‚Ņ‹Đŧ ŅŅ‚Đ˛Đ°Ņ€Đ°ŅŽŅ†Ņ†Đ° Ņ„Đ°ĐšĐģŅ‹ йОĐģŅŒŅˆĐ°ĐŗĐ° ĐŋаĐŧĐĩŅ€Ņƒ Ņ– ĐŧĐžĐļа СĐŊŅ–ĐˇŅ–Ņ†Ņ†Đ° Ņ…ŅƒŅ‚ĐēĐ°ŅŅ†ŅŒ Đ˛ĐžĐ´ĐŗŅƒĐē҃ ĐŋҀҋĐēĐģадаĐŊĐŊŅ. ĐŽŅŅ‚Đ°ĐŊĐžŅžĐēа ĐŊŅ–ĐˇĐēĐ°ĐŗĐ° СĐŊĐ°Ņ‡ŅĐŊĐŊŅ ĐŧĐžĐļа ĐŋĐ°ŅžĐŋĐģŅ‹Đ˛Đ°Ņ†ŅŒ ĐŊа ŅĐēĐ°ŅŅ†ŅŒ ĐŧĐ°ŅˆŅ‹ĐŊĐŊĐ°ĐŗĐ° ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊŅ.", "image_preview_quality_description": "Đ¯ĐēĐ°ŅŅ†ŅŒ ĐŋŅ€Đ°ŅĐ˛Ņ‹ ад 1 да 100. Đ§Ņ‹Đŧ Đ˛Ņ‹ŅˆŅĐš, ҂ҋĐŧ ĐģĐĩĐŋ҈, аĐģĐĩ ĐŋҀҋ ĐŗŅŅ‚Ņ‹Đŧ ŅŅ‚Đ˛Đ°Ņ€Đ°ŅŽŅ†Ņ†Đ° Ņ„Đ°ĐšĐģŅ‹ йОĐģŅŒŅˆĐ°ĐŗĐ° ĐŋаĐŧĐĩŅ€Ņƒ Ņ– ĐŧĐžĐļа СĐŊŅ–ĐˇŅ–Ņ†Ņ†Đ° Ņ…ŅƒŅ‚ĐēĐ°ŅŅ†ŅŒ Đ˛ĐžĐ´ĐŗŅƒĐē҃ ĐŋҀҋĐēĐģадаĐŊĐŊŅ. ĐŽŅŅ‚Đ°ĐŊĐžŅžĐēа ĐŊŅ–ĐˇĐēĐ°ĐŗĐ° СĐŊĐ°Ņ‡ŅĐŊĐŊŅ ĐŧĐžĐļа ĐŋĐ°ŅžĐŋĐģŅ‹Đ˛Đ°Ņ†ŅŒ ĐŊа ŅĐēĐ°ŅŅ†ŅŒ ĐŧĐ°ŅˆŅ‹ĐŊĐŊĐ°ĐŗĐ° ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊŅ.",
"image_preview_title": "НаĐģĐ°Đ´Ņ‹ ĐŋаĐŋŅŅ€ŅĐ´ĐŊŅĐŗĐ° ĐŋŅ€Đ°ĐŗĐģŅĐ´Ņƒ", "image_preview_title": "НаĐģĐ°Đ´Ņ‹ ĐŋаĐŋŅŅ€ŅĐ´ĐŊŅĐŗĐ° ĐŋŅ€Đ°ĐŗĐģŅĐ´Ņƒ",
"image_progressive": "ĐŸŅ€Đ°ĐŗŅ€ŅŅŅ–ŅžĐŊŅ‹",
"image_progressive_description": "Đ’Ņ‹ŅĐ˛Ņ‹ С ĐŋŅ€Đ°ĐŗŅ€ŅŅŅ–ŅžĐŊŅ‹Đŧ ĐēОдаваĐŊĐŊĐĩĐŧ ĐˇĐ°ĐŗŅ€ŅƒĐļĐ°ŅŽŅ†Ņ†Đ° Ņ…ŅƒŅ‚Ņ‡ŅĐš, ĐŋĐ°ŅŅ‚ŅƒĐŋОва ĐŋаĐģŅĐŋŅˆĐ°ĐĩŅ†Ņ†Đ° ŅĐēĐ°ŅŅ†ŅŒ. НаĐģада ĐŊĐĩ ŅžĐŋĐģŅ‹Đ˛Đ°Đĩ ĐŊа Đ˛Ņ‹ŅĐ˛Ņƒ Ņž Ņ„Đ°Ņ€ĐŧĐ°Ņ†Đĩ WebP.",
"image_quality": "Đ¯ĐēĐ°ŅŅ†ŅŒ", "image_quality": "Đ¯ĐēĐ°ŅŅ†ŅŒ",
"image_resolution": "Đ Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŒ", "image_resolution": "Đ Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŒ",
"image_resolution_description": "БоĐģҌ҈ Đ˛Ņ‹ŅĐžĐēĐ°Ņ Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŒ даСваĐģŅĐĩ ĐˇĐ°Ņ…Đ°Đ˛Đ°Ņ†ŅŒ йОĐģҌ҈ Đ´ŅŅ‚Đ°ĐģŅŅž, аĐģĐĩ ĐŋĐ°Ņ‚Ņ€Đ°ĐąŅƒĐĩ йОĐģҌ҈ Ņ‡Đ°ŅŅƒ Đ´ĐģŅ ĐēадаваĐŊĐŊŅ, ĐŋŅ€Ņ‹Đ˛ĐžĐ´ĐˇŅ–Ņ†ŅŒ да ĐŋĐ°Đ˛ŅĐģŅ–Ņ‡Đ˛Đ°ĐŊĐŊŅ ĐŋаĐŧĐĩŅ€Ņƒ Ņ„Đ°ĐšĐģĐ°Ņž Ņ– ĐŧĐžĐļа СĐŊŅ–ĐˇŅ–Ņ†ŅŒ Ņ…ŅƒŅ‚ĐēĐ°ŅŅ†ŅŒ Đ˛ĐžĐ´ĐŗŅƒĐē҃ Đ´Đ°Đ´Đ°Ņ‚Đē҃.", "image_resolution_description": "БоĐģҌ҈ Đ˛Ņ‹ŅĐžĐēĐ°Ņ Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŒ даСваĐģŅĐĩ ĐˇĐ°Ņ…Đ°Đ˛Đ°Ņ†ŅŒ йОĐģҌ҈ Đ´ŅŅ‚Đ°ĐģŅŅž, аĐģĐĩ ĐŋĐ°Ņ‚Ņ€Đ°ĐąŅƒĐĩ йОĐģҌ҈ Ņ‡Đ°ŅŅƒ Đ´ĐģŅ ĐēадаваĐŊĐŊŅ, ĐŋŅ€Ņ‹Đ˛ĐžĐ´ĐˇŅ–Ņ†ŅŒ да ĐŋĐ°Đ˛ŅĐģŅ–Ņ‡Đ˛Đ°ĐŊĐŊŅ ĐŋаĐŧĐĩŅ€Ņƒ Ņ„Đ°ĐšĐģĐ°Ņž Ņ– ĐŧĐžĐļа СĐŊŅ–ĐˇŅ–Ņ†ŅŒ Ņ…ŅƒŅ‚ĐēĐ°ŅŅ†ŅŒ Đ˛ĐžĐ´ĐŗŅƒĐē҃ Đ´Đ°Đ´Đ°Ņ‚Đē҃.",
@@ -122,7 +120,6 @@
"job_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ĐŊаĐģадаĐŧŅ– ĐŋĐ°Ņ€Đ°ĐģĐĩĐģҌĐŊĐ°ĐŗĐ° Đ˛Ņ‹ĐēаĐŊаĐŊĐŊŅ СадаĐŊĐŊŅŅž", "job_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ĐŊаĐģадаĐŧŅ– ĐŋĐ°Ņ€Đ°ĐģĐĩĐģҌĐŊĐ°ĐŗĐ° Đ˛Ņ‹ĐēаĐŊаĐŊĐŊŅ СадаĐŊĐŊŅŅž",
"jobs_delayed": "{jobCount, plural, other {# адĐēĐģадСĐĩĐŊа}}", "jobs_delayed": "{jobCount, plural, other {# адĐēĐģадСĐĩĐŊа}}",
"jobs_failed": "{jobCount, plural, other {# ĐŊĐĩ Đ˛Ņ‹ĐēаĐŊаĐģĐ°ŅŅ}}", "jobs_failed": "{jobCount, plural, other {# ĐŊĐĩ Đ˛Ņ‹ĐēаĐŊаĐģĐ°ŅŅ}}",
"jobs_over_time": "Đ“Ņ€Đ°Ņ„Ņ–Đē аĐŋŅ€Đ°Ņ†ĐžŅžĐēŅ–",
"library_created": "ĐĄŅ‚Đ˛ĐžŅ€Đ°ĐŊа ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēа: {library}", "library_created": "ĐĄŅ‚Đ˛ĐžŅ€Đ°ĐŊа ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēа: {library}",
"library_deleted": "Đ‘Ņ–ĐąĐģŅ–ŅŅ‚ŅĐēа Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа", "library_deleted": "Đ‘Ņ–ĐąĐģŅ–ŅŅ‚ŅĐēа Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа",
"library_details": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ–", "library_details": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ–",
@@ -163,27 +160,8 @@
"machine_learning_facial_recognition_model_description": "ĐœĐ°Đ´ŅĐģŅ– ĐŋĐĩŅ€Đ°ĐģŅ–Ņ‡Đ°ĐŊŅ‹ Ņž ĐŋĐ°Ņ€Đ°Đ´Đē҃ ŅžĐąŅ‹Đ˛Đ°ĐŊĐŊŅ Ņ–Ņ… ĐŋаĐŧĐĩŅ€Ņƒ. БоĐģŅŒŅˆŅ‹Ņ ĐŧĐ°Đ´ŅĐģŅ– ĐŋавОĐģҌĐŊĐĩĐš Ņ– Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ŅŽŅ†ŅŒ йОĐģҌ҈ ĐŋаĐŧŅŅ†Ņ–, аĐģĐĩ Đ´Đ°ŅŽŅ†ŅŒ ĐģĐĩĐŋŅˆŅ‹Ņ Đ˛Ņ‹ĐŊŅ–ĐēŅ–. Đ—Đ˛ŅŅ€ĐŊҖ҆Đĩ ŅƒĐ˛Đ°ĐŗŅƒ, ŅˆŅ‚Đž ĐŋĐ°ŅĐģŅ СĐŧĐĩĐŊŅ‹ ĐŧĐ°Đ´ŅĐģŅ– Ņ‚Ņ€ŅĐąĐ° СĐŊĐžŅž СаĐŋŅƒŅŅ†Ņ–Ņ†ŅŒ СадаĐŊĐŊĐĩ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ Ņ‚Đ˛Đ°Ņ€Đ°Ņž Đ´ĐģŅ ŅžŅŅ–Ņ… Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņž.", "machine_learning_facial_recognition_model_description": "ĐœĐ°Đ´ŅĐģŅ– ĐŋĐĩŅ€Đ°ĐģŅ–Ņ‡Đ°ĐŊŅ‹ Ņž ĐŋĐ°Ņ€Đ°Đ´Đē҃ ŅžĐąŅ‹Đ˛Đ°ĐŊĐŊŅ Ņ–Ņ… ĐŋаĐŧĐĩŅ€Ņƒ. БоĐģŅŒŅˆŅ‹Ņ ĐŧĐ°Đ´ŅĐģŅ– ĐŋавОĐģҌĐŊĐĩĐš Ņ– Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ŅŽŅ†ŅŒ йОĐģҌ҈ ĐŋаĐŧŅŅ†Ņ–, аĐģĐĩ Đ´Đ°ŅŽŅ†ŅŒ ĐģĐĩĐŋŅˆŅ‹Ņ Đ˛Ņ‹ĐŊŅ–ĐēŅ–. Đ—Đ˛ŅŅ€ĐŊҖ҆Đĩ ŅƒĐ˛Đ°ĐŗŅƒ, ŅˆŅ‚Đž ĐŋĐ°ŅĐģŅ СĐŧĐĩĐŊŅ‹ ĐŧĐ°Đ´ŅĐģŅ– Ņ‚Ņ€ŅĐąĐ° СĐŊĐžŅž СаĐŋŅƒŅŅ†Ņ–Ņ†ŅŒ СадаĐŊĐŊĐĩ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ Ņ‚Đ˛Đ°Ņ€Đ°Ņž Đ´ĐģŅ ŅžŅŅ–Ņ… Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņž.",
"machine_learning_facial_recognition_setting": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊĐĩ Ņ‚Đ˛Đ°Ņ€Đ°Ņž", "machine_learning_facial_recognition_setting": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊĐĩ Ņ‚Đ˛Đ°Ņ€Đ°Ņž",
"machine_learning_facial_recognition_setting_description": "КаĐģŅ– адĐēĐģŅŽŅ‡Đ°ĐŊа, Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅŅ‹ ĐŊĐĩ ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐēĐ°Đ´Đ°Đ˛Đ°Ņ†Ņ†Đ° Đ´ĐģŅ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ Ņ‚Đ˛Đ°Ņ€Đ°Ņž, Ņ– ĐŊĐĩ ĐąŅƒĐ´ĐˇĐĩ СаĐŋĐ°ŅžĐŊŅŅ†Ņ†Đ° Ņ€Đ°ĐˇĐ´ĐˇĐĩĐģ \"Đ›ŅŽĐ´ĐˇŅ–\" ĐŊа ŅŅ‚Đ°Ņ€ĐžĐŊ҆ҋ \"ĐĐŗĐģŅĐ´\".", "machine_learning_facial_recognition_setting_description": "КаĐģŅ– адĐēĐģŅŽŅ‡Đ°ĐŊа, Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅŅ‹ ĐŊĐĩ ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐēĐ°Đ´Đ°Đ˛Đ°Ņ†Ņ†Đ° Đ´ĐģŅ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ Ņ‚Đ˛Đ°Ņ€Đ°Ņž, Ņ– ĐŊĐĩ ĐąŅƒĐ´ĐˇĐĩ СаĐŋĐ°ŅžĐŊŅŅ†Ņ†Đ° Ņ€Đ°ĐˇĐ´ĐˇĐĩĐģ \"Đ›ŅŽĐ´ĐˇŅ–\" ĐŊа ŅŅ‚Đ°Ņ€ĐžĐŊ҆ҋ \"ĐĐŗĐģŅĐ´\".",
"machine_learning_max_detection_distance": "МаĐēҁҖĐŧаĐģҌĐŊĐ°Ņ адĐģĐĩĐŗĐģĐ°ŅŅ†ŅŒ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅ",
"machine_learning_max_detection_distance_description": "МаĐēҁҖĐŧаĐģҌĐŊĐ°Ņ Ņ€ĐžĐˇĐŊŅ–Ņ†Đ° ĐŋаĐŧŅ–Đļ Đ´Đ˛ŅƒĐŧа Đ˛Ņ‹ŅĐ˛Đ°ĐŧŅ–, ŅĐēŅ–Ņ ĐģŅ–Ņ‡Đ°Ņ†Ņ†Đ° Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ°ĐŧŅ–, ҁĐēĐģадаĐĩ ад 0,001 да 0,1. БоĐģҌ҈ Đ˛Ņ‹ŅĐžĐēŅ–Ņ СĐŊĐ°Ņ‡ŅĐŊĐŊŅ– даСвОĐģŅŅ†ŅŒ Đ˛Ņ‹ŅĐ˛Ņ–Ņ†ŅŒ йОĐģҌ҈ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ°Ņž, аĐģĐĩ ĐŧĐžĐŗŅƒŅ†ŅŒ ĐŋŅ€Ņ‹Đ˛Đĩҁ҆Җ да ĐŊŅĐŋŅ€Đ°Đ˛Ņ–ĐģҌĐŊҋ҅ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅŅž.",
"machine_learning_max_recognition_distance": "ĐŸĐ°Ņ€ĐžĐŗ Ņ€Đ°ĐˇĐŋаСĐŊаваĐŊĐŊŅ",
"machine_learning_max_recognition_distance_description": "МаĐēҁҖĐŧаĐģҌĐŊаĐĩ Đ°Đ´Ņ€ĐžĐˇĐŊĐĩĐŊĐŊĐĩ ĐŋаĐŧŅ–Đļ Đ´Đ˛ŅƒĐŧа Đ°ŅĐžĐąĐ°ĐŧŅ–, ŅĐēŅ–Ņ ĐŧĐžĐļĐŊа ĐģŅ–Ņ‡Ņ‹Ņ†ŅŒ адĐŊŅ‹Đŧ Ņ‡Đ°ĐģавĐĩĐēаĐŧ (҃ Đ´Ņ‹ŅĐŋаСОĐŊĐĩ ад 0 да 2).ЗĐŊŅ–ĐļŅĐŊĐŊĐĩ ĐŗŅŅ‚Đ°ĐŗĐ° ĐŋĐ°Ņ€Đ°ĐŧĐĩŅ‚Ņ€Ņƒ ĐŧĐžĐļа ĐŋŅ€Đ°Đ´ŅƒŅ…Ņ–ĐģŅ–Ņ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаĐŊĐŊĐĩ Đ´Đ˛ŅƒŅ… ĐģŅŽĐ´ĐˇĐĩĐš ŅĐē адĐŊĐ°ĐŗĐž Ņ– Ņ‚Đ°ĐŗĐž Đļ Ņ‡Đ°ĐģавĐĩĐēа, а ĐŋĐ°Đ˛Ņ‹ŅˆŅĐŊĐŊĐĩ - ŅĐē Đ´Đ˛ŅƒŅ… Ņ€ĐžĐˇĐŊҋ҅ ĐģŅŽĐ´ĐˇĐĩĐš. ĐœĐ°ĐšŅ†Đĩ ĐŊа ŅžĐ˛Đ°ĐˇĐĩ, ŅˆŅ‚Đž ĐŋŅ€Đ°ŅŅ†ĐĩĐš ай'ŅĐ´ĐŊĐ°Ņ†ŅŒ Đ´Đ˛ŅƒŅ… ĐģŅŽĐ´ĐˇĐĩĐš, ҇ҋĐŧ ĐŋĐ°Đ´ĐˇŅĐģŅ–Ņ†ŅŒ адĐŊĐ°ĐŗĐž Ņ‡Đ°ĐģавĐĩĐēа ĐŊа Đ´Đ˛Đ°Ņ–Ņ…, Ņ‚Đ°Đŧ҃ Đŋа ĐŧĐ°ĐŗŅ‡Ņ‹ĐŧĐ°ŅŅ†Ņ– Đ˛Ņ‹ĐąŅ–Ņ€Đ°ĐšŅ†Đĩ ĐŧĐĩĐŊŅˆŅ‹ ĐŋĐ°Ņ€ĐžĐŗ.",
"machine_learning_min_detection_score": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊŅ‹ ĐŋĐ°Ņ€ĐžĐŗ Ņ€Đ°ĐˇĐŋаСĐŊаваĐŊĐŊŅ",
"machine_learning_min_detection_score_description": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊŅ‹ ĐŋĐ°Ņ€ĐžĐŗ Đ´ĐģŅ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅ Đ°ŅĐžĐąŅ‹ (ад 0 да 1). ĐŅ–ĐļŅĐšŅˆĐ°Đĩ СĐŊĐ°Ņ‡ŅĐŊĐŊĐĩ даСвОĐģŅ–Ņ†ŅŒ СĐŊĐ°Ņ…ĐžĐ´ĐˇŅ–Ņ†ŅŒ йОĐģҌ҈ Đ°ŅĐžĐą, аĐģĐĩ ĐŧĐžĐļа ĐŋŅ€Ņ‹Đ˛Đĩҁ҆Җ да Ņ–ĐģĐļŅ‹Đ˛Ņ‹Ņ… ҁĐŋŅ€Đ°Ņ†ĐžŅžĐ˛Đ°ĐŊĐŊŅŅž.",
"machine_learning_min_recognized_faces": "ĐœŅ–ĐŊŅ–Đŧ҃Đŧ Ņ€Đ°ĐˇĐŋаСĐŊаĐŊҋ҅ Ņ‚Đ˛Đ°Ņ€Đ°Ņž",
"machine_learning_min_recognized_faces_description": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊĐ°Ņ ĐēĐžĐģҌĐēĐ°ŅŅ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаĐŊҋ҅ Ņ‚Đ˛Đ°Ņ€Đ°Ņž Đ´ĐģŅ ŅŅ‚Đ˛Đ°Ņ€ŅĐŊĐŊŅ Đ°ŅĐžĐąŅ‹. ĐŸĐ°Đ˛ŅĐģŅ–Ņ‡ŅĐŊĐŊĐĩ ĐŗŅŅ‚Đ°ĐŗĐ° ĐŋĐ°Ņ€Đ°ĐŧĐĩŅ‚Ņ€Đ° Ņ€ĐžĐąŅ–Ņ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаĐŊĐŊĐĩ Đ°ŅĐžĐą йОĐģҌ҈ даĐēĐģадĐŊŅ‹Đŧ, аĐģĐĩ ĐŋҀҋ ĐŗŅŅ‚Ņ‹Đŧ ĐŋĐ°Đ˛ŅĐģŅ–Ņ‡Đ˛Đ°ĐĩŅ†Ņ†Đ° вĐĩŅ€Đ°ĐŗĐžĐ´ĐŊĐ°ŅŅ†ŅŒ Ņ‚Đ°ĐŗĐž, ŅˆŅ‚Đž Ņ‚Đ˛Đ°Ņ€ ĐŊĐĩ ĐąŅƒĐ´ĐˇĐĩ ĐŋŅ€Ņ‹ŅĐ˛ĐžĐĩĐŊŅ‹ Đ°ŅĐžĐąĐĩ.",
"machine_learning_ocr": "РаСĐŋаСĐŊаваĐŊĐŊĐĩ Ņ‚ŅĐēŅŅ‚Ņƒ (OCR)",
"machine_learning_ocr_description": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐšŅ†Đĩ ĐŧĐ°ŅˆŅ‹ĐŊĐŊаĐĩ ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊĐĩ Đ´ĐģŅ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ Ņ‚ŅĐēŅŅ‚Ņƒ ĐŊа ĐŧаĐģŅŽĐŊĐēĐ°Ņ…",
"machine_learning_ocr_enabled": "Đ”Đ°Đ´Đ°Ņ†ŅŒ OCR",
"machine_learning_ocr_enabled_description": "КаĐģŅ– адĐēĐģŅŽŅ‡Đ°ĐŊа, Đ˛Ņ‹ŅĐ˛Ņ‹ ĐŊĐĩ ĐąŅƒĐ´ŅƒŅ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊĐ°Đ˛Đ°Ņ†Ņ†Đ° С Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊĐŊĐĩĐŧ Ņ‚ŅĐēŅŅ‚Ņƒ.",
"machine_learning_ocr_max_resolution": "МаĐēҁҖĐŧаĐģҌĐŊĐ°Ņ Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŒ", "machine_learning_ocr_max_resolution": "МаĐēҁҖĐŧаĐģҌĐŊĐ°Ņ Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŒ",
"machine_learning_ocr_max_resolution_description": "Đ’Ņ–Đ´Đ°Ņ€Ņ‹ŅŅ‹ С Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŽ йОĐģҌ҈ ĐŗŅŅ‚Đ°Đš ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐŋаĐŧĐĩĐŊŅˆĐ°ĐŊŅ‹ С ĐˇĐ°Ņ…Đ°Đ˛Đ°ĐŊĐŊĐĩĐŧ ŅŅƒĐ°Đ´ĐŊĐžŅŅ–ĐŊŅ‹ йаĐēĐžŅž. БоĐģҌ҈ Đ˛Ņ‹ŅĐžĐēŅ–Ņ СĐŊĐ°Ņ‡ŅĐŊĐŊŅ– ĐŋĐ°Đ˛Ņ‹ŅˆĐ°ŅŽŅ†ŅŒ даĐēĐģадĐŊĐ°ŅŅ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ, аĐģĐĩ ĐŋĐ°Ņ‚Ņ€Đ°ĐąŅƒŅŽŅ†ŅŒ йОĐģҌ҈ Ņ‡Đ°ŅŅƒ ĐŊа аĐŋŅ€Đ°Ņ†ĐžŅžĐē҃ Ņ– Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ŅŽŅ†ŅŒ йОĐģҌ҈ ĐŋаĐŧŅŅ†Ņ–.", "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": "НаĐģĐ°Đ´Ņ‹ ĐŧĐ°ŅˆŅ‹ĐŊĐŊĐ°ĐŗĐ° ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊŅ",
"map_dark_style": "ĐĻŅ‘ĐŧĐŊŅ‹ ҁ҂ҋĐģҌ", "map_dark_style": "ĐĻŅ‘ĐŧĐŊŅ‹ ҁ҂ҋĐģҌ",
"map_enable_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Ņ„ŅƒĐŊĐē҆ҋҖ ĐēĐ°Ņ€Ņ‚Ņ‹", "map_enable_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Ņ„ŅƒĐŊĐē҆ҋҖ ĐēĐ°Ņ€Ņ‚Ņ‹",
"map_gps_settings": "НаĐģĐ°Đ´Ņ‹ ĐēĐ°Ņ€Ņ‚Ņ‹ Ņ– GPS", "map_gps_settings": "НаĐģĐ°Đ´Ņ‹ ĐēĐ°Ņ€Ņ‚Ņ‹ Ņ– GPS",
@@ -193,7 +171,6 @@
"map_style_description": "URL-Đ°Đ´Ņ€Đ°Ņ style.json Ņ‚ŅĐŧŅ‹ ĐēĐ°Ņ€Ņ‚Ņ‹", "map_style_description": "URL-Đ°Đ´Ņ€Đ°Ņ style.json Ņ‚ŅĐŧŅ‹ ĐēĐ°Ņ€Ņ‚Ņ‹",
"metadata_extraction_job_description": "Đ’Ņ‹ĐŊŅŅ†ŅŒ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊŅ‹Ņ С Ņ„Đ°ĐšĐģĐ°Ņž, Ņ‚Đ°ĐēŅ–Ņ ŅĐē ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ, Ņ‚Đ˛Đ°Ņ€Ņ‹ Ņ– Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŒ", "metadata_extraction_job_description": "Đ’Ņ‹ĐŊŅŅ†ŅŒ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊŅ‹Ņ С Ņ„Đ°ĐšĐģĐ°Ņž, Ņ‚Đ°ĐēŅ–Ņ ŅĐē ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ, Ņ‚Đ˛Đ°Ņ€Ņ‹ Ņ– Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŒ",
"metadata_settings": "НаĐģĐ°Đ´Ņ‹ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊҋ҅", "metadata_settings": "НаĐģĐ°Đ´Ņ‹ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊҋ҅",
"notification_email_port_description": "ĐŸĐžŅ€Ņ‚ ĐŋĐ°ŅˆŅ‚ĐžĐ˛Đ°ĐŗĐ° ҁĐĩŅ€Đ˛ĐĩŅ€Đ° (ĐŊаĐŋҀҋĐēĐģад, 25, 465 айО 587)",
"oauth_button_text": "ĐĸŅĐēҁ҂ ĐēĐŊĐžĐŋĐēŅ–", "oauth_button_text": "ĐĸŅĐēҁ҂ ĐēĐŊĐžĐŋĐēŅ–",
"oauth_settings": "OAuth", "oauth_settings": "OAuth",
"refreshing_all_libraries": "АйĐŊĐ°ŅžĐģĐĩĐŊĐŊĐĩ ŅžŅŅ–Ņ… ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐē", "refreshing_all_libraries": "АйĐŊĐ°ŅžĐģĐĩĐŊĐŊĐĩ ŅžŅŅ–Ņ… ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐē",
@@ -239,7 +216,7 @@
"user_settings": "НаĐģĐ°Đ´Ņ‹ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", "user_settings": "НаĐģĐ°Đ´Ņ‹ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа",
"user_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", "user_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа",
"version_check_enabled_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ĐŋŅ€Đ°Đ˛ĐĩŅ€Đē҃ вĐĩҀҁҖҖ", "version_check_enabled_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ĐŋŅ€Đ°Đ˛ĐĩŅ€Đē҃ вĐĩҀҁҖҖ",
"version_check_implications": "Đ¤ŅƒĐŊĐēŅ†Ņ‹Ņ ĐŋŅ€Đ°Đ˛ĐĩŅ€ĐēŅ– вĐĩҀҁҖҖ ĐŋĐĩŅ€Ņ‹ŅĐ´Ņ‹Ņ‡ĐŊа ĐˇĐ˛ŅŅ€Ņ‚Đ°ĐĩŅ†Ņ†Đ° да {server}", "version_check_implications": "Đ¤ŅƒĐŊĐēŅ†Ņ‹Ņ ĐŋŅ€Đ°Đ˛ĐĩŅ€ĐēŅ– вĐĩҀҁҖҖ ĐŋĐĩŅ€Ņ‹ŅĐ´Ņ‹Ņ‡ĐŊа ĐˇĐ˛ŅŅ€Ņ‚Đ°ĐĩŅ†Ņ†Đ° да github.com",
"version_check_settings": "ĐŸŅ€Đ°Đ˛ĐĩŅ€Đēа вĐĩҀҁҖҖ", "version_check_settings": "ĐŸŅ€Đ°Đ˛ĐĩŅ€Đēа вĐĩҀҁҖҖ",
"version_check_settings_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ/адĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅ– ай ĐŊОваК вĐĩҀҁҖҖ" "version_check_settings_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ/адĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅ– ай ĐŊОваК вĐĩҀҁҖҖ"
}, },
+25 -48
View File
@@ -61,7 +61,7 @@
"backup_onboarding_1_description": "ĐēĐžĐŋиĐĩ ĐŊа ОйĐģаĐēа иĐģи Đ´Ņ€ŅƒĐŗĐž Ņ„Đ¸ĐˇĐ¸Ņ‡ĐĩҁĐēĐž ĐŧŅŅŅ‚Đž.", "backup_onboarding_1_description": "ĐēĐžĐŋиĐĩ ĐŊа ОйĐģаĐēа иĐģи Đ´Ņ€ŅƒĐŗĐž Ņ„Đ¸ĐˇĐ¸Ņ‡ĐĩҁĐēĐž ĐŧŅŅŅ‚Đž.",
"backup_onboarding_2_description": "ĐģĐžĐēаĐģĐŊи ĐēĐžĐŋĐ¸Ņ ĐŊа Ņ€Đ°ĐˇĐģĐ¸Ņ‡ĐŊи ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°. ĐĸОва вĐēĐģŅŽŅ‡Đ˛Đ° ĐžŅĐŊОвĐŊĐ¸Ņ‚Đĩ Ņ„Đ°ĐšĐģОвĐĩ и ĐģĐžĐēаĐģĐŊи Đ°Ņ€Ņ…Đ¸Đ˛Đ¸ ĐŊа Ņ‚ĐĩСи Ņ„Đ°ĐšĐģОвĐĩ.", "backup_onboarding_2_description": "ĐģĐžĐēаĐģĐŊи ĐēĐžĐŋĐ¸Ņ ĐŊа Ņ€Đ°ĐˇĐģĐ¸Ņ‡ĐŊи ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°. ĐĸОва вĐēĐģŅŽŅ‡Đ˛Đ° ĐžŅĐŊОвĐŊĐ¸Ņ‚Đĩ Ņ„Đ°ĐšĐģОвĐĩ и ĐģĐžĐēаĐģĐŊи Đ°Ņ€Ņ…Đ¸Đ˛Đ¸ ĐŊа Ņ‚ĐĩСи Ņ„Đ°ĐšĐģОвĐĩ.",
"backup_onboarding_3_description": "ĐžĐąŅ‰Đž ĐēĐžĐŋĐ¸Ņ ĐŊа Đ˛Đ°ŅˆĐ¸Ņ‚Đĩ даĐŊĐŊи, вĐēĐģŅŽŅ‡Đ¸Ņ‚ĐĩĐŊĐž ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐŊĐ¸Ņ‚Đĩ Ņ„Đ°ĐšĐģОвĐĩ. ĐĸОва вĐēĐģŅŽŅ‡Đ˛Đ° 1 ĐēĐžĐŋиĐĩ Đ¸ĐˇĐ˛ŅŠĐŊ ŅĐ¸ŅŅ‚ĐĩĐŧĐ°Ņ‚Đ° и 2 ĐģĐžĐēаĐģĐŊи ĐēĐžĐŋĐ¸Ņ.", "backup_onboarding_3_description": "ĐžĐąŅ‰Đž ĐēĐžĐŋĐ¸Ņ ĐŊа Đ˛Đ°ŅˆĐ¸Ņ‚Đĩ даĐŊĐŊи, вĐēĐģŅŽŅ‡Đ¸Ņ‚ĐĩĐŊĐž ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐŊĐ¸Ņ‚Đĩ Ņ„Đ°ĐšĐģОвĐĩ. ĐĸОва вĐēĐģŅŽŅ‡Đ˛Đ° 1 ĐēĐžĐŋиĐĩ Đ¸ĐˇĐ˛ŅŠĐŊ ŅĐ¸ŅŅ‚ĐĩĐŧĐ°Ņ‚Đ° и 2 ĐģĐžĐēаĐģĐŊи ĐēĐžĐŋĐ¸Ņ.",
"backup_onboarding_description": "За ĐŊадĐĩĐļĐ´ĐŊа ĐˇĐ°Ņ‰Đ¸Ņ‚Đ° ĐŋŅ€ĐĩĐŋĐžŅ€ŅŠŅ‡Đ˛Đ°ĐŧĐĩ <backblaze-link>ŅŅ‚Ņ€Đ°Ņ‚ĐĩĐŗĐ¸ŅŅ‚Đ° 3-2-1</backblaze-link>. ĐŸŅ€Đ°Đ˛ĐĩŅ‚Đĩ Đ°Ņ€Ņ…Đ¸Đ˛ĐŊи ĐēĐžĐŋĐ¸Ņ ĐēаĐēŅ‚Đž ĐŊа ĐēĐ°Ņ‡ĐĩĐŊĐ¸Ņ‚Đĩ ҁĐŊиĐŧĐēи/видĐĩа, Ņ‚Đ°Đēа и ĐŊа ĐąĐ°ĐˇĐ°Ņ‚Đ° даĐŊĐŊи ĐŊа Immich.", "backup_onboarding_description": "За ĐŊадĐĩĐļĐ´ĐŊа ĐˇĐ°Ņ‰Đ¸Ņ‚Đ° ĐŋŅ€ĐĩĐŋĐžŅ€ŅŠŅ‡Đ˛Đ°ĐŧĐĩ ŅŅ‚Ņ€Đ°Ņ‚ĐĩĐŗĐ¸ŅŅ‚Đ° <backblaze-link>3-2-1</backblaze-link>. ĐŸŅ€Đ°Đ˛ĐĩŅ‚Đĩ Đ°Ņ€Ņ…Đ¸Đ˛ĐŊи ĐēĐžĐŋĐ¸Ņ ĐēаĐēŅ‚Đž ĐŊа ĐēĐ°Ņ‡ĐĩĐŊĐ¸Ņ‚Đĩ ҁĐŊиĐŧĐēи/видĐĩа, Ņ‚Đ°Đēа и ĐŊа ĐąĐ°ĐˇĐ°Ņ‚Đ° даĐŊĐŊи ĐŊа Immich.",
"backup_onboarding_footer": "За ĐŋĐžĐ´Ņ€ĐžĐąĐŊа иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ ĐžŅ‚ĐŊĐžŅĐŊĐž Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž в Immich, ĐŧĐžĐģŅ виĐļŅ‚Đĩ в <link>Đ´ĐžĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Đ¸ŅŅ‚Đ°</link>.", "backup_onboarding_footer": "За ĐŋĐžĐ´Ņ€ĐžĐąĐŊа иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ ĐžŅ‚ĐŊĐžŅĐŊĐž Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž в Immich, ĐŧĐžĐģŅ виĐļŅ‚Đĩ в <link>Đ´ĐžĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Đ¸ŅŅ‚Đ°</link>.",
"backup_onboarding_parts_title": "ĐĄŅ‚Ņ€Đ°Ņ‚ĐĩĐŗĐ¸ŅŅ‚Đ° 3-2-1 вĐēĐģŅŽŅ‡Đ˛Đ°:", "backup_onboarding_parts_title": "ĐĄŅ‚Ņ€Đ°Ņ‚ĐĩĐŗĐ¸ŅŅ‚Đ° 3-2-1 вĐēĐģŅŽŅ‡Đ˛Đ°:",
"backup_onboarding_title": "Đ ĐĩСĐĩŅ€Đ˛ĐŊи ĐēĐžĐŋĐ¸Ņ", "backup_onboarding_title": "Đ ĐĩСĐĩŅ€Đ˛ĐŊи ĐēĐžĐŋĐ¸Ņ",
@@ -104,7 +104,7 @@
"image_preview_description": "ĐĄŅ€ĐĩĐ´ĐĩĐŊ Ņ€Đ°ĐˇĐŧĐĩŅ€ ĐŊа Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩŅ‚Đž ҁ ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚Đ¸ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊĐŊи, иСĐŋĐžĐģСваĐŊĐž ĐŋŅ€Đ¸ ĐŋŅ€ĐĩĐŗĐģĐĩĐ´ ĐŊа ĐĩдиĐŊ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚ и Са ĐŧĐ°ŅˆĐ¸ĐŊĐŊĐž ĐžĐąŅƒŅ‡ĐĩĐŊиĐĩ", "image_preview_description": "ĐĄŅ€ĐĩĐ´ĐĩĐŊ Ņ€Đ°ĐˇĐŧĐĩŅ€ ĐŊа Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩŅ‚Đž ҁ ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚Đ¸ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊĐŊи, иСĐŋĐžĐģСваĐŊĐž ĐŋŅ€Đ¸ ĐŋŅ€ĐĩĐŗĐģĐĩĐ´ ĐŊа ĐĩдиĐŊ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚ и Са ĐŧĐ°ŅˆĐ¸ĐŊĐŊĐž ĐžĐąŅƒŅ‡ĐĩĐŊиĐĩ",
"image_preview_quality_description": "ĐšĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž ĐŊа ĐŋŅ€ĐĩĐ´Đ˛Đ°Ņ€Đ¸Ņ‚ĐĩĐģĐŊĐ¸Ņ ĐŋŅ€ĐĩĐŗĐģĐĩĐ´ ĐžŅ‚ 1 Đ´Đž 100. По-Đ˛Đ¸ŅĐžĐēĐ°Ņ‚Đ° ŅŅ‚ĐžĐšĐŊĐžŅŅ‚ Đĩ ĐŋĐž-Đ´ĐžĐąŅ€Đ°, ĐŊĐž вОди Đ´Đž ĐŋĐž-ĐŗĐžĐģĐĩĐŧи Ņ„Đ°ĐšĐģОвĐĩ и ĐŧĐžĐļĐĩ да ĐŊаĐŧаĐģи ĐąŅŠŅ€ĐˇĐžĐ´ĐĩĐšŅŅ‚Đ˛Đ¸ĐĩŅ‚Đž ĐŊа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž. ЗадаваĐŊĐĩŅ‚Đž ĐŊа ĐŊĐ¸ŅĐēа ŅŅ‚ĐžĐšĐŊĐžŅŅ‚ ĐŧĐžĐļĐĩ да ĐŋОвĐģĐ¸ŅĐĩ ĐŊа ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛ĐžŅ‚Đž ĐŊа ĐŧĐ°ŅˆĐ¸ĐŊĐŊĐžŅ‚Đž ĐžĐąŅƒŅ‡ĐĩĐŊиĐĩ.", "image_preview_quality_description": "ĐšĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž ĐŊа ĐŋŅ€ĐĩĐ´Đ˛Đ°Ņ€Đ¸Ņ‚ĐĩĐģĐŊĐ¸Ņ ĐŋŅ€ĐĩĐŗĐģĐĩĐ´ ĐžŅ‚ 1 Đ´Đž 100. По-Đ˛Đ¸ŅĐžĐēĐ°Ņ‚Đ° ŅŅ‚ĐžĐšĐŊĐžŅŅ‚ Đĩ ĐŋĐž-Đ´ĐžĐąŅ€Đ°, ĐŊĐž вОди Đ´Đž ĐŋĐž-ĐŗĐžĐģĐĩĐŧи Ņ„Đ°ĐšĐģОвĐĩ и ĐŧĐžĐļĐĩ да ĐŊаĐŧаĐģи ĐąŅŠŅ€ĐˇĐžĐ´ĐĩĐšŅŅ‚Đ˛Đ¸ĐĩŅ‚Đž ĐŊа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž. ЗадаваĐŊĐĩŅ‚Đž ĐŊа ĐŊĐ¸ŅĐēа ŅŅ‚ĐžĐšĐŊĐžŅŅ‚ ĐŧĐžĐļĐĩ да ĐŋОвĐģĐ¸ŅĐĩ ĐŊа ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛ĐžŅ‚Đž ĐŊа ĐŧĐ°ŅˆĐ¸ĐŊĐŊĐžŅ‚Đž ĐžĐąŅƒŅ‡ĐĩĐŊиĐĩ.",
"image_preview_title": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐŊа ĐŋŅ€ĐĩĐŗĐģĐĩда", "image_preview_title": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐŊа ĐŋŅ€ĐĩĐŗĐģĐĩда",
"image_progressive": "ĐŸŅ€ĐžĐŗŅ€ĐĩŅĐ¸Đ˛ĐŊĐž", "image_progressive": "ĐŸŅ€ĐžĐŗŅ€ĐĩŅĐ¸Đ˛ĐĩĐŊ JPEG",
"image_progressive_description": "Đ˜ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸ŅŅ‚Đ°, ĐēĐžĐ´Đ¸Ņ€Đ°ĐŊи в ĐŋŅ€ĐžĐŗŅ€ĐĩŅĐ¸Đ˛ĐĩĐŊ JPEG Ņ„ĐžŅ€ĐŧĐ°Ņ‚, ҁĐĩ ĐˇĐ°Ņ€ĐĩĐļĐ´Đ°Ņ‚ ĐŋĐž-ĐąŅŠŅ€ĐˇĐž, ҁ ĐŋĐžŅŅ‚ĐĩĐŋĐĩĐŊĐŊĐž ĐŋĐžĐ´ĐžĐąŅ€ŅĐ˛Đ°Ņ‰Đž ҁĐĩ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž. ĐĸОва ĐŊŅĐŧа вĐģĐ¸ŅĐŊиĐĩ ĐŊа ĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐ¸Ņ‚Đĩ ĐēĐ°Ņ‚Đž WebP Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ.", "image_progressive_description": "Đ˜ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸ŅŅ‚Đ°, ĐēĐžĐ´Đ¸Ņ€Đ°ĐŊи в ĐŋŅ€ĐžĐŗŅ€ĐĩŅĐ¸Đ˛ĐĩĐŊ JPEG Ņ„ĐžŅ€ĐŧĐ°Ņ‚, ҁĐĩ ĐˇĐ°Ņ€ĐĩĐļĐ´Đ°Ņ‚ ĐŋĐž-ĐąŅŠŅ€ĐˇĐž, ҁ ĐŋĐžŅŅ‚ĐĩĐŋĐĩĐŊĐŊĐž ĐŋĐžĐ´ĐžĐąŅ€ŅĐ˛Đ°Ņ‰Đž ҁĐĩ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž. ĐĸОва ĐŊŅĐŧа вĐģĐ¸ŅĐŊиĐĩ ĐŊа ĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐ¸Ņ‚Đĩ ĐēĐ°Ņ‚Đž WebP Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ.",
"image_quality": "ĐšĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž", "image_quality": "ĐšĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž",
"image_resolution": "Đ ĐĩСОĐģŅŽŅ†Đ¸Ņ", "image_resolution": "Đ ĐĩСОĐģŅŽŅ†Đ¸Ņ",
@@ -311,7 +311,7 @@
"search_jobs": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ ĐŊа ĐˇĐ°Đ´Đ°Ņ‡Đ¸â€Ļ", "search_jobs": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ ĐŊа ĐˇĐ°Đ´Đ°Ņ‡Đ¸â€Ļ",
"send_welcome_email": "ИСĐŋŅ€Đ°Ņ‰Đ°ĐŊĐĩ ĐŊа иĐŧĐĩĐšĐģ Са Đ´ĐžĐąŅ€Đĩ Đ´ĐžŅˆĐģи", "send_welcome_email": "ИСĐŋŅ€Đ°Ņ‰Đ°ĐŊĐĩ ĐŊа иĐŧĐĩĐšĐģ Са Đ´ĐžĐąŅ€Đĩ Đ´ĐžŅˆĐģи",
"server_external_domain_settings": "Đ’ŅŠĐŊ҈ĐĩĐŊ Đ´ĐžĐŧĐĩĐšĐŊ", "server_external_domain_settings": "Đ’ŅŠĐŊ҈ĐĩĐŊ Đ´ĐžĐŧĐĩĐšĐŊ",
"server_external_domain_settings_description": "ДоĐŧĐĩĐšĐŊ Са Đ˛ŅŠĐŊ҈ĐŊи Đ˛Ņ€ŅŠĐˇĐēи", "server_external_domain_settings_description": "ДоĐŧĐĩĐšĐŊ Са ĐŋŅƒĐąĐģĐ¸Ņ‡ĐŊи ҁĐŋОдĐĩĐģĐĩĐŊи Đ˛Ņ€ŅŠĐˇĐēи, вĐēĐģŅŽŅ‡Đ¸Ņ‚ĐĩĐģĐŊĐž http(s)://",
"server_public_users": "ĐŸŅƒĐąĐģĐ¸Ņ‡ĐŊи ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи", "server_public_users": "ĐŸŅƒĐąĐģĐ¸Ņ‡ĐŊи ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи",
"server_public_users_description": "Đ’ŅĐ¸Ņ‡Đēи ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи (иĐŧĐĩ и иĐŧĐĩĐšĐģ) ŅĐ° Đ¸ĐˇĐąŅ€ĐžĐĩĐŊи ĐŋŅ€Đ¸ Đ´ĐžĐąĐ°Đ˛ŅĐŊĐĩ ĐŊа ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ в ҁĐŋОдĐĩĐģĐĩĐŊи аĐģĐąŅƒĐŧи. ĐšĐžĐŗĐ°Ņ‚Đž Đĩ Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐž, ҁĐŋĐ¸ŅŅŠĐēŅŠŅ‚ ҁ ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи ҉Đĩ ĐąŅŠĐ´Đĩ Đ´ĐžŅŅ‚ŅŠĐŋĐĩĐŊ ŅĐ°ĐŧĐž Са адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ¸Ņ‚Đĩ.", "server_public_users_description": "Đ’ŅĐ¸Ņ‡Đēи ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи (иĐŧĐĩ и иĐŧĐĩĐšĐģ) ŅĐ° Đ¸ĐˇĐąŅ€ĐžĐĩĐŊи ĐŋŅ€Đ¸ Đ´ĐžĐąĐ°Đ˛ŅĐŊĐĩ ĐŊа ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ в ҁĐŋОдĐĩĐģĐĩĐŊи аĐģĐąŅƒĐŧи. ĐšĐžĐŗĐ°Ņ‚Đž Đĩ Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐž, ҁĐŋĐ¸ŅŅŠĐēŅŠŅ‚ ҁ ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи ҉Đĩ ĐąŅŠĐ´Đĩ Đ´ĐžŅŅ‚ŅŠĐŋĐĩĐŊ ŅĐ°ĐŧĐž Са адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ¸Ņ‚Đĩ.",
"server_settings": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ°", "server_settings": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ°",
@@ -333,7 +333,7 @@
"storage_template_migration_description": "ĐŸŅ€Đ¸ĐģĐ°ĐŗĐ°ĐŊĐĩ ĐŊа Ņ‚ĐĩĐēŅƒŅ‰Đ¸Ņ <link>{template}</link> ĐēҊĐŧ ĐŋŅ€ĐĩĐ´Đ¸ŅˆĐŊĐž ĐēĐ°Ņ‡ĐĩĐŊĐ¸Ņ‚Đĩ Ņ„Đ°ĐšĐģОвĐĩ", "storage_template_migration_description": "ĐŸŅ€Đ¸ĐģĐ°ĐŗĐ°ĐŊĐĩ ĐŊа Ņ‚ĐĩĐēŅƒŅ‰Đ¸Ņ <link>{template}</link> ĐēҊĐŧ ĐŋŅ€ĐĩĐ´Đ¸ŅˆĐŊĐž ĐēĐ°Ņ‡ĐĩĐŊĐ¸Ņ‚Đĩ Ņ„Đ°ĐšĐģОвĐĩ",
"storage_template_migration_info": "ШайĐģĐžĐŊа ҉Đĩ ĐŋŅ€ĐĩĐžĐąŅ€Đ°ĐˇŅƒĐ˛Đ° Đ˛ŅĐ¸Ņ‡Đēи Ņ€Đ°ĐˇŅˆĐ¸Ņ€ĐĩĐŊĐ¸Ņ ĐŊа иĐŧĐĩĐŊĐ°Ņ‚Đ° ĐŊа Ņ„Đ°ĐšĐģОвĐĩŅ‚Đĩ в Đ´ĐžĐģĐĩĐŊ Ņ€ĐĩĐŗĐ¸ŅŅ‚ŅŠŅ€. ĐŸŅ€ĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ в ŅˆĐ°ĐąĐģĐžĐŊĐ¸Ņ‚Đĩ ҉Đĩ ҁĐĩ ĐŋŅ€Đ¸ĐģĐ°ĐŗĐ°Ņ‚ ŅĐ°ĐŧĐž Са ĐŊОви ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸. За да ĐŋŅ€Đ¸ĐģĐžĐļĐ¸Ņ‚Đĩ ĐŋŅ€Đ¸ĐŊŅƒĐ´Đ¸Ņ‚ĐĩĐģĐŊĐž ŅˆĐ°ĐąĐģĐžĐŊа ĐēҊĐŧ вĐĩ҇Đĩ ĐēĐ°Ņ‡ĐĩĐŊи ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸, иСĐŋҊĐģĐŊĐĩŅ‚Đĩ <link>{job}</link>.", "storage_template_migration_info": "ШайĐģĐžĐŊа ҉Đĩ ĐŋŅ€ĐĩĐžĐąŅ€Đ°ĐˇŅƒĐ˛Đ° Đ˛ŅĐ¸Ņ‡Đēи Ņ€Đ°ĐˇŅˆĐ¸Ņ€ĐĩĐŊĐ¸Ņ ĐŊа иĐŧĐĩĐŊĐ°Ņ‚Đ° ĐŊа Ņ„Đ°ĐšĐģОвĐĩŅ‚Đĩ в Đ´ĐžĐģĐĩĐŊ Ņ€ĐĩĐŗĐ¸ŅŅ‚ŅŠŅ€. ĐŸŅ€ĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ в ŅˆĐ°ĐąĐģĐžĐŊĐ¸Ņ‚Đĩ ҉Đĩ ҁĐĩ ĐŋŅ€Đ¸ĐģĐ°ĐŗĐ°Ņ‚ ŅĐ°ĐŧĐž Са ĐŊОви ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸. За да ĐŋŅ€Đ¸ĐģĐžĐļĐ¸Ņ‚Đĩ ĐŋŅ€Đ¸ĐŊŅƒĐ´Đ¸Ņ‚ĐĩĐģĐŊĐž ŅˆĐ°ĐąĐģĐžĐŊа ĐēҊĐŧ вĐĩ҇Đĩ ĐēĐ°Ņ‡ĐĩĐŊи ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸, иСĐŋҊĐģĐŊĐĩŅ‚Đĩ <link>{job}</link>.",
"storage_template_migration_job": "Đ—Đ°Đ´Đ°Ņ‡Đ° Са ĐŧĐ¸ĐŗŅ€Đ°Ņ†Đ¸Ņ ĐŊа ŅˆĐ°ĐąĐģĐžĐŊа Са ŅŅŠŅ…Ņ€Đ°ĐŊĐĩĐŊиĐĩ", "storage_template_migration_job": "Đ—Đ°Đ´Đ°Ņ‡Đ° Са ĐŧĐ¸ĐŗŅ€Đ°Ņ†Đ¸Ņ ĐŊа ŅˆĐ°ĐąĐģĐžĐŊа Са ŅŅŠŅ…Ņ€Đ°ĐŊĐĩĐŊиĐĩ",
"storage_template_more_details": "За ĐŋОвĐĩ҇Đĩ ĐŋĐžĐ´Ņ€ĐžĐąĐŊĐžŅŅ‚Đ¸ ĐžŅ‚ĐŊĐžŅĐŊĐž Ņ‚Đ°ĐˇĐ¸ Ņ„ŅƒĐŊĐēŅ†Đ¸Ņ ҁĐĩ ĐžĐąŅŠŅ€ĐŊĐĩŅ‚Đĩ ĐēҊĐŧ ŅˆĐ°ĐąĐģĐžĐŊа <template-link>Storage Template</template-link> и ĐŊĐĩĐŗĐžĐ˛Đ¸Ņ‚Đĩ <implications-link>ĐŋĐžŅĐģĐĩĐ´ŅŅ‚Đ˛Đ¸Ņ</implications-link>", "storage_template_more_details": "За ĐŋОвĐĩ҇Đĩ ĐŋĐžĐ´Ņ€ĐžĐąĐŊĐžŅŅ‚Đ¸ ĐžŅ‚ĐŊĐžŅĐŊĐž Ņ‚Đ°ĐˇĐ¸ Ņ„ŅƒĐŊĐēŅ†Đ¸Ņ ҁĐĩ ĐžĐąŅŠŅ€ĐŊĐĩŅ‚Đĩ ĐēҊĐŧ ŅˆĐ°ĐąĐģĐžĐŊа <template-link>Storage Template</template-link> и ĐŊĐĩĐŗĐžĐ˛Đ¸Ņ‚Đĩ <implications-link> ĐŋĐžŅĐģĐĩĐ´ŅŅ‚Đ˛Đ¸Ņ </implications-link>",
"storage_template_onboarding_description_v2": "ĐšĐžĐŗĐ°Ņ‚Đž Đĩ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊа, Ņ‚Đ°ĐˇĐ¸ Ņ„ŅƒĐŊĐēŅ†Đ¸Ņ ҉Đĩ ĐžŅ€ĐŗĐ°ĐŊĐ¸ĐˇĐ¸Ņ€Đ° Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž Ņ„Đ°ĐšĐģОвĐĩŅ‚Đĩ, ҁĐŋĐžŅ€ĐĩĐ´ ŅˆĐ°ĐąĐģĐžĐŊ, Đ´ĐĩŅ„Đ¸ĐŊĐ¸Ņ€Đ°ĐŊ ĐžŅ‚ ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģŅ. За Đ´ĐžĐŋҊĐģĐŊĐ¸Ņ‚ĐĩĐģĐŊа иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ, ĐŧĐžĐģŅ виĐļŅ‚Đĩ <link>Đ´ĐžĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Đ¸ŅŅ‚Đ°</link>.", "storage_template_onboarding_description_v2": "ĐšĐžĐŗĐ°Ņ‚Đž Đĩ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊа, Ņ‚Đ°ĐˇĐ¸ Ņ„ŅƒĐŊĐēŅ†Đ¸Ņ ҉Đĩ ĐžŅ€ĐŗĐ°ĐŊĐ¸ĐˇĐ¸Ņ€Đ° Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž Ņ„Đ°ĐšĐģОвĐĩŅ‚Đĩ, ҁĐŋĐžŅ€ĐĩĐ´ ŅˆĐ°ĐąĐģĐžĐŊ, Đ´ĐĩŅ„Đ¸ĐŊĐ¸Ņ€Đ°ĐŊ ĐžŅ‚ ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģŅ. За Đ´ĐžĐŋҊĐģĐŊĐ¸Ņ‚ĐĩĐģĐŊа иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ, ĐŧĐžĐģŅ виĐļŅ‚Đĩ <link>Đ´ĐžĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Đ¸ŅŅ‚Đ°</link>.",
"storage_template_path_length": "ĐžĐŗŅ€Đ°ĐŊĐ¸Ņ‡ĐĩĐŊиĐĩ ĐŊа Đ´ŅŠĐģĐļиĐŊĐ°Ņ‚Đ° ĐŊа ĐŋŅŠŅ‚Ņ: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "ĐžĐŗŅ€Đ°ĐŊĐ¸Ņ‡ĐĩĐŊиĐĩ ĐŊа Đ´ŅŠĐģĐļиĐŊĐ°Ņ‚Đ° ĐŊа ĐŋŅŠŅ‚Ņ: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "ШайĐģĐžĐŊ Са ŅŅŠŅ…Ņ€Đ°ĐŊĐĩĐŊиĐĩ", "storage_template_settings": "ШайĐģĐžĐŊ Са ŅŅŠŅ…Ņ€Đ°ĐŊĐĩĐŊиĐĩ",
@@ -372,7 +372,7 @@
"transcoding_audio_codec": "ĐŅƒĐ´Đ¸Đž ĐēОдĐĩĐē", "transcoding_audio_codec": "ĐŅƒĐ´Đ¸Đž ĐēОдĐĩĐē",
"transcoding_audio_codec_description": "Opus Đĩ ĐžĐŋŅ†Đ¸ŅŅ‚Đ° ҁ ĐŊаК-Đ˛Đ¸ŅĐžĐēĐž ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž, ĐŊĐž иĐŧа ĐŋĐž-ĐŊĐ¸ŅĐēа ŅŅŠĐ˛ĐŧĐĩŅŅ‚Đ¸ĐŧĐžŅŅ‚ ҁҊҁ ŅŅ‚Đ°Ņ€Đ¸ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ° иĐģи ŅĐžŅ„Ņ‚ŅƒĐĩŅ€.", "transcoding_audio_codec_description": "Opus Đĩ ĐžĐŋŅ†Đ¸ŅŅ‚Đ° ҁ ĐŊаК-Đ˛Đ¸ŅĐžĐēĐž ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž, ĐŊĐž иĐŧа ĐŋĐž-ĐŊĐ¸ŅĐēа ŅŅŠĐ˛ĐŧĐĩŅŅ‚Đ¸ĐŧĐžŅŅ‚ ҁҊҁ ŅŅ‚Đ°Ņ€Đ¸ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ° иĐģи ŅĐžŅ„Ņ‚ŅƒĐĩŅ€.",
"transcoding_bitrate_description": "ВидĐĩĐžĐēĐģиĐŋОвĐĩ ҁ ĐŋĐž-Đ˛Đ¸ŅĐžĐē ĐžŅ‚ ĐŧаĐēŅĐ¸ĐŧаĐģĐŊĐ¸Ņ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚ иĐģи ĐŊĐĩ в ĐŋŅ€Đ¸ĐĩŅ‚ Ņ„ĐžŅ€ĐŧĐ°Ņ‚", "transcoding_bitrate_description": "ВидĐĩĐžĐēĐģиĐŋОвĐĩ ҁ ĐŋĐž-Đ˛Đ¸ŅĐžĐē ĐžŅ‚ ĐŧаĐēŅĐ¸ĐŧаĐģĐŊĐ¸Ņ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚ иĐģи ĐŊĐĩ в ĐŋŅ€Đ¸ĐĩŅ‚ Ņ„ĐžŅ€ĐŧĐ°Ņ‚",
"transcoding_codecs_learn_more": "За да ĐŊĐ°ŅƒŅ‡Đ¸Ņ‚Đĩ ĐŋОвĐĩ҇Đĩ Са иСĐŋĐžĐģСваĐŊĐ°Ņ‚Đ° Ņ‚ĐĩŅ€ĐŧиĐŊĐžĐģĐžĐŗĐ¸Ņ, виĐļŅ‚Đĩ Đ´ĐžĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Đ¸ŅŅ‚Đ° ĐŊа FFmpeg Са <h264-link>ĐēОдĐĩĐē H.264</h264-link>, <hevc-link>ĐēОдĐĩĐē HEVC</hevc-link> и <vp9-link>ĐēОдĐĩĐē VP9</vp9-link>.", "transcoding_codecs_learn_more": "За да ĐŊĐ°ŅƒŅ‡Đ¸Ņ‚Đĩ ĐŋОвĐĩ҇Đĩ Са иСĐŋĐžĐģСваĐŊĐ°Ņ‚Đ° Ņ‚ĐĩŅ€ĐŧиĐŊĐžĐģĐžĐŗĐ¸Ņ, виĐļŅ‚Đĩ Đ´ĐžĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Đ¸ŅŅ‚Đ° ĐŊа FFmpeg Са <h264-link>ĐēОдĐĩĐē H.264</h264-link>, <hevc-link>ĐēОдĐĩĐē HEVC</hevc-link> и <vp9-link>VP9 ĐēОдĐĩĐē</vp9-link>.",
"transcoding_constant_quality_mode": "Đ ĐĩĐļиĐŧ ĐŊа ĐŋĐžŅŅ‚ĐžŅĐŊĐŊĐž ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž", "transcoding_constant_quality_mode": "Đ ĐĩĐļиĐŧ ĐŊа ĐŋĐžŅŅ‚ĐžŅĐŊĐŊĐž ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž",
"transcoding_constant_quality_mode_description": "ICQ Đĩ ĐŋĐž-Đ´ĐžĐąŅŠŅ€ ĐžŅ‚ CQP, ĐŊĐž ĐŊŅĐēОи ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ° Са Ņ…Đ°Ņ€Đ´ŅƒĐĩŅ€ĐŊĐž ҃ҁĐēĐžŅ€ŅĐ˛Đ°ĐŊĐĩ ĐŊĐĩ ĐŋĐžĐ´Đ´ŅŠŅ€ĐļĐ°Ņ‚ Ņ‚ĐžĐˇĐ¸ Ņ€ĐĩĐļиĐŧ. ĐĄ СадаваĐŊĐĩŅ‚Đž ĐŊа Ņ‚Đ°ĐˇĐ¸ ĐžĐŋŅ†Đ¸Ņ ҉Đĩ ĐŋŅ€ĐĩĐ´ĐŋĐžŅ‡ĐĩŅ‚Đĩ ĐŋĐžŅĐžŅ‡ĐĩĐŊĐ¸Ņ Ņ€ĐĩĐļиĐŧ ĐŋŅ€Đ¸ иСĐŋĐžĐģСваĐŊĐĩ ĐŊа ĐąĐ°ĐˇĐ¸Ņ€Đ°ĐŊĐž ĐŊа ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž ĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ. Đ˜ĐŗĐŊĐžŅ€Đ¸Ņ€Đ°ĐŊĐž ĐžŅ‚ NVENC, Ņ‚ŅŠĐš ĐēĐ°Ņ‚Đž ĐŊĐĩ ĐŋĐžĐ´Đ´ŅŠŅ€Đļа ICQ.", "transcoding_constant_quality_mode_description": "ICQ Đĩ ĐŋĐž-Đ´ĐžĐąŅŠŅ€ ĐžŅ‚ CQP, ĐŊĐž ĐŊŅĐēОи ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ° Са Ņ…Đ°Ņ€Đ´ŅƒĐĩŅ€ĐŊĐž ҃ҁĐēĐžŅ€ŅĐ˛Đ°ĐŊĐĩ ĐŊĐĩ ĐŋĐžĐ´Đ´ŅŠŅ€ĐļĐ°Ņ‚ Ņ‚ĐžĐˇĐ¸ Ņ€ĐĩĐļиĐŧ. ĐĄ СадаваĐŊĐĩŅ‚Đž ĐŊа Ņ‚Đ°ĐˇĐ¸ ĐžĐŋŅ†Đ¸Ņ ҉Đĩ ĐŋŅ€ĐĩĐ´ĐŋĐžŅ‡ĐĩŅ‚Đĩ ĐŋĐžŅĐžŅ‡ĐĩĐŊĐ¸Ņ Ņ€ĐĩĐļиĐŧ ĐŋŅ€Đ¸ иСĐŋĐžĐģСваĐŊĐĩ ĐŊа ĐąĐ°ĐˇĐ¸Ņ€Đ°ĐŊĐž ĐŊа ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž ĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ. Đ˜ĐŗĐŊĐžŅ€Đ¸Ņ€Đ°ĐŊĐž ĐžŅ‚ NVENC, Ņ‚ŅŠĐš ĐēĐ°Ņ‚Đž ĐŊĐĩ ĐŋĐžĐ´Đ´ŅŠŅ€Đļа ICQ.",
"transcoding_constant_rate_factor": "КоĐĩŅ„Đ¸Ņ†Đ¸ĐĩĐŊŅ‚ ĐŊа ĐŋĐžŅŅ‚ĐžŅĐŊĐŊа ҁĐēĐžŅ€ĐžŅŅ‚ (-crf)", "transcoding_constant_rate_factor": "КоĐĩŅ„Đ¸Ņ†Đ¸ĐĩĐŊŅ‚ ĐŊа ĐŋĐžŅŅ‚ĐžŅĐŊĐŊа ҁĐēĐžŅ€ĐžŅŅ‚ (-crf)",
@@ -411,7 +411,7 @@
"transcoding_tone_mapping": "ĐĸĐžĐŊаĐģĐŊĐž ĐēĐ°Ņ€Ņ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ€Đ°ĐŊĐĩ", "transcoding_tone_mapping": "ĐĸĐžĐŊаĐģĐŊĐž ĐēĐ°Ņ€Ņ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ€Đ°ĐŊĐĩ",
"transcoding_tone_mapping_description": "ОĐŋĐ¸Ņ‚Đ˛Đ° ҁĐĩ да СаĐŋаСи Đ˛ŅŠĐŊ҈ĐŊĐ¸Ņ вид ĐŊа HDR видĐĩĐžĐēĐģиĐŋОвĐĩ, ĐēĐžĐŗĐ°Ņ‚Đž ҁĐĩ ĐŋŅ€ĐĩĐžĐąŅ€Đ°ĐˇŅƒĐ˛Đ° в SDR. Đ’ŅĐĩĐēи аĐģĐŗĐžŅ€Đ¸Ņ‚ŅŠĐŧ ĐŋŅ€Đ°Đ˛Đ¸ Ņ€Đ°ĐˇĐģĐ¸Ņ‡ĐŊи ĐēĐžĐŧĐŋŅ€ĐžĐŧĐ¸ŅĐ¸ Са Ņ†Đ˛ŅŅ‚, Đ´ĐĩŅ‚Đ°ĐšĐģĐŊĐžŅŅ‚ и ŅŅ€ĐēĐžŅŅ‚. Hable СаĐŋаСва Đ´ĐĩŅ‚Đ°ĐšĐģĐ¸Ņ‚Đĩ, Mobius СаĐŋаСва Ņ†Đ˛ĐĩŅ‚Đ°, а Reinhard СаĐŋаСва ŅŅ€ĐēĐžŅŅ‚Ņ‚Đ°.", "transcoding_tone_mapping_description": "ОĐŋĐ¸Ņ‚Đ˛Đ° ҁĐĩ да СаĐŋаСи Đ˛ŅŠĐŊ҈ĐŊĐ¸Ņ вид ĐŊа HDR видĐĩĐžĐēĐģиĐŋОвĐĩ, ĐēĐžĐŗĐ°Ņ‚Đž ҁĐĩ ĐŋŅ€ĐĩĐžĐąŅ€Đ°ĐˇŅƒĐ˛Đ° в SDR. Đ’ŅĐĩĐēи аĐģĐŗĐžŅ€Đ¸Ņ‚ŅŠĐŧ ĐŋŅ€Đ°Đ˛Đ¸ Ņ€Đ°ĐˇĐģĐ¸Ņ‡ĐŊи ĐēĐžĐŧĐŋŅ€ĐžĐŧĐ¸ŅĐ¸ Са Ņ†Đ˛ŅŅ‚, Đ´ĐĩŅ‚Đ°ĐšĐģĐŊĐžŅŅ‚ и ŅŅ€ĐēĐžŅŅ‚. Hable СаĐŋаСва Đ´ĐĩŅ‚Đ°ĐšĐģĐ¸Ņ‚Đĩ, Mobius СаĐŋаСва Ņ†Đ˛ĐĩŅ‚Đ°, а Reinhard СаĐŋаСва ŅŅ€ĐēĐžŅŅ‚Ņ‚Đ°.",
"transcoding_transcode_policy": "ĐŸŅ€Đ°Đ˛Đ¸Đģа Са Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ", "transcoding_transcode_policy": "ĐŸŅ€Đ°Đ˛Đ¸Đģа Са Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ",
"transcoding_transcode_policy_description": "ĐŸŅ€Đ°Đ˛Đ¸Đģа Са Ņ‚ĐžĐ˛Đ° ĐēĐžĐŗĐ° видĐĩĐžĐēĐģиĐŋŅŠŅ‚ Ņ‚Ņ€ŅĐąĐ˛Đ° да ĐąŅŠĐ´Đĩ Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊ. HDR видĐĩĐžĐēĐģиĐŋОвĐĩŅ‚Đĩ и Ņ‚ĐĩСи ҁ Ņ„ĐžŅ€ĐŧĐ°Ņ‚, Ņ€Đ°ĐˇĐģĐ¸Ņ‡ĐĩĐŊ ĐžŅ‚ YUV 4:2:0, ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ виĐŊĐ°ĐŗĐ¸ Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊи (ĐžŅĐ˛ĐĩĐŊ аĐēĐž Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž Đĩ Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐž).", "transcoding_transcode_policy_description": "ĐŸŅ€Đ°Đ˛Đ¸Đģа Са Ņ‚ĐžĐ˛Đ° ĐēĐžĐŗĐ° видĐĩĐžĐēĐģиĐŋŅŠŅ‚ Ņ‚Ņ€ŅĐąĐ˛Đ° да ĐąŅŠĐ´Đĩ Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊ. HDR видĐĩĐžĐēĐģиĐŋОвĐĩŅ‚Đĩ виĐŊĐ°ĐŗĐ¸ ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊи (ĐžŅĐ˛ĐĩĐŊ аĐēĐž Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž Đĩ Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐž).",
"transcoding_two_pass_encoding": "ĐšĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ ҁ двОКĐŊĐž ĐŧиĐŊаваĐŊĐĩ", "transcoding_two_pass_encoding": "ĐšĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ ҁ двОКĐŊĐž ĐŧиĐŊаваĐŊĐĩ",
"transcoding_two_pass_encoding_setting_description": "ĐĸŅ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž ҁ двĐĩ ĐŧиĐŊаваĐŊĐ¸Ņ ŅŅŠĐˇĐ´Đ°Đ˛Đ° ĐŋĐž-Đ´ĐžĐąŅ€Đĩ ĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ видĐĩа. ĐšĐžĐŗĐ°Ņ‚Đž ĐŧаĐēŅĐ¸ĐŧаĐģĐŊĐ¸Ņ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚ Đĩ вĐēĐģŅŽŅ‡ĐĩĐŊ (ĐˇĐ°Đ´ŅŠĐģĐļĐ¸Ņ‚ĐĩĐģĐŊĐž Đĩ да ҁĐĩ Ņ€Đ°ĐąĐžŅ‚Đ¸ ҁ H.264 и HEVC), Ņ‚Đ°ĐˇĐ¸ ĐžĐŋŅ†Đ¸Ņ иСĐŋĐžĐģСва диаĐŋаСОĐŊ ĐŊа ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚Đ° ĐąĐ°ĐˇĐ¸Ņ€Đ°ĐŊ ĐŊа ĐŧаĐēŅĐ¸ĐŧаĐģĐŊĐ¸Ņ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚ и Đ¸ĐŗĐŊĐžŅ€Đ¸Ņ€Đ° CRF. За VP9, CRF ĐŧĐžĐļĐĩ да ҁĐĩ иСĐŋĐžĐģСва аĐēĐž ĐŧаĐēŅĐ¸ĐŧаĐģĐŊĐ¸ŅŅ‚ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚ Đĩ иСĐēĐģŅŽŅ‡ĐĩĐŊ.", "transcoding_two_pass_encoding_setting_description": "ĐĸŅ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž ҁ двĐĩ ĐŧиĐŊаваĐŊĐ¸Ņ ŅŅŠĐˇĐ´Đ°Đ˛Đ° ĐŋĐž-Đ´ĐžĐąŅ€Đĩ ĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ видĐĩа. ĐšĐžĐŗĐ°Ņ‚Đž ĐŧаĐēŅĐ¸ĐŧаĐģĐŊĐ¸Ņ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚ Đĩ вĐēĐģŅŽŅ‡ĐĩĐŊ (ĐˇĐ°Đ´ŅŠĐģĐļĐ¸Ņ‚ĐĩĐģĐŊĐž Đĩ да ҁĐĩ Ņ€Đ°ĐąĐžŅ‚Đ¸ ҁ H.264 и HEVC), Ņ‚Đ°ĐˇĐ¸ ĐžĐŋŅ†Đ¸Ņ иСĐŋĐžĐģСва диаĐŋаСОĐŊ ĐŊа ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚Đ° ĐąĐ°ĐˇĐ¸Ņ€Đ°ĐŊ ĐŊа ĐŧаĐēŅĐ¸ĐŧаĐģĐŊĐ¸Ņ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚ и Đ¸ĐŗĐŊĐžŅ€Đ¸Ņ€Đ° CRF. За VP9, CRF ĐŧĐžĐļĐĩ да ҁĐĩ иСĐŋĐžĐģСва аĐēĐž ĐŧаĐēŅĐ¸ĐŧаĐģĐŊĐ¸ŅŅ‚ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚ Đĩ иСĐēĐģŅŽŅ‡ĐĩĐŊ.",
"transcoding_video_codec": "ВидĐĩĐžĐēОдĐĩĐē", "transcoding_video_codec": "ВидĐĩĐžĐēОдĐĩĐē",
@@ -441,7 +441,7 @@
"user_successfully_removed": "ĐŸĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ {email} Đĩ ҃ҁĐŋĐĩ҈ĐŊĐž ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚.", "user_successfully_removed": "ĐŸĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ {email} Đĩ ҃ҁĐŋĐĩ҈ĐŊĐž ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚.",
"users_page_description": "ĐĄŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Đ° Са адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи", "users_page_description": "ĐĄŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Đ° Са адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи",
"version_check_enabled_description": "АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°Đš ĐŋŅ€ĐžĐ˛ĐĩŅ€Đēа ĐŊа вĐĩŅ€ŅĐ¸ŅŅ‚Đ°", "version_check_enabled_description": "АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°Đš ĐŋŅ€ĐžĐ˛ĐĩŅ€Đēа ĐŊа вĐĩŅ€ŅĐ¸ŅŅ‚Đ°",
"version_check_implications": "Đ¤ŅƒĐŊĐēŅ†Đ¸ŅŅ‚Đ° Са ĐŋŅ€ĐžĐ˛ĐĩŅ€Đēа ĐŊа вĐĩŅ€ŅĐ¸ŅŅ‚Đ° Ņ€Đ°ĐˇŅ‡Đ¸Ņ‚Đ° ĐŊа ĐŋĐĩŅ€Đ¸ĐžĐ´Đ¸Ņ‡ĐŊа ĐēĐžĐŧ҃ĐŊиĐēĐ°Ņ†Đ¸Ņ ҁ {server}", "version_check_implications": "Đ¤ŅƒĐŊĐēŅ†Đ¸ŅŅ‚Đ° Са ĐŋŅ€ĐžĐ˛ĐĩŅ€Đēа ĐŊа вĐĩŅ€ŅĐ¸ŅŅ‚Đ° Ņ€Đ°ĐˇŅ‡Đ¸Ņ‚Đ° ĐŊа ĐŋĐĩŅ€Đ¸ĐžĐ´Đ¸Ņ‡ĐŊа ĐēĐžĐŧ҃ĐŊиĐēĐ°Ņ†Đ¸Ņ ҁ github.com",
"version_check_settings": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đēа ĐŊа вĐĩŅ€ŅĐ¸ŅŅ‚Đ°", "version_check_settings": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đēа ĐŊа вĐĩŅ€ŅĐ¸ŅŅ‚Đ°",
"version_check_settings_description": "АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐšŅ‚Đĩ/Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐšŅ‚Đĩ иСвĐĩŅŅ‚Đ¸ĐĩŅ‚Đž Са ĐŊОва вĐĩŅ€ŅĐ¸Ņ", "version_check_settings_description": "АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐšŅ‚Đĩ/Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐšŅ‚Đĩ иСвĐĩŅŅ‚Đ¸ĐĩŅ‚Đž Са ĐŊОва вĐĩŅ€ŅĐ¸Ņ",
"video_conversion_job": "ĐĸŅ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа видĐĩĐžĐēĐģиĐŋОвĐĩŅ‚Đĩ", "video_conversion_job": "ĐĸŅ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа видĐĩĐžĐēĐģиĐŋОвĐĩŅ‚Đĩ",
@@ -794,11 +794,6 @@
"color": "ĐĻĐ˛ŅŅ‚", "color": "ĐĻĐ˛ŅŅ‚",
"color_theme": "ĐĻвĐĩŅ‚ĐžĐ˛Đ° Ņ‚ĐĩĐŧа", "color_theme": "ĐĻвĐĩŅ‚ĐžĐ˛Đ° Ņ‚ĐĩĐŧа",
"command": "КоĐŧаĐŊда", "command": "КоĐŧаĐŊда",
"command_palette_prompt": "Đ‘ŅŠŅ€ĐˇĐž ĐŊаĐŧĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Đ¸, Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ иĐģи ĐēĐžĐŧаĐŊди",
"command_palette_to_close": "ĐˇĐ°Ņ‚Đ˛ĐžŅ€Đ¸",
"command_palette_to_navigate": "вĐģĐĩС",
"command_palette_to_select": "иСйĐĩŅ€Đ¸",
"command_palette_to_show_all": "ĐŋĐžĐēаĐļи Đ˛ŅĐ¸Ņ‡ĐēĐž",
"comment_deleted": "КоĐŧĐĩĐŊŅ‚Đ°Ņ€ŅŠŅ‚ Đĩ Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚", "comment_deleted": "КоĐŧĐĩĐŊŅ‚Đ°Ņ€ŅŠŅ‚ Đĩ Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚",
"comment_options": "ОĐŋŅ†Đ¸Đ¸ Са ĐēĐžĐŧĐĩĐŊŅ‚Đ°Ņ€", "comment_options": "ОĐŋŅ†Đ¸Đ¸ Са ĐēĐžĐŧĐĩĐŊŅ‚Đ°Ņ€",
"comments_and_likes": "КоĐŧĐĩĐŊŅ‚Đ°Ņ€Đ¸ и Ņ…Đ°Ņ€ĐĩŅĐ˛Đ°ĐŊĐ¸Ņ", "comments_and_likes": "КоĐŧĐĩĐŊŅ‚Đ°Ņ€Đ¸ и Ņ…Đ°Ņ€ĐĩŅĐ˛Đ°ĐŊĐ¸Ņ",
@@ -866,14 +861,13 @@
"crop_aspect_ratio_fixed": "ФиĐēŅĐ¸Ņ€Đ°ĐŊ", "crop_aspect_ratio_fixed": "ФиĐēŅĐ¸Ņ€Đ°ĐŊ",
"crop_aspect_ratio_free": "ХвОйОдĐĩĐŊ", "crop_aspect_ratio_free": "ХвОйОдĐĩĐŊ",
"crop_aspect_ratio_original": "ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐĩĐŊ", "crop_aspect_ratio_original": "ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐĩĐŊ",
"crop_aspect_ratio_square": "ĐšĐ˛Đ°Đ´Ņ€Đ°Ņ‚",
"curated_object_page_title": "НĐĩŅ‰Đ°", "curated_object_page_title": "НĐĩŅ‰Đ°",
"current_device": "ĐĸĐĩĐēŅƒŅ‰Đž ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đž", "current_device": "ĐĸĐĩĐēŅƒŅ‰Đž ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đž",
"current_pin_code": "ĐĄĐĩĐŗĐ°ŅˆĐĩĐŊ PIN ĐēОд", "current_pin_code": "ĐĄĐĩĐŗĐ°ŅˆĐĩĐŊ PIN ĐēОд",
"current_server_address": "ĐĐ°ŅŅ‚ĐžŅŅ‰ Đ°Đ´Ņ€Đĩҁ ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ°", "current_server_address": "ĐĐ°ŅŅ‚ĐžŅŅ‰ Đ°Đ´Ņ€Đĩҁ ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ°",
"custom_date": "ПĐĩŅ€ŅĐžĐŊаĐģĐ¸ĐˇĐ¸Ņ€Đ°ĐŊа Đ´Đ°Ņ‚Đ°", "custom_date": "ПĐĩŅ€ŅĐžĐŊаĐģĐ¸ĐˇĐ¸Ņ€Đ°ĐŊа Đ´Đ°Ņ‚Đ°",
"custom_locale": "ПĐĩŅ€ŅĐžĐŊаĐģĐ¸ĐˇĐ¸Ņ€Đ°ĐŊи ĐĩСиĐēОви ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи", "custom_locale": "ПĐĩŅ€ŅĐžĐŊаĐģĐ¸ĐˇĐ¸Ņ€Đ°ĐŊ ĐģĐžĐēаĐģ",
"custom_locale_description": "Đ¤ĐžŅ€ĐŧĐ°Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа Đ´Đ°Ņ‚Đ°, Đ˛Ņ€ĐĩĐŧĐĩ и Ņ‡Đ¸ŅĐģа в ĐˇĐ°Đ˛Đ¸ŅĐ¸ĐŧĐžŅŅ‚ ĐžŅ‚ Đ¸ĐˇĐąŅ€Đ°ĐŊĐ¸Ņ ĐĩСиĐē и Ņ€ĐĩĐŗĐ¸ĐžĐŊ", "custom_locale_description": "Đ¤ĐžŅ€ĐŧĐ°Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа Đ´Đ°Ņ‚Đ¸ и Ņ‡Đ¸ŅĐģа в ĐˇĐ°Đ˛Đ¸ŅĐ¸ĐŧĐžŅŅ‚ ĐžŅ‚ ĐĩСиĐēа и Ņ€ĐĩĐŗĐ¸ĐžĐŊа",
"custom_url": "ПĐĩŅ€ŅĐžĐŊаĐģĐ¸ĐˇĐ¸Ņ€Đ°ĐŊ URL Đ°Đ´Ņ€Đĩҁ", "custom_url": "ПĐĩŅ€ŅĐžĐŊаĐģĐ¸ĐˇĐ¸Ņ€Đ°ĐŊ URL Đ°Đ´Ņ€Đĩҁ",
"cutoff_date_description": "ЗаĐŋаСваĐŊĐĩ ĐŊа ҁĐŊиĐŧĐēи ĐžŅ‚ ĐŋĐžŅĐģĐĩĐ´ĐŊĐ¸Ņ‚Đĩâ€Ļ", "cutoff_date_description": "ЗаĐŋаСваĐŊĐĩ ĐŊа ҁĐŊиĐŧĐēи ĐžŅ‚ ĐŋĐžŅĐģĐĩĐ´ĐŊĐ¸Ņ‚Đĩâ€Ļ",
"cutoff_day": "{count, plural, one {Đ´ĐĩĐŊ} other {Đ´ĐŊи}}", "cutoff_day": "{count, plural, one {Đ´ĐĩĐŊ} other {Đ´ĐŊи}}",
@@ -881,7 +875,7 @@
"daily_title_text_date": "E, dd MMM", "daily_title_text_date": "E, dd MMM",
"daily_title_text_date_year": "E, dd MMM yyyy", "daily_title_text_date_year": "E, dd MMM yyyy",
"dark": "ĐĸҊĐŧĐĩĐŊ", "dark": "ĐĸҊĐŧĐĩĐŊ",
"dark_theme": "ĐŸŅ€ĐĩĐŧиĐŊи ĐēҊĐŧ Ņ‚ŅŠĐŧĐŊа Ņ‚ĐĩĐŧа", "dark_theme": "ĐĸҊĐŧĐŊа Ņ‚ĐĩĐŧа",
"date": "Đ”Đ°Ņ‚Đ°", "date": "Đ”Đ°Ņ‚Đ°",
"date_after": "Đ”Đ°Ņ‚Đ° ҁĐģĐĩĐ´", "date_after": "Đ”Đ°Ņ‚Đ° ҁĐģĐĩĐ´",
"date_and_time": "Đ”Đ°Ņ‚Đ° и Ņ‡Đ°Ņ", "date_and_time": "Đ”Đ°Ņ‚Đ° и Ņ‡Đ°Ņ",
@@ -892,8 +886,12 @@
"day": "ДĐĩĐŊ", "day": "ДĐĩĐŊ",
"days": "ДĐŊи", "days": "ДĐŊи",
"deduplicate_all": "ДĐĩĐ´ŅƒĐŋĐģиĐēĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа Đ˛ŅĐ¸Ņ‡Đēи", "deduplicate_all": "ДĐĩĐ´ŅƒĐŋĐģиĐēĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа Đ˛ŅĐ¸Ņ‡Đēи",
"default_locale": "ЕзиĐē ĐŋĐž ĐŋĐžĐ´Ņ€Đ°ĐˇĐąĐ¸Ņ€Đ°ĐŊĐĩ", "deduplication_criteria_1": "РаСĐŧĐĩŅ€ ĐŊа ҁĐŊиĐŧĐēĐ°Ņ‚Đ° в ĐąĐ°ĐšŅ‚ĐžĐ˛Đĩ",
"default_locale_description": "Đ¤ĐžŅ€ĐŧĐ°Ņ‚ ĐŊа Đ´Đ°Ņ‚Đ° и Ņ‡Đ¸ŅĐģа ҁĐŋĐžŅ€ĐĩĐ´ ĐĩСиĐēĐžĐ˛Đ°Ņ‚Đ° ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēа ĐŊа ĐąŅ€Đ°ŅƒĐˇŅŠŅ€Đ°", "deduplication_criteria_2": "Đ‘Ņ€ĐžĐš EXIF даĐŊĐŊи",
"deduplication_info": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Са Đ´ĐĩĐ´ŅƒĐŋĐģиĐēĐ°Ņ†Đ¸ŅŅ‚Đ°",
"deduplication_info_description": "За Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž ĐŋŅ€ĐĩĐ´Đ˛Đ°Ņ€Đ¸Ņ‚ĐĩĐģĐŊĐž Đ¸ĐˇĐąĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа Ņ€ĐĩŅŅƒŅ€ŅĐ¸ и ĐŋŅ€ĐĩĐŧĐ°Ņ…Đ˛Đ°ĐŊĐĩ ĐŊа Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Đ¸ ĐŊа ĐĩĐ´Ņ€Đž, Ņ€Đ°ĐˇĐŗĐģĐĩĐļдаĐŧĐĩ:",
"default_locale": "ЛоĐēаĐģĐ¸ĐˇĐ°Ņ†Đ¸Ņ ĐŋĐž ĐŋĐžĐ´Ņ€Đ°ĐˇĐąĐ¸Ņ€Đ°ĐŊĐĩ",
"default_locale_description": "Đ¤ĐžŅ€ĐŧĐ°Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа Đ´Đ°Ņ‚Đ¸ и Ņ‡Đ¸ŅĐģа в ĐˇĐ°Đ˛Đ¸ŅĐ¸ĐŧĐžŅŅ‚ ĐžŅ‚ ĐĩСиĐēĐžĐ˛Đ°Ņ‚Đ° ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēа ĐŊа ĐąŅ€Đ°ŅƒĐˇŅŠŅ€Đ°",
"delete": "Đ˜ĐˇŅ‚Ņ€Đ¸Đš", "delete": "Đ˜ĐˇŅ‚Ņ€Đ¸Đš",
"delete_action_confirmation_message": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да Đ¸ĐˇŅ‚Ņ€Đ¸ĐĩŅ‚Đĩ Ņ‚ĐžĐˇĐ¸ ОйĐĩĐēŅ‚? ĐĄĐģĐĩдва ĐŋŅ€ĐĩĐŧĐĩŅŅ‚Đ˛Đ°ĐŊĐĩ ĐŊа ОйĐĩĐēŅ‚Đ° в ĐēĐžŅˆĐ° Са ĐžŅ‚ĐŋĐ°Đ´ŅŠŅ†Đ¸ ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ° и ҉Đĩ ĐŋĐžĐģŅƒŅ‡Đ¸Ņ‚Đĩ ĐŋŅ€ĐĩĐ´ĐģĐžĐļĐĩĐŊиĐĩ ОйĐĩĐēŅ‚Đ° да ĐąŅŠĐ´Đĩ Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚ ĐģĐžĐēаĐģĐŊĐž", "delete_action_confirmation_message": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да Đ¸ĐˇŅ‚Ņ€Đ¸ĐĩŅ‚Đĩ Ņ‚ĐžĐˇĐ¸ ОйĐĩĐēŅ‚? ĐĄĐģĐĩдва ĐŋŅ€ĐĩĐŧĐĩŅŅ‚Đ˛Đ°ĐŊĐĩ ĐŊа ОйĐĩĐēŅ‚Đ° в ĐēĐžŅˆĐ° Са ĐžŅ‚ĐŋĐ°Đ´ŅŠŅ†Đ¸ ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ° и ҉Đĩ ĐŋĐžĐģŅƒŅ‡Đ¸Ņ‚Đĩ ĐŋŅ€ĐĩĐ´ĐģĐžĐļĐĩĐŊиĐĩ ОйĐĩĐēŅ‚Đ° да ĐąŅŠĐ´Đĩ Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚ ĐģĐžĐēаĐģĐŊĐž",
"delete_action_prompt": "{count} ŅĐ° Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚Đ¸", "delete_action_prompt": "{count} ŅĐ° Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚Đ¸",
@@ -969,7 +967,7 @@
"downloading_media": "Đ˜ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩ ĐŊа ĐŧĐĩĐ´Đ¸Ņ", "downloading_media": "Đ˜ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩ ĐŊа ĐŧĐĩĐ´Đ¸Ņ",
"drop_files_to_upload": "ĐŸŅƒŅĐŊĐĩŅ‚Đĩ Ņ„Đ°ĐšĐģОвĐĩŅ‚Đĩ, Са да ĐŗĐ¸ ĐēĐ°Ņ‡Đ¸Ņ‚Đĩ", "drop_files_to_upload": "ĐŸŅƒŅĐŊĐĩŅ‚Đĩ Ņ„Đ°ĐšĐģОвĐĩŅ‚Đĩ, Са да ĐŗĐ¸ ĐēĐ°Ņ‡Đ¸Ņ‚Đĩ",
"duplicates": "Đ”ŅƒĐąĐģиĐēĐ°Ņ‚Đ¸", "duplicates": "Đ”ŅƒĐąĐģиĐēĐ°Ņ‚Đ¸",
"duplicates_description": "ИСйĐĩŅ€ĐĩŅ‚Đĩ Đ˛ŅŅĐēа ĐŗŅ€ŅƒĐŋа, ĐēĐ°Ņ‚Đž ĐŋĐžŅĐžŅ‡Đ¸Ņ‚Đĩ ĐēОи, аĐēĐž иĐŧа Ņ‚Đ°Đēива, ŅĐ° Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Đ¸.", "duplicates_description": "ИСйĐĩŅ€ĐĩŅ‚Đĩ Đ˛ŅŅĐēа ĐŗŅ€ŅƒĐŋа, ĐēĐ°Ņ‚Đž ĐŋĐžŅĐžŅ‡Đ¸Ņ‚Đĩ ĐēОи, аĐēĐž иĐŧа Ņ‚Đ°Đēива, ŅĐ° Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Đ¸",
"duration": "ĐŸŅ€ĐžĐ´ŅŠĐģĐļĐ¸Ņ‚ĐĩĐģĐŊĐžŅŅ‚", "duration": "ĐŸŅ€ĐžĐ´ŅŠĐģĐļĐ¸Ņ‚ĐĩĐģĐŊĐžŅŅ‚",
"edit": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€Đ°ĐŊĐĩ", "edit": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€Đ°ĐŊĐĩ",
"edit_album": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа аĐģĐąŅƒĐŧ", "edit_album": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа аĐģĐąŅƒĐŧ",
@@ -1006,8 +1004,6 @@
"editor_edits_applied_success": "ĐŖŅĐŋĐĩ҈ĐŊĐž ĐŋŅ€Đ¸ĐģĐ°ĐŗĐ°ĐŊĐĩ ĐŊа ĐŋŅ€ĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ", "editor_edits_applied_success": "ĐŖŅĐŋĐĩ҈ĐŊĐž ĐŋŅ€Đ¸ĐģĐ°ĐŗĐ°ĐŊĐĩ ĐŊа ĐŋŅ€ĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ",
"editor_flip_horizontal": "ĐžĐąŅŠŅ€ĐŊи Ņ…ĐžŅ€Đ¸ĐˇĐžĐŊŅ‚Đ°ĐģĐŊĐž", "editor_flip_horizontal": "ĐžĐąŅŠŅ€ĐŊи Ņ…ĐžŅ€Đ¸ĐˇĐžĐŊŅ‚Đ°ĐģĐŊĐž",
"editor_flip_vertical": "ĐžĐąŅŠŅ€ĐŊи вĐĩŅ€Ņ‚Đ¸ĐēаĐģĐŊĐž", "editor_flip_vertical": "ĐžĐąŅŠŅ€ĐŊи вĐĩŅ€Ņ‚Đ¸ĐēаĐģĐŊĐž",
"editor_handle_corner": "МаĐŊиĐŋ҃ĐģĐ°Ņ‚ĐžŅ€ {corner, select, top_left {ĐŗĐžŅ€ĐĩĐŊ ĐģŅĐ˛} top_right {ĐŗĐžŅ€ĐĩĐŊ Đ´ĐĩҁĐĩĐŊ} bottom_left {Đ´ĐžĐģĐĩĐŊ ĐģŅĐ˛} bottom_right {Đ´ĐžĐģĐĩĐŊ Đ´ĐĩҁĐĩĐŊ} other {в}} ŅŠĐŗŅŠĐģ",
"editor_handle_edge": "МаĐŊиĐŋ҃ĐģĐ°Ņ‚ĐžŅ€ {edge, select, top {ĐŗĐžŅ€ĐĩĐŊ} bottom {Đ´ĐžĐģĐĩĐŊ} left {ĐģŅĐ˛} right {Đ´ĐĩҁĐĩĐŊ} other {ĐŋĐž}} Ņ€ŅŠĐą",
"editor_orientation": "ĐžŅ€Đ¸ĐĩĐŊŅ‚Đ°Ņ†Đ¸Ņ", "editor_orientation": "ĐžŅ€Đ¸ĐĩĐŊŅ‚Đ°Ņ†Đ¸Ņ",
"editor_reset_all_changes": "Đ’ŅŠĐˇŅŅ‚Đ°ĐŊОви Đ˛ŅĐ¸Ņ‡Đēи ĐŋŅ€ĐžĐŧĐĩĐŊи", "editor_reset_all_changes": "Đ’ŅŠĐˇŅŅ‚Đ°ĐŊОви Đ˛ŅĐ¸Ņ‡Đēи ĐŋŅ€ĐžĐŧĐĩĐŊи",
"editor_rotate_left": "Đ—Đ°Đ˛ŅŠŅ€Ņ‚Đ¸ 90° ĐžĐąŅ€Đ°Ņ‚ĐŊĐž ĐŊа Ņ‡Đ°ŅĐžĐ˛ĐŊиĐēĐžĐ˛Đ°Ņ‚Đ° ҁ҂ҀĐĩĐģĐēа", "editor_rotate_left": "Đ—Đ°Đ˛ŅŠŅ€Ņ‚Đ¸ 90° ĐžĐąŅ€Đ°Ņ‚ĐŊĐž ĐŊа Ņ‡Đ°ŅĐžĐ˛ĐŊиĐēĐžĐ˛Đ°Ņ‚Đ° ҁ҂ҀĐĩĐģĐēа",
@@ -1073,7 +1069,6 @@
"failed_to_update_notification_status": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ОйĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐĩ ĐŊа ŅŅŠŅŅ‚ĐžŅĐŊиĐĩŅ‚Đž ĐŊа иСвĐĩŅŅ‚Đ¸ŅŅ‚Đ°", "failed_to_update_notification_status": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ОйĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐĩ ĐŊа ŅŅŠŅŅ‚ĐžŅĐŊиĐĩŅ‚Đž ĐŊа иСвĐĩŅŅ‚Đ¸ŅŅ‚Đ°",
"incorrect_email_or_password": "НĐĩĐŋŅ€Đ°Đ˛Đ¸ĐģĐĩĐŊ иĐŧĐĩĐšĐģ иĐģи ĐŋĐ°Ņ€ĐžĐģа", "incorrect_email_or_password": "НĐĩĐŋŅ€Đ°Đ˛Đ¸ĐģĐĩĐŊ иĐŧĐĩĐšĐģ иĐģи ĐŋĐ°Ņ€ĐžĐģа",
"library_folder_already_exists": "ĐĸаСи ĐŋаĐŋĐēа вĐĩ҇Đĩ ŅŅŠŅ‰ĐĩŅŅ‚Đ˛ŅƒĐ˛Đ°.", "library_folder_already_exists": "ĐĸаСи ĐŋаĐŋĐēа вĐĩ҇Đĩ ŅŅŠŅ‰ĐĩŅŅ‚Đ˛ŅƒĐ˛Đ°.",
"page_not_found": "ĐĄŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Đ°Ņ‚Đ° ĐŊĐĩ Đĩ ĐŊаĐŧĐĩŅ€ĐĩĐŊа",
"paths_validation_failed": "{paths, plural, one {# ĐŋŅŠŅ‚} other {# ĐŋŅŠŅ‚Đ¸Ņ‰Đ°}} ĐŊĐĩ ĐŋŅ€ĐĩĐŧиĐŊĐ°Ņ…Đ° ваĐģĐ¸Đ´Đ°Ņ†Đ¸Ņ", "paths_validation_failed": "{paths, plural, one {# ĐŋŅŠŅ‚} other {# ĐŋŅŠŅ‚Đ¸Ņ‰Đ°}} ĐŊĐĩ ĐŋŅ€ĐĩĐŧиĐŊĐ°Ņ…Đ° ваĐģĐ¸Đ´Đ°Ņ†Đ¸Ņ",
"profile_picture_transparent_pixels": "ĐŸŅ€ĐžŅ„Đ¸ĐģĐŊĐ¸Ņ‚Đĩ ҁĐŊиĐŧĐēи ĐŊĐĩ ĐŧĐžĐŗĐ°Ņ‚ да иĐŧĐ°Ņ‚ ĐŋŅ€ĐžĐˇŅ€Đ°Ņ‡ĐŊи ĐŋиĐēҁĐĩĐģи. МоĐģŅ, ŅƒĐ˛ĐĩĐģĐ¸Ņ‡ĐĩŅ‚Đĩ и/иĐģи ĐŋŅ€ĐĩĐŧĐĩҁ҂ĐĩŅ‚Đĩ Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩŅ‚Đž.", "profile_picture_transparent_pixels": "ĐŸŅ€ĐžŅ„Đ¸ĐģĐŊĐ¸Ņ‚Đĩ ҁĐŊиĐŧĐēи ĐŊĐĩ ĐŧĐžĐŗĐ°Ņ‚ да иĐŧĐ°Ņ‚ ĐŋŅ€ĐžĐˇŅ€Đ°Ņ‡ĐŊи ĐŋиĐēҁĐĩĐģи. МоĐģŅ, ŅƒĐ˛ĐĩĐģĐ¸Ņ‡ĐĩŅ‚Đĩ и/иĐģи ĐŋŅ€ĐĩĐŧĐĩҁ҂ĐĩŅ‚Đĩ Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩŅ‚Đž.",
"quota_higher_than_disk_size": "ЗададĐĩĐŊа Đĩ ĐēĐ˛ĐžŅ‚Đ°, ĐŋĐž-ĐŗĐžĐģŅĐŧа ĐžŅ‚ Ņ€Đ°ĐˇĐŧĐĩŅ€Đ° ĐŊа Đ´Đ¸ŅĐēа", "quota_higher_than_disk_size": "ЗададĐĩĐŊа Đĩ ĐēĐ˛ĐžŅ‚Đ°, ĐŋĐž-ĐŗĐžĐģŅĐŧа ĐžŅ‚ Ņ€Đ°ĐˇĐŧĐĩŅ€Đ° ĐŊа Đ´Đ¸ŅĐēа",
@@ -1173,7 +1168,6 @@
"exif_bottom_sheet_people": "ĐĨОРА", "exif_bottom_sheet_people": "ĐĨОРА",
"exif_bottom_sheet_person_add_person": "Добави иĐŧĐĩ", "exif_bottom_sheet_person_add_person": "Добави иĐŧĐĩ",
"exit_slideshow": "Đ˜ĐˇŅ…ĐžĐ´ ĐžŅ‚ ҁĐģĐ°ĐšĐ´ŅˆĐžŅƒŅ‚Đž", "exit_slideshow": "Đ˜ĐˇŅ…ĐžĐ´ ĐžŅ‚ ҁĐģĐ°ĐšĐ´ŅˆĐžŅƒŅ‚Đž",
"expand": "Đ Đ°ĐˇĐŗŅŠĐŊи",
"expand_all": "Đ Đ°ĐˇŅˆĐ¸Ņ€Đ¸ Đ˛ŅĐ¸Ņ‡Đēи", "expand_all": "Đ Đ°ĐˇŅˆĐ¸Ņ€Đ¸ Đ˛ŅĐ¸Ņ‡Đēи",
"experimental_settings_new_asset_list_subtitle": "В Ņ€Đ°ĐˇĐ˛Đ¸Ņ‚Đ¸Đĩ", "experimental_settings_new_asset_list_subtitle": "В Ņ€Đ°ĐˇĐ˛Đ¸Ņ‚Đ¸Đĩ",
"experimental_settings_new_asset_list_title": "ВĐēĐģŅŽŅ‡Đ¸ ĐĩĐēҁĐŋĐĩŅ€Đ¸ĐŧĐĩĐŊŅ‚Đ°ĐģĐŊа ĐŋĐžĐ´Ņ€Đĩдйа ĐŊа ҁĐŊиĐŧĐēи", "experimental_settings_new_asset_list_title": "ВĐēĐģŅŽŅ‡Đ¸ ĐĩĐēҁĐŋĐĩŅ€Đ¸ĐŧĐĩĐŊŅ‚Đ°ĐģĐŊа ĐŋĐžĐ´Ņ€Đĩдйа ĐŊа ҁĐŊиĐŧĐēи",
@@ -1218,7 +1212,6 @@
"filter_description": "ĐŖŅĐģĐžĐ˛Đ¸Ņ Са Ņ„Đ¸ĐģŅ‚Ņ€Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ОйĐĩĐēŅ‚Đ¸", "filter_description": "ĐŖŅĐģĐžĐ˛Đ¸Ņ Са Ņ„Đ¸ĐģŅ‚Ņ€Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ОйĐĩĐēŅ‚Đ¸",
"filter_people": "ФиĐģŅ‚Ņ€Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа Ņ…ĐžŅ€Đ°", "filter_people": "ФиĐģŅ‚Ņ€Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа Ņ…ĐžŅ€Đ°",
"filter_places": "ФиĐģŅ‚ŅŠŅ€ ĐŋĐž ĐŧŅŅŅ‚Đž", "filter_places": "ФиĐģŅ‚ŅŠŅ€ ĐŋĐž ĐŧŅŅŅ‚Đž",
"filter_tags": "ФиĐģŅ‚Ņ€Đ¸Ņ€Đ°ĐŊĐĩ ĐŋĐž ĐĩŅ‚Đ¸ĐēĐĩŅ‚Đ¸",
"filters": "ФиĐģŅ‚Ņ€Đ¸", "filters": "ФиĐģŅ‚Ņ€Đ¸",
"find_them_fast": "НаĐŧĐĩŅ€ĐĩŅ‚Đĩ ĐŗĐ¸ ĐąŅŠŅ€ĐˇĐž ĐŋĐž иĐŧĐĩ ҁ Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ", "find_them_fast": "НаĐŧĐĩŅ€ĐĩŅ‚Đĩ ĐŗĐ¸ ĐąŅŠŅ€ĐˇĐž ĐŋĐž иĐŧĐĩ ҁ Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ",
"first": "ĐŸŅŠŅ€Đ˛Đ¸", "first": "ĐŸŅŠŅ€Đ˛Đ¸",
@@ -1318,7 +1311,7 @@
"import_path": "ĐŸŅŠŅ‚ Са иĐŧĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ", "import_path": "ĐŸŅŠŅ‚ Са иĐŧĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ",
"in_albums": "В {count, plural, one {# аĐģĐąŅƒĐŧ} other {# аĐģĐąŅƒĐŧа}}", "in_albums": "В {count, plural, one {# аĐģĐąŅƒĐŧ} other {# аĐģĐąŅƒĐŧа}}",
"in_archive": "В Đ°Ņ€Ņ…Đ¸Đ˛", "in_archive": "В Đ°Ņ€Ņ…Đ¸Đ˛",
"in_year": "ĐŸŅ€ĐĩС {year}", "in_year": "{year} Đŗ.",
"in_year_selector": "ĐŸŅ€ĐĩС", "in_year_selector": "ĐŸŅ€ĐĩС",
"include_archived": "ВĐēĐģŅŽŅ‡Đ˛Đ°ĐŊĐĩ ĐŊа Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊи", "include_archived": "ВĐēĐģŅŽŅ‡Đ˛Đ°ĐŊĐĩ ĐŊа Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊи",
"include_shared_albums": "ВĐēĐģŅŽŅ‡Đ˛Đ°ĐŊĐĩ ĐŊа ҁĐŋОдĐĩĐģĐĩĐŊи аĐģĐąŅƒĐŧи", "include_shared_albums": "ВĐēĐģŅŽŅ‡Đ˛Đ°ĐŊĐĩ ĐŊа ҁĐŋОдĐĩĐģĐĩĐŊи аĐģĐąŅƒĐŧи",
@@ -1386,11 +1379,9 @@
"library_page_sort_title": "Đ—Đ°ĐŗĐģавиĐĩ ĐŊа аĐģĐąŅƒĐŧа", "library_page_sort_title": "Đ—Đ°ĐŗĐģавиĐĩ ĐŊа аĐģĐąŅƒĐŧа",
"licenses": "Đ›Đ¸Ņ†ĐĩĐŊСи", "licenses": "Đ›Đ¸Ņ†ĐĩĐŊСи",
"light": "ХвĐĩŅ‚ĐģĐž", "light": "ХвĐĩŅ‚ĐģĐž",
"light_theme": "ĐŸŅ€ĐĩĐŧиĐŊи ĐēҊĐŧ ŅĐ˛ĐĩŅ‚Đģа Ņ‚ĐĩĐŧа",
"like": "ĐĨĐ°Ņ€ĐĩŅĐ°ĐšŅ‚Đĩ", "like": "ĐĨĐ°Ņ€ĐĩŅĐ°ĐšŅ‚Đĩ",
"like_deleted": "ĐšĐ°Ņ‚Đž Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚", "like_deleted": "ĐšĐ°Ņ‚Đž Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚",
"link_motion_video": "ЛиĐŊĐē ĐēҊĐŧ видĐĩĐž", "link_motion_video": "ЛиĐŊĐē ĐēҊĐŧ видĐĩĐž",
"link_to_docs": "За ĐŋОвĐĩ҇Đĩ иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ виĐļŅ‚Đĩ <link>Đ´ĐžĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Đ¸ŅŅ‚Đ°</link>.",
"link_to_oauth": "ЛиĐŊĐē ĐēҊĐŧ OAuth", "link_to_oauth": "ЛиĐŊĐē ĐēҊĐŧ OAuth",
"linked_oauth_account": "ĐĄĐ˛ŅŠŅ€ĐˇĐ°ĐŊ OAuth аĐēĐ°ŅƒĐŊŅ‚", "linked_oauth_account": "ĐĄĐ˛ŅŠŅ€ĐˇĐ°ĐŊ OAuth аĐēĐ°ŅƒĐŊŅ‚",
"list": "Đ›Đ¸ŅŅ‚", "list": "Đ›Đ¸ŅŅ‚",
@@ -1651,15 +1642,13 @@
"online": "ОĐŊĐģаКĐŊ", "online": "ОĐŊĐģаКĐŊ",
"only_favorites": "ХаĐŧĐž ĐģŅŽĐąĐ¸Đŧи", "only_favorites": "ХаĐŧĐž ĐģŅŽĐąĐ¸Đŧи",
"open": "ĐžŅ‚Đ˛ĐžŅ€Đ¸", "open": "ĐžŅ‚Đ˛ĐžŅ€Đ¸",
"open_calendar": "ĐžŅ‚Đ˛ĐžŅ€Đ¸ ĐēаĐģĐĩĐŊĐ´Đ°Ņ€",
"open_in_browser": "ĐžŅ‚Đ˛ĐžŅ€Đ¸ в ĐąŅ€Đ°ŅƒĐˇŅŠŅ€",
"open_in_map_view": "ĐžŅ‚Đ˛ĐžŅ€Đ¸ Đ¸ĐˇĐŗĐģĐĩĐ´ ĐŊа ĐēĐ°Ņ€Ņ‚Đ°", "open_in_map_view": "ĐžŅ‚Đ˛ĐžŅ€Đ¸ Đ¸ĐˇĐŗĐģĐĩĐ´ ĐŊа ĐēĐ°Ņ€Ņ‚Đ°",
"open_in_openstreetmap": "ĐžŅ‚Đ˛ĐžŅ€Đ¸ в OpenStreetMap", "open_in_openstreetmap": "ĐžŅ‚Đ˛ĐžŅ€Đ¸ в OpenStreetMap",
"open_the_search_filters": "ĐžŅ‚Đ˛Đ°Ņ€Đ¸ Ņ„Đ¸ĐģŅ‚Ņ€Đ¸Ņ‚Đĩ Са Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ", "open_the_search_filters": "ĐžŅ‚Đ˛Đ°Ņ€Đ¸ Ņ„Đ¸ĐģŅ‚Ņ€Đ¸Ņ‚Đĩ Са Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ",
"options": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи", "options": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи",
"or": "иĐģи", "or": "иĐģи",
"organize_into_albums": "ĐŸĐžĐ´Ņ€ĐĩĐ´ĐĩŅ‚Đĩ в аĐģĐąŅƒĐŧи", "organize_into_albums": "Organitzar per àlbums",
"organize_into_albums_description": "ДобавĐĩŅ‚Đĩ ĐŊаĐģĐ¸Ņ‡ĐŊĐ¸Ņ‚Đĩ ҁĐŊиĐŧĐēи в аĐģĐąŅƒĐŧи, ĐēĐ°Ņ‚Đž иСĐŋĐžĐģĐˇĐ˛Đ°Ņ‚Đĩ Ņ‚ĐĩĐēŅƒŅ‰Đ¸Ņ‚Đĩ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи Са ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ¸Ņ€Đ°ĐŊĐĩ", "organize_into_albums_description": "Posar les fotos existents dins dels àlbums fent servir la configuraciÃŗ de sincronitzaciÃŗ",
"organize_your_library": "ĐžŅ€ĐŗĐ°ĐŊĐ¸ĐˇĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа Đ˛Đ°ŅˆĐ°Ņ‚Đ° йийĐģĐ¸ĐžŅ‚ĐĩĐēа", "organize_your_library": "ĐžŅ€ĐŗĐ°ĐŊĐ¸ĐˇĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа Đ˛Đ°ŅˆĐ°Ņ‚Đ° йийĐģĐ¸ĐžŅ‚ĐĩĐēа",
"original": "ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģ", "original": "ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģ",
"other": "Đ”Ņ€ŅƒĐŗĐ¸", "other": "Đ”Ņ€ŅƒĐŗĐ¸",
@@ -1807,13 +1796,14 @@
"purchase_server_description_2": "ĐĄŅ‚Đ°Ņ‚ŅƒŅ ĐŊа ĐŋĐžĐ´Đ´Ņ€ŅŠĐļĐŊиĐē", "purchase_server_description_2": "ĐĄŅ‚Đ°Ņ‚ŅƒŅ ĐŊа ĐŋĐžĐ´Đ´Ņ€ŅŠĐļĐŊиĐē",
"purchase_server_title": "ĐĄŅŠŅ€Đ˛ŅŠŅ€", "purchase_server_title": "ĐĄŅŠŅ€Đ˛ŅŠŅ€",
"purchase_settings_server_activated": "ĐŸŅ€ĐžĐ´ŅƒĐēŅ‚ĐžĐ˛Đ¸ŅŅ‚ ĐēĐģŅŽŅ‡ ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ° ҁĐĩ ҃ĐŋŅ€Đ°Đ˛ĐģŅĐ˛Đ° ĐžŅ‚ адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ°", "purchase_settings_server_activated": "ĐŸŅ€ĐžĐ´ŅƒĐēŅ‚ĐžĐ˛Đ¸ŅŅ‚ ĐēĐģŅŽŅ‡ ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ° ҁĐĩ ҃ĐŋŅ€Đ°Đ˛ĐģŅĐ˛Đ° ĐžŅ‚ адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ°",
"query_asset_id": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ ĐŊа ĐĩĐģĐĩĐŧĐĩĐŊŅ‚ ĐŋĐž ID", "query_asset_id": "Buscar item per ID",
"queue_status": "В ĐžĐŋĐ°ŅˆĐēа {count} ĐžŅ‚ {total}", "queue_status": "В ĐžĐŋĐ°ŅˆĐēа {count} ĐžŅ‚ {total}",
"rate_asset": "ЗадаваĐŊĐĩ ĐŊа Ņ€ĐĩĐšŅ‚Đ¸ĐŊĐŗ", "rate_asset": "ЗадаваĐŊĐĩ ĐŊа Ņ€ĐĩĐšŅ‚Đ¸ĐŊĐŗ",
"rating": "ĐžŅ†ĐĩĐŊĐēа ҁҊҁ СвĐĩСди", "rating": "ĐžŅ†ĐĩĐŊĐēа ҁҊҁ СвĐĩСди",
"rating_clear": "Đ˜ĐˇŅ‡Đ¸ŅŅ‚Đ¸ ĐžŅ†ĐĩĐŊĐēĐ°Ņ‚Đ°", "rating_clear": "Đ˜ĐˇŅ‡Đ¸ŅŅ‚Đ¸ ĐžŅ†ĐĩĐŊĐēĐ°Ņ‚Đ°",
"rating_count": "{count, plural, =0 {БĐĩС Ņ€ĐĩĐšŅ‚Đ¸ĐŊĐŗ} one {# СвĐĩСда} other {# СвĐĩСди}}", "rating_count": "{count, plural, one {# СвĐĩСда} other {# СвĐĩСди}}",
"rating_description": "ПоĐēаĐļи EXIF ĐžŅ†ĐĩĐŊĐēĐ°Ņ‚Đ° в ĐŋаĐŊĐĩĐģа ҁ иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ", "rating_description": "ПоĐēаĐļи EXIF ĐžŅ†ĐĩĐŊĐēĐ°Ņ‚Đ° в ĐŋаĐŊĐĩĐģа ҁ иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ",
"rating_set": "ЗададĐĩĐŊ Đĩ Ņ€ĐĩĐšŅ‚Đ¸ĐŊĐŗ {rating, plural, one {# СвĐĩСда} other {# СвĐĩСди}}",
"reaction_options": "Đ˜ĐˇĐąĐžŅ€ ĐŊа Ņ€ĐĩаĐēŅ†Đ¸Ņ", "reaction_options": "Đ˜ĐˇĐąĐžŅ€ ĐŊа Ņ€ĐĩаĐēŅ†Đ¸Ņ",
"read_changelog": "ĐŸŅ€ĐžŅ‡ĐĩŅ‚Đ¸ ĐŋŅ€ĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ", "read_changelog": "ĐŸŅ€ĐžŅ‡ĐĩŅ‚Đ¸ ĐŋŅ€ĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ",
"readonly_mode_disabled": "Đ ĐĩĐļиĐŧа ŅĐ°ĐŧĐž Са ҇ĐĩŅ‚ĐĩĐŊĐĩ Đĩ Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊ", "readonly_mode_disabled": "Đ ĐĩĐļиĐŧа ŅĐ°ĐŧĐž Са ҇ĐĩŅ‚ĐĩĐŊĐĩ Đĩ Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊ",
@@ -1885,10 +1875,7 @@
"reset_pin_code_success": "ĐŖŅĐŋĐĩ҈ĐŊĐž ĐŊ҃ĐģĐ¸Ņ€Đ°ĐŊ ПИН ĐēОд", "reset_pin_code_success": "ĐŖŅĐŋĐĩ҈ĐŊĐž ĐŊ҃ĐģĐ¸Ņ€Đ°ĐŊ ПИН ĐēОд",
"reset_pin_code_with_password": "ĐĄ Đ˛Đ°ŅˆĐ°Ņ‚Đ° ĐŋĐ°Ņ€ĐžĐģа ĐŧĐžĐļĐĩŅ‚Đĩ виĐŊĐ°ĐŗĐ¸ да ĐŊ҃ĐģĐ¸Ņ€Đ°Ņ‚Đĩ ŅĐ˛ĐžŅ ПИН ĐēОд", "reset_pin_code_with_password": "ĐĄ Đ˛Đ°ŅˆĐ°Ņ‚Đ° ĐŋĐ°Ņ€ĐžĐģа ĐŧĐžĐļĐĩŅ‚Đĩ виĐŊĐ°ĐŗĐ¸ да ĐŊ҃ĐģĐ¸Ņ€Đ°Ņ‚Đĩ ŅĐ˛ĐžŅ ПИН ĐēОд",
"reset_sqlite": "ĐŅƒĐģĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐąĐ°ĐˇĐ°Ņ‚Đ° даĐŊĐŊи SQLite", "reset_sqlite": "ĐŅƒĐģĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐąĐ°ĐˇĐ°Ņ‚Đ° даĐŊĐŊи SQLite",
"reset_sqlite_clear_app_data": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи даĐŊĐŊĐ¸Ņ‚Đĩ", "reset_sqlite_confirmation": "ĐĐ°Đ¸ŅŅ‚Đ¸ĐŊа Đģи Đ¸ŅĐēĐ°Ņ‚Đĩ да ĐŊ҃ĐģĐ¸Ņ€Đ°Ņ‚Đĩ ĐąĐ°ĐˇĐ°Ņ‚Đ° даĐŊĐŊи SQLite? ĐŠĐĩ Ņ‚Ņ€ŅĐąĐ˛Đ° да иСĐģĐĩСĐĩŅ‚Đĩ ĐžŅ‚ ŅĐ¸ŅŅ‚ĐĩĐŧĐ°Ņ‚Đ° и да ҁĐĩ вĐŋĐ¸ŅˆĐĩŅ‚Đĩ ĐžŅ‚ĐŊОвО Са ĐŊОва ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Ņ ĐŊа даĐŊĐŊĐ¸Ņ‚Đĩ",
"reset_sqlite_confirmation": "ĐĐ°Đ¸ŅŅ‚Đ¸ĐŊа Đģи Đ¸ŅĐēĐ°Ņ‚Đĩ да ĐŊ҃ĐģĐ¸Ņ€Đ°Ņ‚Đĩ даĐŊĐŊĐ¸Ņ‚Đĩ ĐŊа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž? ĐĸОва ҉Đĩ ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊи Đ˛ŅĐ¸Ņ‡Đēи ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи и ҉Đĩ Ви ĐžŅ‚ĐŋĐ¸ŅˆĐĩ ĐžŅ‚ ŅĐ¸ŅŅ‚ĐĩĐŧĐ°Ņ‚Đ°.",
"reset_sqlite_confirmation_note": "БĐĩĐģĐĩĐļĐēа: ĐĄĐģĐĩĐ´ ĐŋŅ€ĐĩĐŧĐ°Ņ…Đ˛Đ°ĐŊĐĩ ĐŊа даĐŊĐŊĐ¸Ņ‚Đĩ ҉Đĩ Ņ‚Ņ€ŅĐąĐ˛Đ° да Ņ€ĐĩŅŅ‚Đ°Ņ€Ņ‚Đ¸Ņ€Đ°Ņ‚Đĩ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž.",
"reset_sqlite_done": "ДаĐŊĐŊĐ¸Ņ‚Đĩ ĐŊа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž ŅĐ° ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚Đ¸. МоĐģŅ, Ņ€ĐĩŅŅ‚Đ°Ņ€Ņ‚Đ¸Ņ€Đ°ĐšŅ‚Đĩ Immich и ҁĐĩ вĐŋĐ¸ŅˆĐĩŅ‚Đĩ ĐžŅ‚ĐŊОвО.",
"reset_sqlite_success": "ĐŖŅĐŋĐĩ҈ĐŊĐž ĐŊ҃ĐģĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐąĐ°ĐˇĐ°Ņ‚Đ° даĐŊĐŊи SQLite", "reset_sqlite_success": "ĐŖŅĐŋĐĩ҈ĐŊĐž ĐŊ҃ĐģĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐąĐ°ĐˇĐ°Ņ‚Đ° даĐŊĐŊи SQLite",
"reset_to_default": "Đ’Ņ€ŅŠŅ‰Đ°ĐŊĐĩ ĐŊа Ņ„Đ°ĐąŅ€Đ¸Ņ‡ĐŊи ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи", "reset_to_default": "Đ’Ņ€ŅŠŅ‰Đ°ĐŊĐĩ ĐŊа Ņ„Đ°ĐąŅ€Đ¸Ņ‡ĐŊи ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи",
"resolution": "Đ ĐĩСОĐģŅŽŅ†Đ¸Ņ", "resolution": "Đ ĐĩСОĐģŅŽŅ†Đ¸Ņ",
@@ -1916,7 +1903,6 @@
"saved_settings": "ЗаĐŋаСĐĩĐŊи ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи", "saved_settings": "ЗаĐŋаСĐĩĐŊи ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи",
"say_something": "КаĐļи ĐŊĐĩŅ‰Đž", "say_something": "КаĐļи ĐŊĐĩŅ‰Đž",
"scaffold_body_error_occurred": "Đ’ŅŠĐˇĐŊиĐēĐŊа ĐŗŅ€Đĩ҈Đēа", "scaffold_body_error_occurred": "Đ’ŅŠĐˇĐŊиĐēĐŊа ĐŗŅ€Đĩ҈Đēа",
"scaffold_body_error_unrecoverable": "Đ’ŅŠĐˇĐŊиĐēĐŊа ĐŊĐĩĐŋĐžĐŋŅ€Đ°Đ˛Đ¸Đŧа ĐŗŅ€Đĩ҈Đēа. МоĐģŅ, ҁĐŋОдĐĩĐģĐĩŅ‚Đĩ ĐŗŅ€Đĩ҈ĐēĐ°Ņ‚Đ° и Ņ‚Ņ€Đ°ŅĐ¸Ņ€Đ°ĐŊĐĩŅ‚Đž ĐŊа ҁ҂ĐĩĐēа в Discord иĐģи GitHub, Са да ĐŧĐžĐļĐĩĐŧ да Ви ĐŋĐžĐŧĐžĐŗĐŊĐĩĐŧ. АĐēĐž ĐąŅŠĐ´ĐĩŅ‚Đĩ ĐŋĐžŅŅŠĐ˛ĐĩŅ‚Đ˛Đ°ĐŊи, ĐŧĐžĐļĐĩ да Đ¸ĐˇŅ‡Đ¸ŅŅ‚Đ¸Ņ‚Đĩ даĐŊĐŊĐ¸Ņ‚Đĩ ĐŊа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž.",
"scan": "ĐĄĐēаĐŊĐ¸Ņ€Đ°ĐŊe", "scan": "ĐĄĐēаĐŊĐ¸Ņ€Đ°ĐŊe",
"scan_all_libraries": "ĐĄĐēаĐŊĐ¸Ņ€Đ°Đš Đ˛ŅĐ¸Ņ‡Đēи йийĐģĐ¸ĐžŅ‚ĐĩĐēи", "scan_all_libraries": "ĐĄĐēаĐŊĐ¸Ņ€Đ°Đš Đ˛ŅĐ¸Ņ‡Đēи йийĐģĐ¸ĐžŅ‚ĐĩĐēи",
"scan_library": "ĐĄĐēаĐŊĐ¸Ņ€Đ°Đš", "scan_library": "ĐĄĐēаĐŊĐ¸Ņ€Đ°Đš",
@@ -1952,7 +1938,6 @@
"search_filter_ocr": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ ĐŊa Ņ‚ĐĩĐēҁ҂", "search_filter_ocr": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ ĐŊa Ņ‚ĐĩĐēҁ҂",
"search_filter_people_title": "ИСйĐĩŅ€Đ¸ Ņ…ĐžŅ€Đ°", "search_filter_people_title": "ИСйĐĩŅ€Đ¸ Ņ…ĐžŅ€Đ°",
"search_filter_star_rating": "КĐģĐ°ŅĐ°Ņ†Đ¸Ņ ҁҊҁ СвĐĩСди", "search_filter_star_rating": "КĐģĐ°ŅĐ°Ņ†Đ¸Ņ ҁҊҁ СвĐĩСди",
"search_filter_tags_title": "ИСйĐĩŅ€ĐĩŅ‚Đĩ ĐĩŅ‚Đ¸ĐēĐĩŅ‚Đ¸",
"search_for": "ĐĸŅŠŅ€ŅĐ¸ Са", "search_for": "ĐĸŅŠŅ€ŅĐ¸ Са",
"search_for_existing_person": "ĐĸŅŠŅ€ŅĐ¸ ŅŅŠŅ‰ĐĩŅŅ‚Đ˛ŅƒĐ˛Đ°Ņ‰ Ņ‡ĐžĐ˛ĐĩĐē", "search_for_existing_person": "ĐĸŅŠŅ€ŅĐ¸ ŅŅŠŅ‰ĐĩŅŅ‚Đ˛ŅƒĐ˛Đ°Ņ‰ Ņ‡ĐžĐ˛ĐĩĐē",
"search_no_more_result": "ĐŅĐŧа Đ´Ņ€ŅƒĐŗĐ¸ Ņ€ĐĩĐˇŅƒĐģŅ‚Đ°Ņ‚Đ¸", "search_no_more_result": "ĐŅĐŧа Đ´Ņ€ŅƒĐŗĐ¸ Ņ€ĐĩĐˇŅƒĐģŅ‚Đ°Ņ‚Đ¸",
@@ -2032,9 +2017,6 @@
"set_profile_picture": "Đ—Đ°Đ´Đ°ĐšŅ‚Đĩ ĐŋŅ€ĐžŅ„Đ¸ĐģĐŊа ҁĐŊиĐŧĐēа", "set_profile_picture": "Đ—Đ°Đ´Đ°ĐšŅ‚Đĩ ĐŋŅ€ĐžŅ„Đ¸ĐģĐŊа ҁĐŊиĐŧĐēа",
"set_slideshow_to_fullscreen": "Đ—Đ°Đ´Đ°ĐšŅ‚Đĩ ĐĄĐģĐ°ĐšĐ´ŅˆĐžŅƒ ĐŊа Ņ†ŅĐģ ĐĩĐēŅ€Đ°ĐŊ", "set_slideshow_to_fullscreen": "Đ—Đ°Đ´Đ°ĐšŅ‚Đĩ ĐĄĐģĐ°ĐšĐ´ŅˆĐžŅƒ ĐŊа Ņ†ŅĐģ ĐĩĐēŅ€Đ°ĐŊ",
"set_stack_primary_asset": "Задай ĐēĐ°Ņ‚Đž ĐžŅĐŊОвĐŊи ОйĐĩĐēŅ‚Đ¸", "set_stack_primary_asset": "Задай ĐēĐ°Ņ‚Đž ĐžŅĐŊОвĐŊи ОйĐĩĐēŅ‚Đ¸",
"setting_image_navigation_enable_subtitle": "АĐēĐž Đĩ Đ¸ĐˇĐąŅ€Đ°ĐŊĐž, ĐŧĐžĐļĐĩŅ‚Đĩ да ĐŊĐ°Đ˛Đ¸ĐŗĐ¸Ņ€Đ°Ņ‚Đĩ ĐēҊĐŧ ĐŋŅ€ĐĩĐ´Đ¸ŅˆĐŊа/ҁĐģĐĩĐ´Đ˛Đ°Ņ‰Đ° ҁĐŊиĐŧĐēа ĐēĐ°Ņ‚Đž ĐŊĐ°Ņ‚Đ¸ŅĐŊĐĩŅ‚Đĩ Đ˛ŅŠŅ€Ņ…Ņƒ ĐģŅĐ˛Đ°Ņ‚Đ°/Đ´ŅŅĐŊĐ°Ņ‚Đ° ŅŅ‚Ņ€Đ°ĐŊа ĐŊа ĐĩĐēŅ€Đ°ĐŊа.",
"setting_image_navigation_enable_title": "ĐĐ°Ņ‚Đ¸ŅĐŊи Са ĐŊĐ°Đ˛Đ¸ĐŗĐ¸Ņ€Đ°ĐŊĐĩ",
"setting_image_navigation_title": "ĐĐ°Đ˛Đ¸ĐŗĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа ҁĐŊиĐŧĐēа",
"setting_image_viewer_help": "ĐŸŅ€Đ¸ ĐŋĐžĐēаСваĐŊĐĩ ĐŊа ОйĐĩĐēŅ‚ ĐŋŅŠŅ€Đ˛Đž ҁĐĩ ĐˇĐ°Ņ€ĐĩĐļда ĐŧиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Đ°, ĐŋĐžŅĐģĐĩ Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩ ҁҊҁ ҁҀĐĩĐ´ĐŊĐž ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž (аĐēĐž Đĩ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊĐž) и ĐŊаĐēŅ€Đ°Ņ ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģа (аĐēĐž Đĩ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊĐž).", "setting_image_viewer_help": "ĐŸŅ€Đ¸ ĐŋĐžĐēаСваĐŊĐĩ ĐŊа ОйĐĩĐēŅ‚ ĐŋŅŠŅ€Đ˛Đž ҁĐĩ ĐˇĐ°Ņ€ĐĩĐļда ĐŧиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Đ°, ĐŋĐžŅĐģĐĩ Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩ ҁҊҁ ҁҀĐĩĐ´ĐŊĐž ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž (аĐēĐž Đĩ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊĐž) и ĐŊаĐēŅ€Đ°Ņ ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģа (аĐēĐž Đĩ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊĐž).",
"setting_image_viewer_original_subtitle": "Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸ Са да ҁĐĩ ĐˇĐ°Ņ€ĐĩĐļда ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐŊĐžŅ‚Đž Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩ в ĐŋҊĐģĐĩĐŊ Ņ€Đ°ĐˇĐŧĐĩŅ€ (ĐŗĐžĐģŅĐŧ!). Đ—Đ°ĐąŅ€Đ°ĐŊи Са да ҁĐĩ ĐŊаĐŧаĐģи ОйĐĩĐŧа ĐŊа даĐŊĐŊĐ¸Ņ‚Đĩ (ĐŋĐž ĐŧŅ€ĐĩĐļĐ°Ņ‚Đ° и в ĐēĐĩŅˆĐ° ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž).", "setting_image_viewer_original_subtitle": "Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸ Са да ҁĐĩ ĐˇĐ°Ņ€ĐĩĐļда ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐŊĐžŅ‚Đž Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩ в ĐŋҊĐģĐĩĐŊ Ņ€Đ°ĐˇĐŧĐĩŅ€ (ĐŗĐžĐģŅĐŧ!). Đ—Đ°ĐąŅ€Đ°ĐŊи Са да ҁĐĩ ĐŊаĐŧаĐģи ОйĐĩĐŧа ĐŊа даĐŊĐŊĐ¸Ņ‚Đĩ (ĐŋĐž ĐŧŅ€ĐĩĐļĐ°Ņ‚Đ° и в ĐēĐĩŅˆĐ° ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž).",
"setting_image_viewer_original_title": "Đ—Đ°Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐŊĐž Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩ", "setting_image_viewer_original_title": "Đ—Đ°Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐŊĐž Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩ",
@@ -2201,7 +2183,6 @@
"support": "ĐŸĐžĐ´Đ´Ņ€ŅŠĐļĐēа", "support": "ĐŸĐžĐ´Đ´Ņ€ŅŠĐļĐēа",
"support_and_feedback": "ĐŸĐžĐ´Đ´Ņ€ŅŠĐļĐēа и ĐžĐąŅ€Đ°Ņ‚ĐŊа Đ˛Ņ€ŅŠĐˇĐēа", "support_and_feedback": "ĐŸĐžĐ´Đ´Ņ€ŅŠĐļĐēа и ĐžĐąŅ€Đ°Ņ‚ĐŊа Đ˛Ņ€ŅŠĐˇĐēа",
"support_third_party_description": "Đ’Đ°ŅˆĐ°Ņ‚Đ° иĐŊŅŅ‚Đ°ĐģĐ°Ņ†Đ¸Ņ ĐŊа Immich Đĩ ĐŋаĐēĐĩŅ‚Đ¸Ņ€Đ°ĐŊа ĐžŅ‚ ҂ҀĐĩŅ‚Đ° ŅŅ‚Ņ€Đ°ĐŊа. ĐŸŅ€ĐžĐąĐģĐĩĐŧĐ¸Ņ‚Đĩ, ĐēĐžĐ¸Ņ‚Đž иСĐŋĐ¸Ņ‚Đ˛Đ°Ņ‚Đĩ, ĐŧĐžĐļĐĩ да ŅĐ° ĐŋŅ€Đ¸Ņ‡Đ¸ĐŊĐĩĐŊи ĐžŅ‚ Ņ‚ĐžĐˇĐ¸ ĐŋаĐēĐĩŅ‚, ĐˇĐ°Ņ‚ĐžĐ˛Đ° ĐŧĐžĐģŅ, ĐŋŅŠŅ€Đ˛Đž ĐŋĐžĐ´Đ°Đ˛Đ°ĐšŅ‚Đĩ ĐŋŅ€ĐžĐąĐģĐĩĐŧĐ¸Ņ‚Đĩ ŅĐ¸ ĐēҊĐŧ Ņ‚ŅŅ… ҇ҀĐĩС ĐģиĐŊĐēОвĐĩŅ‚Đĩ ĐŋĐž-Đ´ĐžĐģ҃.", "support_third_party_description": "Đ’Đ°ŅˆĐ°Ņ‚Đ° иĐŊŅŅ‚Đ°ĐģĐ°Ņ†Đ¸Ņ ĐŊа Immich Đĩ ĐŋаĐēĐĩŅ‚Đ¸Ņ€Đ°ĐŊа ĐžŅ‚ ҂ҀĐĩŅ‚Đ° ŅŅ‚Ņ€Đ°ĐŊа. ĐŸŅ€ĐžĐąĐģĐĩĐŧĐ¸Ņ‚Đĩ, ĐēĐžĐ¸Ņ‚Đž иСĐŋĐ¸Ņ‚Đ˛Đ°Ņ‚Đĩ, ĐŧĐžĐļĐĩ да ŅĐ° ĐŋŅ€Đ¸Ņ‡Đ¸ĐŊĐĩĐŊи ĐžŅ‚ Ņ‚ĐžĐˇĐ¸ ĐŋаĐēĐĩŅ‚, ĐˇĐ°Ņ‚ĐžĐ˛Đ° ĐŧĐžĐģŅ, ĐŋŅŠŅ€Đ˛Đž ĐŋĐžĐ´Đ°Đ˛Đ°ĐšŅ‚Đĩ ĐŋŅ€ĐžĐąĐģĐĩĐŧĐ¸Ņ‚Đĩ ŅĐ¸ ĐēҊĐŧ Ņ‚ŅŅ… ҇ҀĐĩС ĐģиĐŊĐēОвĐĩŅ‚Đĩ ĐŋĐž-Đ´ĐžĐģ҃.",
"supporter": "ĐŸĐžĐ´Đ´Ņ€ŅŠĐļĐŊиĐē",
"swap_merge_direction": "РаСĐŧŅĐŊа ĐŋĐžŅĐžĐēĐ°Ņ‚Đ° ĐŊа ҁĐģиваĐŊĐĩ", "swap_merge_direction": "РаСĐŧŅĐŊа ĐŋĐžŅĐžĐēĐ°Ņ‚Đ° ĐŊа ҁĐģиваĐŊĐĩ",
"sync": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ¸Ņ€Đ°ĐŊĐĩ", "sync": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ¸Ņ€Đ°ĐŊĐĩ",
"sync_albums": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа аĐģĐąŅƒĐŧи", "sync_albums": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа аĐģĐąŅƒĐŧи",
@@ -2215,7 +2196,7 @@
"tag_assets": "ĐĸĐ°ĐŗĐŊи ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", "tag_assets": "ĐĸĐ°ĐŗĐŊи ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸",
"tag_created": "ĐĄŅŠĐˇĐ´Đ°Đ´ĐĩĐŊ ĐĩŅ‚Đ¸ĐēĐĩŅ‚: {tag}", "tag_created": "ĐĄŅŠĐˇĐ´Đ°Đ´ĐĩĐŊ ĐĩŅ‚Đ¸ĐēĐĩŅ‚: {tag}",
"tag_feature_description": "Đ Đ°ĐˇĐŗĐģĐĩĐļдаĐŊĐĩ ĐŊа ҁĐŊиĐŧĐēи и видĐĩĐžĐēĐģиĐŋОвĐĩ, ĐŗŅ€ŅƒĐŋĐ¸Ņ€Đ°ĐŊи ĐŋĐž Ņ‚ĐĩĐŧи ҁ ĐģĐžĐŗĐ¸Ņ‡ĐĩҁĐēи Ņ‚Đ°ĐŗĐžĐ˛Đĩ", "tag_feature_description": "Đ Đ°ĐˇĐŗĐģĐĩĐļдаĐŊĐĩ ĐŊа ҁĐŊиĐŧĐēи и видĐĩĐžĐēĐģиĐŋОвĐĩ, ĐŗŅ€ŅƒĐŋĐ¸Ņ€Đ°ĐŊи ĐŋĐž Ņ‚ĐĩĐŧи ҁ ĐģĐžĐŗĐ¸Ņ‡ĐĩҁĐēи Ņ‚Đ°ĐŗĐžĐ˛Đĩ",
"tag_not_found_question": "НĐĩ ĐŧĐžĐļĐĩŅ‚Đĩ да ĐŊаĐŧĐĩŅ€Đ¸Ņ‚Đĩ ĐĩŅ‚Đ¸ĐēĐĩŅ‚? <link>ĐĄŅŠĐˇĐ´Đ°ĐšŅ‚Đĩ ĐŊОв ĐĩŅ‚Đ¸ĐēĐĩŅ‚.</link>", "tag_not_found_question": "НĐĩ ĐŧĐžĐļĐĩŅ‚Đĩ да ĐŊаĐŧĐĩŅ€Đ¸Ņ‚Đĩ ĐĩŅ‚Đ¸ĐēĐĩŅ‚? ĐĄŅŠĐˇĐ´Đ°ĐšŅ‚Đĩ Ņ‚Đ°ĐēŅŠĐ˛ <link>Ņ‚ŅƒĐē</link>",
"tag_people": "ĐžŅ‚ĐąĐĩĐģĐĩĐļи ĐĨĐžŅ€Đ°", "tag_people": "ĐžŅ‚ĐąĐĩĐģĐĩĐļи ĐĨĐžŅ€Đ°",
"tag_updated": "ОбĐŊОвĐĩĐŊ ĐĩŅ‚Đ¸ĐēĐĩŅ‚: {tag}", "tag_updated": "ОбĐŊОвĐĩĐŊ ĐĩŅ‚Đ¸ĐēĐĩŅ‚: {tag}",
"tagged_assets": "ĐĸĐ°ĐŗĐŊĐ°Ņ‚Đ¸ {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸}}", "tagged_assets": "ĐĸĐ°ĐŗĐŊĐ°Ņ‚Đ¸ {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸}}",
@@ -2313,7 +2294,6 @@
"unstack_action_prompt": "{count} ŅĐ° Ņ€Đ°ĐˇĐŗŅ€ŅƒĐŋĐ¸Ņ€Đ°ĐŊи", "unstack_action_prompt": "{count} ŅĐ° Ņ€Đ°ĐˇĐŗŅ€ŅƒĐŋĐ¸Ņ€Đ°ĐŊи",
"unstacked_assets_count": "РаСĐēĐ°Ņ‡ĐĩĐŊи {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸}}", "unstacked_assets_count": "РаСĐēĐ°Ņ‡ĐĩĐŊи {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸}}",
"unsupported_field_type": "ĐĸиĐŋа ĐŊа ĐŋĐžĐģĐĩŅ‚Đž ĐŊĐĩ ҁĐĩ ĐŋĐžĐ´Đ´ŅŠŅ€Đļа", "unsupported_field_type": "ĐĸиĐŋа ĐŊа ĐŋĐžĐģĐĩŅ‚Đž ĐŊĐĩ ҁĐĩ ĐŋĐžĐ´Đ´ŅŠŅ€Đļа",
"unsupported_file_type": "ФаКĐģŅŠŅ‚ {file} ĐŊĐĩ ĐŧĐžĐļĐĩ да ĐąŅŠĐ´Đĩ ĐˇĐ°Ņ€ĐĩĐ´ĐĩĐŊ, ĐˇĐ°Ņ‰ĐžŅ‚Đž ĐŊĐĩĐŗĐžĐ˛Đ¸ŅŅ‚ Ņ‚Đ¸Đŋ {type} ĐŊĐĩ ҁĐĩ ĐŋĐžĐ´Đ´ŅŠŅ€Đļа.",
"untagged": "НĐĩĐŧĐ°Ņ€ĐēĐ¸Ņ€Đ°ĐŊи", "untagged": "НĐĩĐŧĐ°Ņ€ĐēĐ¸Ņ€Đ°ĐŊи",
"untitled_workflow": "Đ Đ°ĐąĐžŅ‚ĐĩĐŊ ĐŋŅ€ĐžŅ†Đĩҁ ĐąĐĩС иĐŧĐĩ", "untitled_workflow": "Đ Đ°ĐąĐžŅ‚ĐĩĐŊ ĐŋŅ€ĐžŅ†Đĩҁ ĐąĐĩС иĐŧĐĩ",
"up_next": "ĐĄĐģĐĩĐ´Đ˛Đ°Ņ‰", "up_next": "ĐĄĐģĐĩĐ´Đ˛Đ°Ņ‰",
@@ -2340,8 +2320,6 @@
"url": "URL", "url": "URL",
"usage": "ĐŸĐžŅ‚Ņ€ĐĩĐąĐģĐĩĐŊиĐĩ", "usage": "ĐŸĐžŅ‚Ņ€ĐĩĐąĐģĐĩĐŊиĐĩ",
"use_biometric": "ИСĐŋĐžĐģСваК йиОĐŧĐĩŅ‚Ņ€Đ¸Ņ", "use_biometric": "ИСĐŋĐžĐģСваК йиОĐŧĐĩŅ‚Ņ€Đ¸Ņ",
"use_browser_locale": "ИСĐŋĐžĐģСваК ĐĩСиĐēĐžĐ˛Đ¸Ņ‚Đĩ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐŊа ĐąŅ€Đ°ŅƒĐˇŅŠŅ€Đ°",
"use_browser_locale_description": "Đ¤ĐžŅ€ĐŧĐ°Ņ‚ ĐŊа Đ´Đ°Ņ‚Đ°, Đ˛Ņ€ĐĩĐŧĐĩ и Ņ‡Đ¸ŅĐģа ҁĐŋĐžŅ€ĐĩĐ´ ĐĩСиĐēĐžĐ˛Đ°Ņ‚Đ° ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēа ĐŊа ĐąŅ€Đ°ŅƒĐˇŅŠŅ€Đ°",
"use_current_connection": "ИСĐŋĐžĐģСваК Ņ‚ĐĩĐēŅƒŅ‰Đ°Ņ‚Đ° Đ˛Ņ€ŅŠĐˇĐēа", "use_current_connection": "ИСĐŋĐžĐģСваК Ņ‚ĐĩĐēŅƒŅ‰Đ°Ņ‚Đ° Đ˛Ņ€ŅŠĐˇĐēа",
"use_custom_date_range": "ИСĐŋĐžĐģĐˇĐ˛Đ°ĐšŅ‚Đĩ ŅĐžĐąŅŅ‚Đ˛ĐĩĐŊ диаĐŋаСОĐŊ ĐžŅ‚ Đ´Đ°Ņ‚Đ¸ вĐŧĐĩŅŅ‚Đž Ņ‚ĐžĐ˛Đ°", "use_custom_date_range": "ИСĐŋĐžĐģĐˇĐ˛Đ°ĐšŅ‚Đĩ ŅĐžĐąŅŅ‚Đ˛ĐĩĐŊ диаĐŋаСОĐŊ ĐžŅ‚ Đ´Đ°Ņ‚Đ¸ вĐŧĐĩŅŅ‚Đž Ņ‚ĐžĐ˛Đ°",
"user": "ĐŸĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ", "user": "ĐŸĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ",
@@ -2395,7 +2373,6 @@
"viewer_remove_from_stack": "ĐŸŅ€ĐĩĐŧĐ°Ņ…Đ˛Đ°ĐŊĐĩ ĐžŅ‚ ĐžĐŋĐ°ŅˆĐēĐ°Ņ‚Đ°", "viewer_remove_from_stack": "ĐŸŅ€ĐĩĐŧĐ°Ņ…Đ˛Đ°ĐŊĐĩ ĐžŅ‚ ĐžĐŋĐ°ŅˆĐēĐ°Ņ‚Đ°",
"viewer_stack_use_as_main_asset": "ИСĐŋĐžĐģСваК ĐēĐ°Ņ‚Đž ĐžŅĐŊОвĐĩĐŊ", "viewer_stack_use_as_main_asset": "ИСĐŋĐžĐģСваК ĐēĐ°Ņ‚Đž ĐžŅĐŊОвĐĩĐŊ",
"viewer_unstack": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи ĐžŅ‚ ĐžĐŋĐ°ŅˆĐēĐ°Ņ‚Đ°", "viewer_unstack": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи ĐžŅ‚ ĐžĐŋĐ°ŅˆĐēĐ°Ņ‚Đ°",
"visibility": "ВидиĐŧĐžŅŅ‚",
"visibility_changed": "ВидиĐŧĐžŅŅ‚Ņ‚Đ° Đĩ ĐŋŅ€ĐžĐŧĐĩĐŊĐĩĐŊа Са {count, plural, one {# Ņ‡ĐžĐ˛ĐĩĐē} other {# Ņ‡ĐžĐ˛ĐĩĐēа}}", "visibility_changed": "ВидиĐŧĐžŅŅ‚Ņ‚Đ° Đĩ ĐŋŅ€ĐžĐŧĐĩĐŊĐĩĐŊа Са {count, plural, one {# Ņ‡ĐžĐ˛ĐĩĐē} other {# Ņ‡ĐžĐ˛ĐĩĐēа}}",
"visual": "Đ’Đ¸ĐˇŅƒĐ°ĐģĐĩĐŊ", "visual": "Đ’Đ¸ĐˇŅƒĐ°ĐģĐĩĐŊ",
"visual_builder": "Đ’Đ¸ĐˇŅƒĐ°ĐģĐĩĐŊ ĐēĐžĐŊŅŅ‚Ņ€ŅƒĐēŅ‚ĐžŅ€", "visual_builder": "Đ’Đ¸ĐˇŅƒĐ°ĐģĐĩĐŊ ĐēĐžĐŊŅŅ‚Ņ€ŅƒĐēŅ‚ĐžŅ€",
+28 -333
View File
@@ -70,23 +70,23 @@
"cleared_jobs": "{job} āĻāϰ āϜāĻ¨ā§āϝ jobs āĻ–āĻžāϞāĻŋ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", "cleared_jobs": "{job} āĻāϰ āϜāĻ¨ā§āϝ jobs āĻ–āĻžāϞāĻŋ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇",
"config_set_by_file": "āĻ•āύāĻĢāĻŋāĻ— āĻŦāĻ°ā§āϤāĻŽāĻžāύ⧇ āĻāĻ•āϟāĻŋ āĻ•āύāĻĢāĻŋāĻ— āĻĢāĻžāχāϞ āĻĻā§āĻŦāĻžāϰāĻž āϏ⧇āϟ āĻ•āϰāĻž āφāϛ⧇", "config_set_by_file": "āĻ•āύāĻĢāĻŋāĻ— āĻŦāĻ°ā§āϤāĻŽāĻžāύ⧇ āĻāĻ•āϟāĻŋ āĻ•āύāĻĢāĻŋāĻ— āĻĢāĻžāχāϞ āĻĻā§āĻŦāĻžāϰāĻž āϏ⧇āϟ āĻ•āϰāĻž āφāϛ⧇",
"confirm_delete_library": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ {library} āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ?", "confirm_delete_library": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ {library} āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ?",
"confirm_delete_library_assets": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤāĻ­āĻžāĻŦ⧇ āĻāχ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋāϟāĻŋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ? āĻāϤ⧇ Immich āĻĨ⧇āϕ⧇ {count, plural, one {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ} other {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ}} āĻŽā§āϛ⧇ āϝāĻžāĻŦ⧇ āĻāĻŦāĻ‚ āĻāχ āĻ•āĻžāϜāϟāĻŋ āĻĒāϰ⧇ āφāϰ āĻĒā§‚āĻ°ā§āĻŦāĻžāĻŦāĻ¸ā§āĻĨāĻžāϝāĻŧ āĻĢ⧇āϰāĻžāύ⧋ āϝāĻžāĻŦ⧇ āύāĻžāĨ¤ āϤāĻŦ⧇ āĻĢāĻžāχāϞāϗ⧁āϞ⧋ āĻĄāĻŋāĻ¸ā§āϕ⧇ āĻĨ⧇āϕ⧇ āϝāĻžāĻŦ⧇āĨ¤", "confirm_delete_library_assets": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āĻāχ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋāϟāĻŋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ? āĻāϟāĻŋ Immich āĻĨ⧇āϕ⧇ {count, plural, one {# contained asset} other {all # contained asset}} āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻŦ⧇ āĻāĻŦāĻ‚ āĻĒā§‚āĻ°ā§āĻŦāĻžāĻŦāĻ¸ā§āĻĨāĻžāϝāĻŧ āĻĢ⧇āϰāĻžāύ⧋ āϝāĻžāĻŦ⧇ āύāĻžāĨ¤ āĻĢāĻžāχāϞāϗ⧁āϞāĻŋ āĻĄāĻŋāĻ¸ā§āϕ⧇ āĻĨāĻžāĻ•āĻŦ⧇āĨ¤",
"confirm_email_below": "āύāĻŋāĻļā§āϚāĻŋāϤ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āύāĻŋāĻšā§‡ \"{email}\" āϟāĻžāχāĻĒ āĻ•āϰ⧁āύ", "confirm_email_below": "āύāĻŋāĻļā§āϚāĻŋāϤ āĻ•āϰāϤ⧇, āύāĻŋāĻšā§‡ \"{email}\" āϟāĻžāχāĻĒ āĻ•āϰ⧁āύ",
"confirm_reprocess_all_faces": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āϏāĻŽāĻ¸ā§āϤ āĻŽā§āĻ– āĻĒ⧁āύāϰāĻžāϝāĻŧ āĻĒāϰāĻŋāĻļā§‹āϧāύ āĻ•āϰāϤ⧇ āϚāĻžāύ? āĻāϤ⧇ āύāĻžāĻŽ āĻĻ⧇āĻ“āϝāĻŧāĻž āĻŦā§āϝāĻ•ā§āϤāĻŋāĻĻ⧇āϰ āϤāĻĨā§āϝāĻ“ āĻŽā§āϛ⧇ āϝāĻžāĻŦ⧇āĨ¤", "confirm_reprocess_all_faces": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āϏāĻŽāĻ¸ā§āϤ āĻŽā§āĻ– āĻĒ⧁āύāϰāĻžāϝāĻŧ āĻĒā§āϰāĻ•ā§āϰāĻŋāϝāĻŧāĻž āĻ•āϰāϤ⧇ āϚāĻžāύ? āĻāϟāĻŋ āύāĻžāĻŽāϝ⧁āĻ•ā§āϤ āĻŦā§āϝāĻ•ā§āϤāĻŋāĻĻ⧇āϰāĻ“ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻŦ⧇āĨ¤",
"confirm_user_password_reset": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ {user}-āĻāϰ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āϰāĻŋāϏ⧇āϟ āĻ•āϰāϤ⧇ āϚāĻžāύ?", "confirm_user_password_reset": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ {user} āĻāϰ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āϰāĻŋāϏ⧇āϟ āĻ•āϰāϤ⧇ āϚāĻžāύ?",
"confirm_user_pin_code_reset": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ {user}-āĻāϰ āĻĒāĻŋāύ āϕ⧋āĻĄ āϰāĻŋāϏ⧇āϟ āĻ•āϰāϤ⧇ āϚāĻžāύ?", "confirm_user_pin_code_reset": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ {user} āĻāϰ āĻĒāĻŋāύ āϕ⧋āĻĄ āϰāĻŋāϏ⧇āϟ āĻ•āϰāϤ⧇ āϚāĻžāύ?",
"copy_config_to_clipboard_description": "āĻŦāĻ°ā§āϤāĻŽāĻžāύ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻ•āύāĻĢāĻŋāĻ—āĻžāϰ⧇āĻļāύāϟāĻŋāϕ⧇ āĻāĻ•āϟāĻŋ JSON āĻ…āĻŦāĻœā§‡āĻ•ā§āϟ āĻšāĻŋāϏ⧇āĻŦ⧇ āĻ•ā§āϞāĻŋāĻĒāĻŦā§‹āĻ°ā§āĻĄā§‡ āĻ•āĻĒāĻŋ āĻ•āϰ⧁āύ", "copy_config_to_clipboard_description": "āĻŦāĻ°ā§āϤāĻŽāĻžāύ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻ•āύāĻĢāĻŋāĻ—āĻžāϰ⧇āĻļāύ āĻāĻ•āϟāĻŋ JSON āĻ…āĻŦāĻœā§‡āĻ•ā§āϟ āĻšāĻŋāϏ⧇āĻŦ⧇ āĻ•ā§āϞāĻŋāĻĒāĻŦā§‹āĻ°ā§āĻĄā§‡ āĻ•āĻĒāĻŋ āĻ•āϰ⧁āύ",
"create_job": "Job āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ", "create_job": "job āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ",
"cron_expression": "Cron āĻāĻ•ā§āϏāĻĒā§āϰ⧇āĻļāύ", "cron_expression": "āĻ•ā§āϰ⧋āύ āĻāĻ•ā§āϏāĻĒā§āϰ⧇āĻļāύ",
"cron_expression_description": "Cron āĻĢāϰāĻŽā§āϝāĻžāϟ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻ¸ā§āĻ•ā§āϝāĻžāύāĻŋāĻ‚ āχāĻ¨ā§āϟāĻžāϰāĻ­ā§āϝāĻžāϞ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧁āύāĨ¤ āφāϰāĻ“ āϤāĻĨā§āϝ⧇āϰ āϜāĻ¨ā§āϝ āĻĻ⧟āĻž āĻ•āϰ⧇ <link>Crontab Guru</link> āĻĻ⧇āϖ⧁āύ", "cron_expression_description": "āĻ•ā§āϰ⧋āύ āĻĢāĻ°ā§āĻŽā§āϝāĻžāϟ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻ¸ā§āĻ•ā§āϝāĻžāύāĻŋāĻ‚ āĻŦā§āϝāĻŦāϧāĻžāύ āϏ⧇āϟ āĻ•āϰ⧁āύāĨ¤ āφāϰāĻ“ āϤāĻĨā§āϝ⧇āϰ āϜāĻ¨ā§āϝ āĻĻāϝāĻŧāĻž āĻ•āϰ⧇ āĻĻ⧇āϖ⧁āύ āϝ⧇āĻŽāύ <link>Crontab Guru</link>",
"cron_expression_presets": "Cron āĻāĻ•ā§āϏāĻĒā§āϰ⧇āĻļāύ āĻĒā§āϰāĻŋāϏ⧇āϟ", "cron_expression_presets": "āĻ•ā§āϰ⧋āύ āĻāĻ•ā§āϏāĻĒā§āϰ⧇āĻļāύ āĻĒā§āϰāĻŋāϏ⧇āϟ",
"disable_login": "āϞāĻ—āχāύ āĻ…āĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ", "disable_login": "āϞāĻ—āχāύ āĻ…āĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ",
"duplicate_detection_job_description": "āϏāĻĻ⧃āĻļ āĻ›āĻŦāĻŋ āĻļāύāĻžāĻ•ā§āϤ āĻ•āϰāϤ⧇ āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋āϰ āωāĻĒāϰ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āϚāĻžāϞāĻžāύāĨ¤ āĻāϟāĻŋ Smart Search-āĻāϰ āωāĻĒāϰ āύāĻŋāĻ°ā§āĻ­āϰ āĻ•āϰ⧇", "duplicate_detection_job_description": "āĻ…āύ⧁āϰ⧂āĻĒ āĻ›āĻŦāĻŋ āϏāύāĻžāĻ•ā§āϤ āĻ•āϰāϤ⧇ āϏāĻŽā§āĻĒāĻĻāϗ⧁āϞāĻŋāϤ⧇ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āϚāĻžāϞāĻžāύāĨ¤ āĻ¸ā§āĻŽāĻžāĻ°ā§āϟ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ⧇āϰ āωāĻĒāϰ āύāĻŋāĻ°ā§āĻ­āϰ āĻ•āϰ⧇",
"exclusion_pattern_description": "āĻāĻ•ā§āϏāĻ•ā§āϞ⧁āĻļāύ āĻĒā§āϝāĻžāϟāĻžāĻ°ā§āύ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻĢāĻžāχāϞ āĻ“ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āωāĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰāĻž āϝāĻžāϝāĻŧāĨ¤ āĻāϟāĻŋ āϤāĻ–āύāχ āωāĻĒāĻ•āĻžāϰ⧀ āϝāĻ–āύ āĻ•āĻŋāϛ⧁ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ⧇ āĻāĻŽāύ āĻĢāĻžāχāϞ āĻĨāĻžāϕ⧇ āϝāĻž āφāĻĒāύāĻŋ āχāĻŽāĻĒā§‹āĻ°ā§āϟ āĻ•āϰāϤ⧇ āϚāĻžāύ āύāĻž, āϝ⧇āĻŽāύ RAW āĻĢāĻžāχāϞāĨ¤", "exclusion_pattern_description": "āĻāĻ•ā§āϏāĻ•ā§āϞ⧁āĻļāύ āĻĒā§āϝāĻžāϟāĻžāĻ°ā§āύ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āφāĻĒāύāĻŋ āφāĻĒāύāĻžāϰ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āĻĢāĻžāχāϞ āĻāĻŦāĻ‚ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰāϗ⧁āϞāĻŋāϕ⧇ āωāĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŦ⧇āύāĨ¤ āϝāĻĻāĻŋ āφāĻĒāύāĻžāϰ āĻāĻŽāύ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻĨāĻžāϕ⧇ āϝ⧇āĻ–āĻžāύ⧇ āĻāĻŽāύ āĻĢāĻžāχāϞ āĻĨāĻžāϕ⧇ āϝāĻž āφāĻĒāύāĻŋ āφāĻŽāĻĻāĻžāύāĻŋ āĻ•āϰāϤ⧇ āϚāĻžāύ āύāĻž, āϝ⧇āĻŽāύ RAW āĻĢāĻžāχāϞāĨ¤",
"export_config_as_json_description": "āĻŦāĻ°ā§āϤāĻŽāĻžāύ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻ•āύāĻĢāĻŋāĻ—āĻžāϰ⧇āĻļāύāϟāĻŋāϕ⧇ āĻāĻ•āϟāĻŋ JSON āĻĢāĻžāχāϞ āĻšāĻŋāϏ⧇āĻŦ⧇ āĻĄāĻžāωāύāϞ⧋āĻĄ āĻ•āϰ⧁āύ", "export_config_as_json_description": "āĻŦāĻ°ā§āϤāĻŽāĻžāύ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻ•āύāĻĢāĻŋāĻ—āĻžāϰ⧇āĻļāύ āĻāĻ•āϟāĻŋ JSON āĻĢāĻžāχāϞ āĻšāĻŋāϏ⧇āĻŦ⧇ āĻĄāĻžāωāύāϞ⧋āĻĄ āĻ•āϰ⧁āύ",
"external_libraries_page_description": "āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ āĻāĻ•ā§āϏāϟāĻžāĻ°ā§āύāĻžāϞ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻĒ⧇āϜ", "external_libraries_page_description": "āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ external āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻĒ⧇āϜ",
"face_detection": "āĻŽā§āĻ– āĻļāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ", "face_detection": "āĻŽā§āĻ– āϏāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ",
"face_detection_description": "āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡ āĻĨāĻžāĻ•āĻž āĻŽā§āĻ–/āĻšā§‡āĻšāĻžāϰāĻž āĻļāύāĻžāĻ•ā§āϤ āĻ•āϰ⧁āύāĨ¤ āĻ­āĻŋāĻĄāĻŋāĻ“āϰ āĻ•ā§āώ⧇āĻ¤ā§āϰ⧇ āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āĻŦāĻŋāĻŦ⧇āϚāύāĻž āĻ•āϰāĻž āĻšāϝāĻŧāĨ¤ \"āϰāĻŋāĻĢā§āϰ⧇āĻļ\" āϏāĻŦ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĒ⧁āύāϰāĻžāϝāĻŧ āĻĒā§āϰāĻ•ā§āϰāĻŋāϝāĻŧāĻž āĻ•āϰ⧇āĨ¤ \"āϰāĻŋāϏ⧇āϟ\" āĻ•āϰāϞ⧇ āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ āϏāĻŦ āĻŽā§āϖ⧇āϰ āĻĄā§‡āϟāĻž āĻŽā§āϛ⧇ āϝāĻžāϝāĻŧāĨ¤ \"āĻŽāĻŋāϏāĻŋāĻ‚\" āĻ“āχ āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋āϕ⧇ āϏāĻžāϰāĻŋāϤ⧇ āϝ⧋āĻ— āĻ•āϰ⧇ āϝāĻžāĻĻ⧇āϰāϕ⧇ āĻāĻ–āύ⧋ āĻĒā§āϰāĻ•ā§āϰāĻŋāϝāĻŧāĻž āĻ•āϰāĻž āĻšāϝāĻŧāύāĻŋāĨ¤ āĻĢ⧇āϏ āĻĄāĻŋāĻŸā§‡āĻ•āĻļāύ āϏāĻŽā§āĻĒāĻ¨ā§āύ āĻšāϞ⧇ āĻļāύāĻžāĻ•ā§āϤ āĻšāĻ“āϝāĻŧāĻž āĻŽā§āĻ–āϗ⧁āϞ⧋ āĻĢ⧇āϏāĻŋāϝāĻŧāĻžāϞ āϰāĻŋāĻ•āĻ—āύāĻŋāĻļāύ⧇āϰ āϜāĻ¨ā§āϝ āϏāĻžāϰāĻŋāϤ⧇ āϝ⧋āĻ— āĻ•āϰāĻž āĻšāĻŦ⧇ āĻāĻŦāĻ‚ āϏ⧇āϗ⧁āϞ⧋āϕ⧇ āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ āĻŦāĻž āύāϤ⧁āύ āĻŦā§āϝāĻ•ā§āϤāĻŋāĻĻ⧇āϰ āϏāĻžāĻĨ⧇ āĻ—ā§āϰ⧁āĻĒ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤", "face_detection_description": "āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡ āĻĨāĻžāĻ•āĻž āĻŽā§āĻ–/āĻšā§‡āĻšāĻžāϰāĻž āϗ⧁āϞāĻŋ āϏāύāĻžāĻ•ā§āϤ āĻ•āϰ⧁āύāĨ¤ āĻ­āĻŋāĻĄāĻŋāĻ“ āϗ⧁āϞāĻŋāϰ āϜāĻ¨ā§āϝ, āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āĻŦāĻŋāĻŦ⧇āϚāύāĻž āĻ•āϰāĻž āĻšāϝāĻŧāĨ¤ \"āϰāĻŋāĻĢā§āϰ⧇āĻļ\" (āĻĒ⧁āύāϰāĻžāϝāĻŧ) āϏāĻŽāĻ¸ā§āϤ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĒā§āϰāĻ•ā§āϰāĻŋāϝāĻŧāĻž āĻ•āϰ⧇āĨ¤ \"āϰāĻŋāϏ⧇āϟ\" āĻ•āϰāĻžāϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āĻ…āϤāĻŋāϰāĻŋāĻ•ā§āϤāĻ­āĻžāĻŦ⧇ āϏāĻŽāĻ¸ā§āϤ āĻŦāĻ°ā§āϤāĻŽāĻžāύ āĻŽā§āϖ⧇āϰ āĻĄā§‡āϟāĻž āϏāĻžāĻĢ āĻ•āϰ⧇āĨ¤ \"āĻ…āύ⧁āĻĒāĻ¸ā§āĻĨāĻŋāϤ\" āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞāĻŋāϕ⧇ āϏāĻžāϰāĻŋāĻŦāĻĻā§āϧ āĻ•āϰ⧇ āϝāĻž āĻāĻ–āύāĻ“ āĻĒā§āϰāĻ•ā§āϰāĻŋāϝāĻŧāĻž āĻ•āϰāĻž āĻšāϝāĻŧāύāĻŋāĨ¤ āϏāύāĻžāĻ•ā§āϤ āĻ•āϰāĻž āĻŽā§āĻ–āϗ⧁āϞāĻŋāϕ⧇ āĻĢ⧇āϏāĻŋāϝāĻŧāĻžāϞ āϰāĻŋāĻ•āĻ—āύāĻŋāĻļāύ⧇āϰ āϜāĻ¨ā§āϝ āϏāĻžāϰāĻŋāĻŦāĻĻā§āϧ āĻ•āϰāĻž āĻšāĻŦ⧇, āĻĢ⧇āϏāĻŋāϝāĻŧāĻžāϞ āĻĄāĻŋāĻŸā§‡āĻ•āĻļāύ āϏāĻŽā§āĻĒā§‚āĻ°ā§āĻŖ āĻšāĻ“āϝāĻŧāĻžāϰ āĻĒāϰ⧇, āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ āĻŦāĻž āύāϤ⧁āύ āĻŦā§āϝāĻ•ā§āϤāĻŋāĻĻ⧇āϰ āĻŽāĻ§ā§āϝ⧇ āĻ—ā§‹āĻˇā§āĻ ā§€āĻŦāĻĻā§āϧ āĻ•āϰ⧇āĨ¤",
"facial_recognition_job_description": "āĻļāύāĻžāĻ•ā§āϤ āĻ•āϰāĻž āĻŽā§āĻ–āϗ⧁āϞāĻŋāϕ⧇ āĻŽāĻžāύ⧁āώ⧇āϰ āĻŽāĻ§ā§āϝ⧇ āĻ—ā§‹āĻˇā§āĻ ā§€āϭ⧁āĻ•ā§āϤ/āĻ—ā§āϰ⧁āĻĒ āĻ•āϰ⧁āύāĨ¤ āĻŽā§āĻ– āϏāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ āϏāĻŽā§āĻĒā§‚āĻ°ā§āĻŖ āĻšāĻ“āϝāĻŧāĻžāϰ āĻĒāϰ⧇ āĻāχ āϧāĻžāĻĒāϟāĻŋ āϚāϞ⧇āĨ¤ \"āϰāĻŋāϏ⧇āϟ\" (āĻĒ⧁āύāϰāĻžāϝāĻŧ) āϏāĻŽāĻ¸ā§āϤ āĻŽā§āĻ–āϕ⧇ āĻ•ā§āϞāĻžāĻ¸ā§āϟāĻžāϰ āĻ•āϰ⧇āĨ¤ \"āĻ…āύ⧁āĻĒāĻ¸ā§āĻĨāĻŋāϤ/āĻŽāĻŋāϏāĻŋāĻ‚\" āĻŽā§āĻ–āϗ⧁āϞāĻŋāϕ⧇ āϏāĻžāϰāĻŋāϤ⧇ āϰāĻžāϖ⧇ āϝ⧇āϗ⧁āϞ⧋ āϕ⧋āύāĻ“ āĻŦā§āϝāĻ•ā§āϤāĻŋāϕ⧇ āĻāϏāĻžāχāύ/āĻŦāϰāĻžāĻĻā§āĻĻ āĻ•āϰāĻž āĻšāϝāĻŧāύāĻŋāĨ¤", "facial_recognition_job_description": "āĻļāύāĻžāĻ•ā§āϤ āĻ•āϰāĻž āĻŽā§āĻ–āϗ⧁āϞāĻŋāϕ⧇ āĻŽāĻžāύ⧁āώ⧇āϰ āĻŽāĻ§ā§āϝ⧇ āĻ—ā§‹āĻˇā§āĻ ā§€āϭ⧁āĻ•ā§āϤ/āĻ—ā§āϰ⧁āĻĒ āĻ•āϰ⧁āύāĨ¤ āĻŽā§āĻ– āϏāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ āϏāĻŽā§āĻĒā§‚āĻ°ā§āĻŖ āĻšāĻ“āϝāĻŧāĻžāϰ āĻĒāϰ⧇ āĻāχ āϧāĻžāĻĒāϟāĻŋ āϚāϞ⧇āĨ¤ \"āϰāĻŋāϏ⧇āϟ\" (āĻĒ⧁āύāϰāĻžāϝāĻŧ) āϏāĻŽāĻ¸ā§āϤ āĻŽā§āĻ–āϕ⧇ āĻ•ā§āϞāĻžāĻ¸ā§āϟāĻžāϰ āĻ•āϰ⧇āĨ¤ \"āĻ…āύ⧁āĻĒāĻ¸ā§āĻĨāĻŋāϤ/āĻŽāĻŋāϏāĻŋāĻ‚\" āĻŽā§āĻ–āϗ⧁āϞāĻŋāϕ⧇ āϏāĻžāϰāĻŋāϤ⧇ āϰāĻžāϖ⧇ āϝ⧇āϗ⧁āϞ⧋ āϕ⧋āύāĻ“ āĻŦā§āϝāĻ•ā§āϤāĻŋāϕ⧇ āĻāϏāĻžāχāύ/āĻŦāϰāĻžāĻĻā§āĻĻ āĻ•āϰāĻž āĻšāϝāĻŧāύāĻŋāĨ¤",
"failed_job_command": "āĻ•āĻŽāĻžāĻ¨ā§āĻĄ {command} āĻ•āĻžāĻœā§‡āϰ āϜāĻ¨ā§āϝ āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇: {job}", "failed_job_command": "āĻ•āĻŽāĻžāĻ¨ā§āĻĄ {command} āĻ•āĻžāĻœā§‡āϰ āϜāĻ¨ā§āϝ āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇: {job}",
"force_delete_user_warning": "āϏāϤāĻ°ā§āĻ•āϤāĻž: āĻāϟāĻŋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āĻāĻŦāĻ‚ āϏāĻŽāĻ¸ā§āϤ āϏāĻŽā§āĻĒāĻĻ āĻ…āĻŦāĻŋāϞāĻŽā§āĻŦ⧇ āϏāϰāĻŋāϝāĻŧ⧇ āĻĢ⧇āϞāĻŦ⧇āĨ¤ āĻāϟāĻŋ āĻĒā§‚āĻ°ā§āĻŦāĻžāĻŦāĻ¸ā§āĻĨāĻžāϝāĻŧ āĻĢ⧇āϰāĻžāύ⧋ āϝāĻžāĻŦ⧇ āύāĻž āĻāĻŦāĻ‚ āĻĢāĻžāχāϞāϗ⧁āϞāĻŋ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāĻž āϝāĻžāĻŦ⧇ āύāĻžāĨ¤", "force_delete_user_warning": "āϏāϤāĻ°ā§āĻ•āϤāĻž: āĻāϟāĻŋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āĻāĻŦāĻ‚ āϏāĻŽāĻ¸ā§āϤ āϏāĻŽā§āĻĒāĻĻ āĻ…āĻŦāĻŋāϞāĻŽā§āĻŦ⧇ āϏāϰāĻŋāϝāĻŧ⧇ āĻĢ⧇āϞāĻŦ⧇āĨ¤ āĻāϟāĻŋ āĻĒā§‚āĻ°ā§āĻŦāĻžāĻŦāĻ¸ā§āĻĨāĻžāϝāĻŧ āĻĢ⧇āϰāĻžāύ⧋ āϝāĻžāĻŦ⧇ āύāĻž āĻāĻŦāĻ‚ āĻĢāĻžāχāϞāϗ⧁āϞāĻŋ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāĻž āϝāĻžāĻŦ⧇ āύāĻžāĨ¤",
@@ -98,9 +98,9 @@
"image_fullsize_quality_description": "āĻĒā§‚āĻ°ā§āĻŖ-āφāĻ•āĻžāϰ⧇āϰ āĻ›āĻŦāĻŋāϰ āĻŽāĻžāύ ā§§-ā§§ā§Ļā§ĻāĨ¤ āωāĻšā§āϚāϤāϰ āĻšāϞ⧇ āĻ­āĻžāϞ⧋, āĻ•āĻŋāĻ¨ā§āϤ⧁ āφāϰāĻ“ āĻŦāĻĄāĻŧ āĻĢāĻžāχāϞ āϤ⧈āϰāĻŋ āĻšāϝāĻŧāĨ¤", "image_fullsize_quality_description": "āĻĒā§‚āĻ°ā§āĻŖ-āφāĻ•āĻžāϰ⧇āϰ āĻ›āĻŦāĻŋāϰ āĻŽāĻžāύ ā§§-ā§§ā§Ļā§ĻāĨ¤ āωāĻšā§āϚāϤāϰ āĻšāϞ⧇ āĻ­āĻžāϞ⧋, āĻ•āĻŋāĻ¨ā§āϤ⧁ āφāϰāĻ“ āĻŦāĻĄāĻŧ āĻĢāĻžāχāϞ āϤ⧈āϰāĻŋ āĻšāϝāĻŧāĨ¤",
"image_fullsize_title": "āĻĒā§‚āĻ°ā§āĻŖ-āφāĻ•āĻžāϰ⧇āϰ āϚāĻŋāĻ¤ā§āϰ āϏ⧇āϟāĻŋāĻ‚āϏ", "image_fullsize_title": "āĻĒā§‚āĻ°ā§āĻŖ-āφāĻ•āĻžāϰ⧇āϰ āϚāĻŋāĻ¤ā§āϰ āϏ⧇āϟāĻŋāĻ‚āϏ",
"image_prefer_embedded_preview": "āĻāĻŽā§āĻŦ⧇āĻĄ āĻ•āϰāĻž āĻĒā§āϰāĻŋāĻ­āĻŋāω āĻĒāĻ›āĻ¨ā§āĻĻ āĻ•āϰ⧁āύ", "image_prefer_embedded_preview": "āĻāĻŽā§āĻŦ⧇āĻĄ āĻ•āϰāĻž āĻĒā§āϰāĻŋāĻ­āĻŋāω āĻĒāĻ›āĻ¨ā§āĻĻ āĻ•āϰ⧁āύ",
"image_prefer_embedded_preview_setting_description": "RAW āĻ›āĻŦāĻŋāϤ⧇ āĻĨāĻžāĻ•āĻž āĻāĻŽāĻŦ⧇āĻĄā§‡āĻĄ āĻĒā§āϰāĻŋāĻ­āĻŋāωāϗ⧁āϞ⧋āϕ⧇ āχāĻŽā§‡āϜ āĻĒā§āϰāϏ⧇āϏāĻŋāĻ‚āϝāĻŧ⧇āϰ āχāύāĻĒ⧁āϟ āĻšāĻŋāϏ⧇āĻŦ⧇ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ āϝāĻĻāĻŋ āϤāĻž āωāĻĒāϞāĻ­ā§āϝ āĻĨāĻžāϕ⧇āĨ¤ āĻāϤ⧇ āĻ•āĻŋāϛ⧁ āĻ›āĻŦāĻŋāϰ āϰāĻ™ āφāϰāĻ“ āϏāĻ āĻŋāĻ•āĻ­āĻžāĻŦ⧇ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝ⧇āϤ⧇ āĻĒāĻžāϰ⧇, āϤāĻŦ⧇ āĻĒā§āϰāĻŋāĻ­āĻŋāωāϝāĻŧ⧇āϰ āĻŽāĻžāύ āĻ•ā§āϝāĻžāĻŽā§‡āϰāĻžāϰ āωāĻĒāϰ āύāĻŋāĻ°ā§āĻ­āϰ āĻ•āϰ⧇ āĻāĻŦāĻ‚ āĻ›āĻŦāĻŋāϤ⧇ āĻŦ⧇āĻļāĻŋ āĻ•āĻŽāĻĒā§āϰ⧇āĻļāύ āφāĻ°ā§āϟāĻŋāĻĢā§āϝāĻžāĻ•ā§āϟ āĻĨāĻžāĻ•āϤ⧇ āĻĒāĻžāϰ⧇āĨ¤", "image_prefer_embedded_preview_setting_description": "āϝāĻĻāĻŋ āĻĒāĻžāĻ“ā§ŸāĻž āϝāĻžā§Ÿ, RAW āĻ›āĻŦāĻŋāϰ āϭ⧇āϤāϰ⧇ āĻĨāĻžāĻ•āĻž āĻĒā§āϰāĻŋāĻ­āĻŋāω āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύāĨ¤ āĻāϤ⧇ āĻ•āĻŋāϛ⧁ āĻ›āĻŦāĻŋāϰ āϰāĻ™ āφāϰāĻ“ āϏāĻ āĻŋāĻ• āĻĻ⧇āĻ–āĻž āϝ⧇āϤ⧇ āĻĒāĻžāϰ⧇, āϤāĻŦ⧇ āĻŽāĻžāύ āĻ•ā§āϝāĻžāĻŽā§‡āϰāĻžāϰ āĻ“āĻĒāϰ āύāĻŋāĻ°ā§āĻ­āϰ āĻ•āϰ⧇ āĻāĻŦāĻ‚ āĻ›āĻŦāĻŋāϤ⧇ āĻŦāĻžā§œāϤāĻŋ āĻ•āĻŽāĻĒā§āϰ⧇āĻļāύ āφāĻ°ā§āϟāĻŋāĻĢā§āϝāĻžāĻ•ā§āϟ āĻĻ⧇āĻ–āĻž āϝ⧇āϤ⧇ āĻĒāĻžāϰ⧇āĨ¤",
"image_prefer_wide_gamut": "āĻĒā§āϰāĻļāĻ¸ā§āϤ āĻĒāϰāĻŋāϏāϰ āĻĒāĻ›āĻ¨ā§āĻĻ āĻ•āϰ⧁āύ", "image_prefer_wide_gamut": "āĻĒā§āϰāĻļāĻ¸ā§āϤ āĻĒāϰāĻŋāϏāϰ āĻĒāĻ›āĻ¨ā§āĻĻ āĻ•āϰ⧁āύ",
"image_prefer_wide_gamut_setting_description": "āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ⧇āϰ āϜāĻ¨ā§āϝ Display P3 āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύāĨ¤ āĻāϤ⧇ āĻŦāĻŋāĻ¸ā§āϤ⧀āĻ°ā§āĻŖ āĻ•āĻžāϞāĻžāϰāĻ¸ā§āĻĒ⧇āϏ⧇āϰ āĻ›āĻŦāĻŋāϤ⧇ āĻ›āĻŦāĻŋāϰ āωāĻœā§āĻœā§āĻŦāϞāϤāĻž āĻ“ āĻĒā§āϰāĻžāĻŖāĻŦāĻ¨ā§āϤāϤāĻž āφāϰāĻ“ āĻ­āĻžāϞ⧋āĻ­āĻžāĻŦ⧇ āĻŦāϜāĻžāϝāĻŧ āĻĨāĻžāϕ⧇āĨ¤ āϤāĻŦ⧇ āĻĒ⧁āϰ⧋āύ⧋ āĻĄāĻŋāĻ­āĻžāχāϏ āĻŦāĻž āĻĒ⧁āϰ⧋āύ⧋ āĻŦā§āϰāĻžāωāϜāĻžāϰ⧇āϰ āĻĻā§‹āώ⧇ āĻ›āĻŦāĻŋāϗ⧁āϞ⧋ āĻ•āĻŋāϛ⧁āϟāĻž āĻ­āĻŋāĻ¨ā§āύāĻ­āĻžāĻŦ⧇ āĻĻ⧇āĻ–āĻž āĻĻāĻŋāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤ sRGB āĻ›āĻŦāĻŋāϗ⧁āϞ⧋āϤ⧇ āϰāϙ⧇āϰ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻāĻĄāĻŧāĻžāϤ⧇ sRGB āĻšāĻŋāϏ⧇āĻŦ⧇āχ āϰāĻžāĻ–āĻž āĻšāϝāĻŧāĨ¤", "image_prefer_wide_gamut_setting_description": "āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ⧇āϰ āϜāĻ¨ā§āϝ Display P3 āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύāĨ¤ āĻāϟāĻŋ āĻ“ā§ŸāĻžāχāĻĄ āĻ•āĻžāϞāĻžāϰāĻ¸ā§āĻĒ⧇āϏ āĻ›āĻŦāĻŋāϰ āωāĻœā§āĻœā§āĻŦāϞāϤāĻž āĻ“ āĻĒā§āϰāĻžāĻŖāĻŦāĻ¨ā§āϤ āϰāĻ™ āĻ­āĻžāϞ⧋āĻ­āĻžāĻŦ⧇ āϧāϰ⧇ āϰāĻžāϖ⧇, āϤāĻŦ⧇ āĻĒ⧁āϰāύ⧋ āĻĄāĻŋāĻ­āĻžāχāϏ āĻŦāĻž āĻŦā§āϰāĻžāωāϜāĻžāϰ⧇ āĻ›āĻŦāĻŋāϗ⧁āϞ⧋ āĻ­āĻŋāĻ¨ā§āύāĻ­āĻžāĻŦ⧇ āĻĻ⧇āĻ–āĻž āϝ⧇āϤ⧇ āĻĒāĻžāϰ⧇āĨ¤ sRGB āĻ›āĻŦāĻŋāϗ⧁āϞ⧋ āϰāϙ⧇āϰ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻā§œāĻžāϤ⧇ sRGB āĻšāĻŋāϏ⧇āĻŦ⧇āχ āϰāĻžāĻ–āĻž āĻšāĻŦ⧇āĨ¤",
"image_preview_description": "āĻ¸ā§āĻŸā§āϰāĻŋāĻĒāĻĄ āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āϏāĻš āĻŽāĻžāĻāĻžāϰāĻŋ āφāĻ•āĻžāϰ⧇āϰ āĻ›āĻŦāĻŋ, āĻāĻ•āϟāĻŋ āĻāĻ•āĻ• āϏāĻŽā§āĻĒāĻĻ āĻĻ⧇āĻ–āĻžāϰ āϏāĻŽāϝāĻŧ āĻāĻŦāĻ‚ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚āϝāĻŧ⧇āϰ āϜāĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšā§ƒāϤ āĻšāϝāĻŧ", "image_preview_description": "āĻ¸ā§āĻŸā§āϰāĻŋāĻĒāĻĄ āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āϏāĻš āĻŽāĻžāĻāĻžāϰāĻŋ āφāĻ•āĻžāϰ⧇āϰ āĻ›āĻŦāĻŋ, āĻāĻ•āϟāĻŋ āĻāĻ•āĻ• āϏāĻŽā§āĻĒāĻĻ āĻĻ⧇āĻ–āĻžāϰ āϏāĻŽāϝāĻŧ āĻāĻŦāĻ‚ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚āϝāĻŧ⧇āϰ āϜāĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšā§ƒāϤ āĻšāϝāĻŧ",
"image_preview_quality_description": "ā§§-ā§§ā§Ļā§Ļ āĻāϰ āĻŽāĻ§ā§āϝ⧇ āĻĒā§āϰāĻŋāĻ­āĻŋāω āϕ⧋āϝāĻŧāĻžāϞāĻŋāϟāĻŋāĨ¤ āĻŦ⧇āĻļāĻŋ āĻšāϞ⧇ āĻ­āĻžāϞ⧋, āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻŦāĻĄāĻŧ āĻĢāĻžāχāϞ āϤ⧈āϰāĻŋ āĻšāϝāĻŧ āĻāĻŦāĻ‚ āĻ…ā§āϝāĻžāĻĒ⧇āϰ āĻĒā§āϰāϤāĻŋāĻ•ā§āϰāĻŋāϝāĻŧāĻžāĻļā§€āϞāϤāĻž āĻ•āĻŽāĻžāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤ āĻ•āĻŽ āĻŽāĻžāύ āϏ⧇āϟ āĻ•āϰāϞ⧇ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āϕ⧋āϝāĻŧāĻžāϞāĻŋāϟāĻŋāϰ āωāĻĒāϰ āĻĒā§āϰāĻ­āĻžāĻŦ āĻĒāĻĄāĻŧāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤", "image_preview_quality_description": "ā§§-ā§§ā§Ļā§Ļ āĻāϰ āĻŽāĻ§ā§āϝ⧇ āĻĒā§āϰāĻŋāĻ­āĻŋāω āϕ⧋āϝāĻŧāĻžāϞāĻŋāϟāĻŋāĨ¤ āĻŦ⧇āĻļāĻŋ āĻšāϞ⧇ āĻ­āĻžāϞ⧋, āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻŦāĻĄāĻŧ āĻĢāĻžāχāϞ āϤ⧈āϰāĻŋ āĻšāϝāĻŧ āĻāĻŦāĻ‚ āĻ…ā§āϝāĻžāĻĒ⧇āϰ āĻĒā§āϰāϤāĻŋāĻ•ā§āϰāĻŋāϝāĻŧāĻžāĻļā§€āϞāϤāĻž āĻ•āĻŽāĻžāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤ āĻ•āĻŽ āĻŽāĻžāύ āϏ⧇āϟ āĻ•āϰāϞ⧇ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āϕ⧋āϝāĻŧāĻžāϞāĻŋāϟāĻŋāϰ āωāĻĒāϰ āĻĒā§āϰāĻ­āĻžāĻŦ āĻĒāĻĄāĻŧāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤",
"image_preview_title": "āĻĒā§āϰāĻŋāĻ­āĻŋāω āϏ⧇āϟāĻŋāĻ‚āϏ", "image_preview_title": "āĻĒā§āϰāĻŋāĻ­āĻŋāω āϏ⧇āϟāĻŋāĻ‚āϏ",
@@ -117,7 +117,7 @@
"import_config_from_json_description": "āĻāĻ•āϟāĻŋ JSON āĻ•āύāĻĢāĻŋāĻ— āĻĢāĻžāχāϞ āφāĻĒāϞ⧋āĻĄ āĻ•āϰ⧇ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻ•āύāĻĢāĻŋāĻ—āĻžāϰ⧇āĻļāύ āχāĻŽāĻĒā§‹āĻ°ā§āϟ āĻ•āϰ⧁āύāĨ¤", "import_config_from_json_description": "āĻāĻ•āϟāĻŋ JSON āĻ•āύāĻĢāĻŋāĻ— āĻĢāĻžāχāϞ āφāĻĒāϞ⧋āĻĄ āĻ•āϰ⧇ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻ•āύāĻĢāĻŋāĻ—āĻžāϰ⧇āĻļāύ āχāĻŽāĻĒā§‹āĻ°ā§āϟ āĻ•āϰ⧁āύāĨ¤",
"job_concurrency": "{job} āĻ•āύāĻ•āĻžāϰ⧇āĻ¨ā§āϏāĻŋ", "job_concurrency": "{job} āĻ•āύāĻ•āĻžāϰ⧇āĻ¨ā§āϏāĻŋ",
"job_created": "Job āϤ⧈āϰāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", "job_created": "Job āϤ⧈āϰāĻŋ āĻšāϝāĻŧ⧇āϛ⧇",
"job_not_concurrency_safe": "āĻāχ āĻ•āĻžāϜāϟāĻŋ āϏāĻŽāĻžāĻ¨ā§āϤāϰāĻžāϞāĻ­āĻžāĻŦ⧇ āϚāĻžāϞāĻžāύ⧋ āύāĻŋāϰāĻžāĻĒāĻĻ āύ⧟āĨ¤", "job_not_concurrency_safe": "āĻāχ āĻ•āĻžāϜāϟāĻŋ āϏāĻŽāĻžāĻ¨ā§āϤāϰāĻžāϞāĻ­āĻžāĻŦ⧇ āϚāĻžāϞāĻžāύ⧋ āύāĻŋāϰāĻžāĻĒāĻĻ āύ⧟",
"job_settings": "āĻ•āĻžāĻœā§‡āϰ āϏ⧇āϟāĻŋāĻ‚āϏ", "job_settings": "āĻ•āĻžāĻœā§‡āϰ āϏ⧇āϟāĻŋāĻ‚āϏ",
"job_settings_description": "āĻ•āĻžāĻœā§‡āϰ āϏāĻŽāĻžāĻ¨ā§āϤāϰāĻžāϞāϤāĻž āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", "job_settings_description": "āĻ•āĻžāĻœā§‡āϰ āϏāĻŽāĻžāĻ¨ā§āϤāϰāĻžāϞāϤāĻž āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ",
"jobs_delayed": "{jobCount, plural, other {# āĻŦāĻŋāϞāĻŽā§āĻŦāĻŋāϤ}}", "jobs_delayed": "{jobCount, plural, other {# āĻŦāĻŋāϞāĻŽā§āĻŦāĻŋāϤ}}",
@@ -137,20 +137,20 @@
"library_tasks_description": "āύāϤ⧁āύ āĻāĻŦāĻ‚/āĻ…āĻĨāĻŦāĻž āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāĻŋāϤ āϏāĻŽā§āĻĒāĻĻ⧇āϰ āϜāĻ¨ā§āϝ āĻŦāĻšāĻŋāϰāĻžāĻ—āϤ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰ⧁āύ", "library_tasks_description": "āύāϤ⧁āύ āĻāĻŦāĻ‚/āĻ…āĻĨāĻŦāĻž āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāĻŋāϤ āϏāĻŽā§āĻĒāĻĻ⧇āϰ āϜāĻ¨ā§āϝ āĻŦāĻšāĻŋāϰāĻžāĻ—āϤ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰ⧁āύ",
"library_updated": "āφāĻĒāĻĄā§‡āϟāĻ•ā§ƒāϤ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋāĨ¤", "library_updated": "āφāĻĒāĻĄā§‡āϟāĻ•ā§ƒāϤ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋāĨ¤",
"library_watching_enable_description": "āĻĢāĻžāχāϞ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ⧇āϰ āϜāĻ¨ā§āϝ āĻŦāĻšāĻŋāϰāĻžāĻ—āϤ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋāϗ⧁āϞāĻŋ āĻĻ⧇āϖ⧁āύ", "library_watching_enable_description": "āĻĢāĻžāχāϞ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ⧇āϰ āϜāĻ¨ā§āϝ āĻŦāĻšāĻŋāϰāĻžāĻ—āϤ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋāϗ⧁āϞāĻŋ āĻĻ⧇āϖ⧁āύ",
"library_watching_settings": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻĒāĻ°ā§āϝāĻŦ⧇āĻ•ā§āώāĻŖ [āĻĒāϰ⧀āĻ•ā§āώāĻžāĻŽā§‚āϞāĻ•]", "library_watching_settings": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻĻ⧇āĻ–āĻž (āĻĒāϰ⧀āĻ•ā§āώāĻžāĻŽā§‚āϞāĻ•)",
"library_watching_settings_description": "āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāĻŋāϤ āĻĢāĻžāχāϞāϗ⧁āϞāĻŋāϰ āϜāĻ¨ā§āϝ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āύāϜāϰ āϰāĻžāϖ⧁āύ", "library_watching_settings_description": "āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāĻŋāϤ āĻĢāĻžāχāϞāϗ⧁āϞāĻŋāϰ āϜāĻ¨ā§āϝ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āύāϜāϰ āϰāĻžāϖ⧁āύ",
"logging_enable_description": "āϞāĻ—āĻŋāĻ‚ āĻāύāĻžāĻŦāϞ/āϏāĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ", "logging_enable_description": "āϞāĻ—āĻŋāĻ‚ āĻāύāĻžāĻŦāϞ/āϏāĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ",
"logging_level_description": "āϏāĻ•ā§āϰāĻŋāϝāĻŧ āĻĨāĻžāĻ•āĻžāĻ•āĻžāϞ⧀āύ, āϕ⧋āύ āϞāĻ— āĻ¸ā§āϤāϰ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāϤ⧇ āĻšāĻŦ⧇āĨ¤", "logging_level_description": "āϏāĻ•ā§āϰāĻŋāϝāĻŧ āĻĨāĻžāĻ•āĻžāĻ•āĻžāϞ⧀āύ, āϕ⧋āύ āϞāĻ— āĻ¸ā§āϤāϰ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāϤ⧇ āĻšāĻŦ⧇āĨ¤",
"logging_settings": "āϞāĻ—āĻŋāĻ‚", "logging_settings": "āϞāĻ—āĻŋāĻ‚",
"machine_learning_availability_checks": "āĻĒā§āϰāĻžāĻĒā§āϝāϤāĻž āĻĒāϰ⧀āĻ•ā§āώāĻž", "machine_learning_availability_checks": "āĻĒā§āϰāĻžāĻĒā§āϝāϤāĻž āĻĒāϰ⧀āĻ•ā§āώāĻž",
"machine_learning_availability_checks_description": "āωāĻĒāϞāĻ­ā§āϝ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āϏāĻžāĻ°ā§āĻ­āĻžāϰāϗ⧁āϞ⧋ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āĻļāύāĻžāĻ•ā§āϤ āĻ•āϰ⧇ āϏ⧇āϗ⧁āϞ⧋āϕ⧇ āĻ…āĻ—ā§āϰāĻžāϧāĻŋāĻ•āĻžāϰ āĻĻāĻŋāύ", "machine_learning_availability_checks_description": "āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āωāĻĒāϞāĻŦā§āϧ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āϏāĻžāĻ°ā§āĻ­āĻžāϰāϗ⧁āϞāĻŋ āϏāύāĻžāĻ•ā§āϤ āĻ•āϰ⧁āύ āĻāĻŦāĻ‚ āĻĒāĻ›āĻ¨ā§āĻĻ āĻ•āϰ⧁āύ",
"machine_learning_availability_checks_enabled": "āĻĒā§āϰāĻžāĻĒā§āϝāϤāĻž āĻĒāϰ⧀āĻ•ā§āώāĻž āϏāĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ", "machine_learning_availability_checks_enabled": "āĻĒā§āϰāĻžāĻĒā§āϝāϤāĻž āĻĒāϰ⧀āĻ•ā§āώāĻž āϏāĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ",
"machine_learning_availability_checks_interval": "āĻšā§‡āĻ• āĻŦā§āϝāĻŦāϧāĻžāύ", "machine_learning_availability_checks_interval": "āĻšā§‡āĻ• āĻŦā§āϝāĻŦāϧāĻžāύ",
"machine_learning_availability_checks_interval_description": "āĻĒā§āϰāĻžāĻĒā§āϝāϤāĻž āĻĒāϰ⧀āĻ•ā§āώāĻžāϗ⧁āϞāĻŋāϰ āĻŽāĻ§ā§āϝ⧇ āĻŦā§āϝāĻŦāϧāĻžāύ āĻŽāĻŋāϞāĻŋāϏ⧇āϕ⧇āĻ¨ā§āĻĄā§‡", "machine_learning_availability_checks_interval_description": "āĻĒā§āϰāĻžāĻĒā§āϝāϤāĻž āĻĒāϰ⧀āĻ•ā§āώāĻžāϗ⧁āϞāĻŋāϰ āĻŽāĻ§ā§āϝ⧇ āĻŦā§āϝāĻŦāϧāĻžāύ āĻŽāĻŋāϞāĻŋāϏ⧇āϕ⧇āĻ¨ā§āĻĄā§‡",
"machine_learning_availability_checks_timeout": "āĻ…āύ⧁āϰ⧋āϧ⧇āϰ āϏāĻŽā§ŸāϏ⧀āĻŽāĻž āĻļ⧇āώ", "machine_learning_availability_checks_timeout": "āĻ…āύ⧁āϰ⧋āϧ⧇āϰ āϏāĻŽā§ŸāϏ⧀āĻŽāĻž āĻļ⧇āώ",
"machine_learning_availability_checks_timeout_description": "āĻĒā§āϰāĻžāĻĒā§āϝāϤāĻžāϰ āĻĒāϰ⧀āĻ•ā§āώāĻžāϰ āϜāĻ¨ā§āϝ āĻŽāĻŋāϞāĻŋāϏ⧇āϕ⧇āĻ¨ā§āĻĄā§‡ āϏāĻŽā§ŸāϏ⧀āĻŽāĻžāĨ¤", "machine_learning_availability_checks_timeout_description": "āĻĒā§āϰāĻžāĻĒā§āϝāϤāĻžāϰ āĻĒāϰ⧀āĻ•ā§āώāĻžāϰ āϜāĻ¨ā§āϝ āĻŽāĻŋāϞāĻŋāϏ⧇āϕ⧇āĻ¨ā§āĻĄā§‡ āϏāĻŽā§ŸāϏ⧀āĻŽāĻžāĨ¤",
"machine_learning_clip_model": "CLIP āĻŽāĻĄā§‡āϞ", "machine_learning_clip_model": "CLIP āĻŽāĻĄā§‡āϞ",
"machine_learning_clip_model_description": "<link>āĻāĻ–āĻžāύ⧇</link> āϤāĻžāϞāĻŋāĻ•āĻžāϭ⧁āĻ•ā§āϤ āĻāĻ•āϟāĻŋ CLIP āĻŽāĻĄā§‡āϞ⧇āϰ āύāĻžāĻŽāĨ¤ āĻŽāύ⧇ āϰāĻžāĻ–āĻŦ⧇āύ, āĻŽāĻĄā§‡āϞ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāϞ⧇ āϏāĻŦ āĻ›āĻŦāĻŋāϰ āϜāĻ¨ā§āϝ 'Smart Search' āϜāĻŦāϟāĻŋ āĻĒ⧁āύāϰāĻžāϝāĻŧ āϚāĻžāϞāĻžāϤ⧇ āĻšāĻŦ⧇āĨ¤", "machine_learning_clip_model_description": "<link>āĻāĻ–āĻžāύ⧇</link> āϤāĻžāϞāĻŋāĻ•āĻžāϭ⧁āĻ•ā§āϤ āĻāĻ•āϟāĻŋ CLIP āĻŽāĻĄā§‡āϞ⧇āϰ āύāĻžāĻŽāĨ¤ āĻŽāύ⧇ āϰāĻžāĻ–āĻŦ⧇āύ, āĻŽāĻĄā§‡āϞ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ⧇āϰ āĻĒāϰ āϏāĻŦ āĻ›āĻŦāĻŋāϰ āϜāĻ¨ā§āϝ āĻ…āĻŦāĻļā§āϝāχ ‘Smart Search’ āĻ•āĻžāϜāϟāĻŋ āφāĻŦāĻžāϰ āϚāĻžāϞāĻžāϤ⧇ āĻšāĻŦ⧇āĨ¤",
"machine_learning_duplicate_detection": "āĻĒ⧁āύāϰāĻžāĻŦ⧃āĻ¤ā§āϤāĻŋ āϏāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ", "machine_learning_duplicate_detection": "āĻĒ⧁āύāϰāĻžāĻŦ⧃āĻ¤ā§āϤāĻŋ āϏāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ",
"machine_learning_duplicate_detection_enabled": "āĻĒ⧁āύāϰāĻžāĻŦ⧃āĻ¤ā§āϤāĻŋ āĻļāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ", "machine_learning_duplicate_detection_enabled": "āĻĒ⧁āύāϰāĻžāĻŦ⧃āĻ¤ā§āϤāĻŋ āĻļāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ",
"machine_learning_duplicate_detection_enabled_description": "āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋ⧟ āĻĨāĻžāĻ•āϞ⧇āĻ“ āĻšā§āĻŦāĻšā§ āĻāĻ•āχ āϏāĻŽā§āĻĒāĻĻāϗ⧁āϞ⧋āϰ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āϏāϰāĻŋā§Ÿā§‡ āĻĢ⧇āϞāĻž āĻšāĻŦ⧇āĨ¤", "machine_learning_duplicate_detection_enabled_description": "āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋ⧟ āĻĨāĻžāĻ•āϞ⧇āĻ“ āĻšā§āĻŦāĻšā§ āĻāĻ•āχ āϏāĻŽā§āĻĒāĻĻāϗ⧁āϞ⧋āϰ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āϏāϰāĻŋā§Ÿā§‡ āĻĢ⧇āϞāĻž āĻšāĻŦ⧇āĨ¤",
@@ -192,7 +192,7 @@
"machine_learning_url_description": "āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇āϰ URLāĨ¤ āϝāĻĻāĻŋ āĻāϕ⧇āϰ āĻŦ⧇āĻļāĻŋ URL āĻĒā§āϰāĻĻāĻžāύ āĻ•āϰāĻž āĻšā§Ÿ, āϤāĻŦ⧇ āĻāĻ•āϟāĻŋ āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āϏāĻžā§œāĻž āύāĻž āĻĻ⧇āĻ“ā§ŸāĻž āĻĒāĻ°ā§āϝāĻ¨ā§āϤ āĻĒā§āϰāϤāĻŋāϟāĻŋ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇ āĻāĻ• āĻāĻ• āĻ•āϰ⧇ āĻšā§‡āĻˇā§āϟāĻž āĻ•āϰāĻž āĻšāĻŦ⧇ (āĻĒā§āϰāĻĨāĻŽ āĻĨ⧇āϕ⧇ āĻļ⧇āώ āĻ•ā§āϰāĻŽāĻžāύ⧁āϏāĻžāϰ⧇)āĨ¤ āϝ⧇ āϏāĻžāĻ°ā§āĻ­āĻžāϰāϗ⧁āϞ⧋ āϏāĻžā§œāĻž āĻĻ⧇āĻŦ⧇ āύāĻž, āϏ⧇āϗ⧁āϞ⧋ āĻĒ⧁āύāϰāĻžā§Ÿ āϏāϚāϞ āĻšāĻ“ā§ŸāĻž āĻĒāĻ°ā§āϝāĻ¨ā§āϤ āϏāĻžāĻŽā§ŸāĻŋāĻ•āĻ­āĻžāĻŦ⧇ āωāĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤", "machine_learning_url_description": "āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇āϰ URLāĨ¤ āϝāĻĻāĻŋ āĻāϕ⧇āϰ āĻŦ⧇āĻļāĻŋ URL āĻĒā§āϰāĻĻāĻžāύ āĻ•āϰāĻž āĻšā§Ÿ, āϤāĻŦ⧇ āĻāĻ•āϟāĻŋ āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āϏāĻžā§œāĻž āύāĻž āĻĻ⧇āĻ“ā§ŸāĻž āĻĒāĻ°ā§āϝāĻ¨ā§āϤ āĻĒā§āϰāϤāĻŋāϟāĻŋ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇ āĻāĻ• āĻāĻ• āĻ•āϰ⧇ āĻšā§‡āĻˇā§āϟāĻž āĻ•āϰāĻž āĻšāĻŦ⧇ (āĻĒā§āϰāĻĨāĻŽ āĻĨ⧇āϕ⧇ āĻļ⧇āώ āĻ•ā§āϰāĻŽāĻžāύ⧁āϏāĻžāϰ⧇)āĨ¤ āϝ⧇ āϏāĻžāĻ°ā§āĻ­āĻžāϰāϗ⧁āϞ⧋ āϏāĻžā§œāĻž āĻĻ⧇āĻŦ⧇ āύāĻž, āϏ⧇āϗ⧁āϞ⧋ āĻĒ⧁āύāϰāĻžā§Ÿ āϏāϚāϞ āĻšāĻ“ā§ŸāĻž āĻĒāĻ°ā§āϝāĻ¨ā§āϤ āϏāĻžāĻŽā§ŸāĻŋāĻ•āĻ­āĻžāĻŦ⧇ āωāĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤",
"maintenance_delete_backup": "āĻŦā§āϝāĻžāĻ•āφāĻĒ (Backup)āĻŽā§āϛ⧁āύ", "maintenance_delete_backup": "āĻŦā§āϝāĻžāĻ•āφāĻĒ (Backup)āĻŽā§āϛ⧁āύ",
"maintenance_delete_backup_description": "āĻāχ āĻĢāĻžāχāϞāϟāĻŋ āϚāĻŋāϰāϤāϰ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāĻŦ⧇āĨ¤", "maintenance_delete_backup_description": "āĻāχ āĻĢāĻžāχāϞāϟāĻŋ āϚāĻŋāϰāϤāϰ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāĻŦ⧇āĨ¤",
"maintenance_delete_error": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āĻŦā§āϝāĻ°ā§āĻĨ āĻšā§Ÿā§‡āϛ⧇āĨ¤", "maintenance_delete_error": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻŽā§āĻ›āϤ⧇ āĻŦā§āϝāĻ°ā§āĻĨ āĻšā§Ÿā§‡āϛ⧇āĨ¤",
"maintenance_restore_backup": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ(Restore) āĻ•āϰ⧁āύ", "maintenance_restore_backup": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ(Restore) āĻ•āϰ⧁āύ",
"maintenance_restore_backup_description": "Immich āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāĻŦ⧇ āĻāĻŦāĻ‚ āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĨ⧇āϕ⧇ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤ āĻ•āĻžāĻ°ā§āϝāĻ•ā§āϰāĻŽ āϚāĻžāϞāĻŋā§Ÿā§‡ āϝāĻžāĻ“ā§ŸāĻžāϰ āφāϗ⧇ āĻāĻ•āϟāĻŋ āĻŦā§āϝāĻžāĻ•āφāĻĒ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤", "maintenance_restore_backup_description": "Immich āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāĻŦ⧇ āĻāĻŦāĻ‚ āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĨ⧇āϕ⧇ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤ āĻ•āĻžāĻ°ā§āϝāĻ•ā§āϰāĻŽ āϚāĻžāϞāĻŋā§Ÿā§‡ āϝāĻžāĻ“ā§ŸāĻžāϰ āφāϗ⧇ āĻāĻ•āϟāĻŋ āĻŦā§āϝāĻžāĻ•āφāĻĒ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤",
"maintenance_restore_backup_different_version": "āĻāχ āĻŦā§āϝāĻžāĻ•āφāĻĒāϟāĻŋ Immich-āĻāϰ āĻāĻ•āϟāĻŋ āĻ­āĻŋāĻ¨ā§āύ āϏāĻ‚āĻ¸ā§āĻ•āϰāϪ⧇āϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āĻšā§Ÿā§‡āĻ›āĻŋāϞ!", "maintenance_restore_backup_different_version": "āĻāχ āĻŦā§āϝāĻžāĻ•āφāĻĒāϟāĻŋ Immich-āĻāϰ āĻāĻ•āϟāĻŋ āĻ­āĻŋāĻ¨ā§āύ āϏāĻ‚āĻ¸ā§āĻ•āϰāϪ⧇āϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āĻšā§Ÿā§‡āĻ›āĻŋāϞ!",
@@ -220,7 +220,7 @@
"map_reverse_geocoding_settings": "āϰāĻŋāĻ­āĻžāĻ°ā§āϏ āϜāĻŋāĻ“āϕ⧋āĻĄāĻŋāĻ‚ āϏ⧇āϟāĻŋāĻ‚āϏ (Reverse Geocoding Settings)", "map_reverse_geocoding_settings": "āϰāĻŋāĻ­āĻžāĻ°ā§āϏ āϜāĻŋāĻ“āϕ⧋āĻĄāĻŋāĻ‚ āϏ⧇āϟāĻŋāĻ‚āϏ (Reverse Geocoding Settings)",
"map_settings": "āĻŽāĻžāύāϚāĻŋāĻ¤ā§āϰ (Map)", "map_settings": "āĻŽāĻžāύāϚāĻŋāĻ¤ā§āϰ (Map)",
"map_settings_description": "āĻŽāĻžāύāϚāĻŋāĻ¤ā§āϰ⧇āϰ āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ (Manage map settings)", "map_settings_description": "āĻŽāĻžāύāϚāĻŋāĻ¤ā§āϰ⧇āϰ āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ (Manage map settings)",
"map_style_description": "style.json āĻŽā§āϝāĻžāĻĒ āĻĨāĻŋāĻŽā§‡āϰ URL āĻ āĻŋāĻ•āĻžāύāĻž", "map_style_description": "āĻāĻ•āϟāĻŋ style.json āĻŽā§āϝāĻžāĻĒ āĻĨāĻŋāĻŽā§‡āϰ URL (URL to a style.json map theme)",
"memory_cleanup_job": "āĻŽā§‡āĻŽāϰāĻŋ āĻ•ā§āϞāĻŋāύāφāĻĒ (Memory cleanup)", "memory_cleanup_job": "āĻŽā§‡āĻŽāϰāĻŋ āĻ•ā§āϞāĻŋāύāφāĻĒ (Memory cleanup)",
"memory_generate_job": "āĻ¸ā§āĻŽā§ƒāϤāĻŋ āϤ⧈āϰāĻŋ āĻ•āϰāĻž(Memory generation)", "memory_generate_job": "āĻ¸ā§āĻŽā§ƒāϤāĻŋ āϤ⧈āϰāĻŋ āĻ•āϰāĻž(Memory generation)",
"metadata_extraction_job": "āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āĻāĻ•ā§āϏāĻŸā§āĻ°ā§āϝāĻžāĻ•ā§āϟ āĻ•āϰ⧁āύ (Extract metadata)", "metadata_extraction_job": "āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āĻāĻ•ā§āϏāĻŸā§āĻ°ā§āϝāĻžāĻ•ā§āϟ āĻ•āϰ⧁āύ (Extract metadata)",
@@ -231,8 +231,6 @@
"metadata_settings_description": "āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ (Manage metadata settings)", "metadata_settings_description": "āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ (Manage metadata settings)",
"migration_job": "āĻŽāĻžāχāĻ—ā§āϰ⧇āĻļāύ (Migration)", "migration_job": "āĻŽāĻžāχāĻ—ā§āϰ⧇āĻļāύ (Migration)",
"migration_job_description": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻāĻŦāĻ‚ āĻĢ⧇āϏ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞāϗ⧁āϞ⧋āϕ⧇ āϏāĻ°ā§āĻŦāĻļ⧇āώ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻ¸ā§āĻŸā§āϰāĻžāĻ•āϚāĻžāϰ⧇ āĻŽāĻžāχāĻ—ā§āϰ⧇āϟ āĻ•āϰ⧁āύāĨ¤ (Migrate thumbnails for assets and faces to the latest folder structure)", "migration_job_description": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻāĻŦāĻ‚ āĻĢ⧇āϏ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞāϗ⧁āϞ⧋āϕ⧇ āϏāĻ°ā§āĻŦāĻļ⧇āώ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻ¸ā§āĻŸā§āϰāĻžāĻ•āϚāĻžāϰ⧇ āĻŽāĻžāχāĻ—ā§āϰ⧇āϟ āĻ•āϰ⧁āύāĨ¤ (Migrate thumbnails for assets and faces to the latest folder structure)",
"nightly_tasks_cluster_faces_setting_description": "āύāϤ⧁āύ āĻļāύāĻžāĻ•ā§āϤ āĻšāĻ“āϝāĻŧāĻž āĻŽā§āĻ–āϗ⧁āϞāĻŋāϤ⧇ āĻĢ⧇āϏāĻŋāϝāĻŧāĻžāϞ āϰāĻŋāĻ•āĻ—āύāĻŋāĻļāύ āϚāĻžāϞāĻžāύ",
"nightly_tasks_cluster_new_faces_setting": "āύāϤ⧁āύ āĻŽā§āĻ–āϗ⧁āϞ⧋āϰ āϗ⧁āĻšā§āĻ›",
"nightly_tasks_database_cleanup_setting": "āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āĻ•ā§āϞāĻŋāύāφāĻĒ āϟāĻžāĻ¸ā§āĻ•āϏāĻŽā§‚āĻš (Database cleanup tasks)", "nightly_tasks_database_cleanup_setting": "āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āĻ•ā§āϞāĻŋāύāφāĻĒ āϟāĻžāĻ¸ā§āĻ•āϏāĻŽā§‚āĻš (Database cleanup tasks)",
"nightly_tasks_database_cleanup_setting_description": "āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āĻĨ⧇āϕ⧇ āĻĒ⧁āϰ⧋āύ⧋ āĻāĻŦāĻ‚ āĻŽā§‡ā§ŸāĻžāĻĻā§‹āĻ¤ā§āϤ⧀āĻ°ā§āĻŖ āĻĄā§‡āϟāĻž āĻŽā§āϛ⧇ āĻĢ⧇āϞ⧁āύ", "nightly_tasks_database_cleanup_setting_description": "āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āĻĨ⧇āϕ⧇ āĻĒ⧁āϰ⧋āύ⧋ āĻāĻŦāĻ‚ āĻŽā§‡ā§ŸāĻžāĻĻā§‹āĻ¤ā§āϤ⧀āĻ°ā§āĻŖ āĻĄā§‡āϟāĻž āĻŽā§āϛ⧇ āĻĢ⧇āϞ⧁āύ",
"nightly_tasks_generate_memories_setting": "āĻŽā§‡āĻŽā§‹āϰāĻŋāϜ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ (Generate memories)", "nightly_tasks_generate_memories_setting": "āĻŽā§‡āĻŽā§‹āϰāĻŋāϜ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ (Generate memories)",
@@ -259,20 +257,6 @@
"notification_email_secure": "SMTPS (āĻ¸ā§āĻŽāĻžāĻ°ā§āϟ āĻŽā§‡āχāϞ āĻŸā§āϰāĻžāĻ¨ā§āϏāĻĢāĻžāϰ āĻĒā§āϰ⧋āĻŸā§‹āĻ•āϞ āϏāĻŋāĻ•āĻŋāωāϰ)", "notification_email_secure": "SMTPS (āĻ¸ā§āĻŽāĻžāĻ°ā§āϟ āĻŽā§‡āχāϞ āĻŸā§āϰāĻžāĻ¨ā§āϏāĻĢāĻžāϰ āĻĒā§āϰ⧋āĻŸā§‹āĻ•āϞ āϏāĻŋāĻ•āĻŋāωāϰ)",
"notification_email_secure_description": "SMTPS (SMTP over TLS) āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ", "notification_email_secure_description": "SMTPS (SMTP over TLS) āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ",
"notification_email_sent_test_email_button": "āĻŸā§‡āĻ¸ā§āϟ āχāĻŽā§‡āϞ āĻĒāĻžāĻ āĻžāύ āĻāĻŦāĻ‚ āϏ⧇āĻ­ āĻ•āϰ⧁āύ", "notification_email_sent_test_email_button": "āĻŸā§‡āĻ¸ā§āϟ āχāĻŽā§‡āϞ āĻĒāĻžāĻ āĻžāύ āĻāĻŦāĻ‚ āϏ⧇āĻ­ āĻ•āϰ⧁āύ",
"notification_email_setting_description": "āχāĻŽā§‡āϞ āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āĻĒāĻžāĻ āĻžāύ⧋āϰ āϏ⧇āϟāĻŋāĻ‚āϏ",
"notification_email_test_email": "āĻĒāϰ⧀āĻ•ā§āώāĻžāĻŽā§‚āϞāĻ• āχāĻŽā§‡āχāϞ āĻĒāĻžāĻ āĻžāύ",
"notification_email_test_email_failed": "āĻĒāϰ⧀āĻ•ā§āώāĻžāĻŽā§‚āϞāĻ• āχāĻŽā§‡āϞ āĻĒāĻžāĻ āĻžāύ⧋ āϏāĻŽā§āĻ­āĻŦ āĻšāϝāĻŧāύāĻŋ, āφāĻĒāύāĻžāϰ āϏ⧇āϟāĻŋāĻ‚āϏ āϝāĻžāϚāĻžāχ āĻ•āϰ⧁āύ",
"notification_email_test_email_sent": "{email}-āĻ āĻāĻ•āϟāĻŋ āĻĒāϰ⧀āĻ•ā§āώāĻžāĻŽā§‚āϞāĻ• āχāĻŽā§‡āϞ āĻĒāĻžāĻ āĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇āĨ¤ āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ āφāĻĒāύāĻžāϰ āχāύāĻŦāĻ•ā§āϏ āĻĻ⧇āϖ⧁āύāĨ¤",
"notification_email_username_description": "āχāĻŽā§‡āϞ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇ āϭ⧇āϰāĻŋāĻĢāĻŋāϕ⧇āϏāύ⧇āϰ āϜāĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšā§ƒāϤ āχāωāϜāĻžāϰāύ⧇āĻŽ",
"notification_enable_email_notifications": "āχāĻŽā§‡āϞ āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āϏāύ āϏāĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰ⧁āύ",
"notification_settings": "āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āϏāύ āϏ⧇āϟāĻŋāĻ‚āϏ",
"notification_settings_description": "āχāĻŽā§‡āχāϞ āϏāĻš āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ",
"oauth_auto_launch": "āĻ…āĻŸā§‹ āϞāĻžā§āϚ",
"oauth_auto_launch_description": "āϞāĻ—āχāύ āĻĒ⧇āĻœā§‡ āĻĒā§āϰāĻŦ⧇āĻļ āĻ•āϰāĻžāϰ āϏāĻžāĻĨ⧇ āϏāĻžāĻĨ⧇ OAuth āϞāĻ—āχāύ āĻĒā§āϰāĻ•ā§āϰāĻŋāϝāĻŧāĻžāϟāĻŋ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āĻļ⧁āϰ⧁ āĻ•āϰ⧁āύ",
"oauth_auto_register": "āϏ⧟āĻ‚āĻ•ā§āϰāĻŋ⧟āĻ­āĻžāĻŦ⧇ āϰ⧇āϜāĻŋāĻ¸ā§āϟāĻžāϰ āĻ•āϰ⧁āύ",
"oauth_auto_register_description": "OAuth āĻĻāĻŋāϝāĻŧ⧇ āϏāĻžāχāύ āχāύ āĻ•āϰāĻžāϰ āĻĒāϰ āύāϤ⧁āύ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āĻĻ⧇āϰ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āύāĻŋāĻŦāĻ¨ā§āϧāύ āĻ•āϰ⧁āύ",
"oauth_button_text": "āĻŦāĻžāϟāύ āĻŸā§‡āĻ•ā§āϏāϟ",
"oauth_client_secret_description": "āĻ—ā§‹āĻĒāύ⧀āϝāĻŧ āĻ•ā§āϞāĻžāϝāĻŧ⧇āĻ¨ā§āĻŸā§‡āϰ āϜāĻ¨ā§āϝ āĻĒā§āϰāϝāĻŧā§‹āϜāύ, āĻ…āĻĨāĻŦāĻž āϝāĻĻāĻŋ āĻĒāĻžāĻŦāϞāĻŋāĻ• āĻ•ā§āϞāĻžāϝāĻŧ⧇āĻ¨ā§āĻŸā§‡āϰ āϜāĻ¨ā§āϝ PKCE (Proof Key for Code Exchange) āϏāĻŽāĻ°ā§āĻĨāĻŋāϤ āύāĻž āĻšāϝāĻŧāĨ¤",
"oauth_enable_description": "OAuth-āĻāϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āϞāĻ—āχāύ āĻ•āϰ⧁āύ", "oauth_enable_description": "OAuth-āĻāϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āϞāĻ—āχāύ āĻ•āϰ⧁āύ",
"oauth_mobile_redirect_uri": "āĻŽā§‹āĻŦāĻžāχāϞ āϰāĻŋāĻĄāĻžāχāϰ⧇āĻ•ā§āϟ āχāωāφāϰāφāχ (URI)", "oauth_mobile_redirect_uri": "āĻŽā§‹āĻŦāĻžāχāϞ āϰāĻŋāĻĄāĻžāχāϰ⧇āĻ•ā§āϟ āχāωāφāϰāφāχ (URI)",
"oauth_mobile_redirect_uri_override": "āĻŽā§‹āĻŦāĻžāχāϞ āϰāĻŋāĻĄāĻžāχāϰ⧇āĻ•ā§āϟ āχāωāφāϰāφāχ (URI) āĻ“āĻ­āĻžāϰāϰāĻžāχāĻĄ", "oauth_mobile_redirect_uri_override": "āĻŽā§‹āĻŦāĻžāχāϞ āϰāĻŋāĻĄāĻžāχāϰ⧇āĻ•ā§āϟ āχāωāφāϰāφāχ (URI) āĻ“āĻ­āĻžāϰāϰāĻžāχāĻĄ",
@@ -311,7 +295,7 @@
"search_jobs": "āϜāĻŦ āϏāĻžāĻ°ā§āϚ āĻ•āϰ⧁āύâ€Ļ", "search_jobs": "āϜāĻŦ āϏāĻžāĻ°ā§āϚ āĻ•āϰ⧁āύâ€Ļ",
"send_welcome_email": "āĻ¸ā§āĻŦāĻžāĻ—āϤ āχāĻŽā§‡āϞ āĻĒāĻžāĻ āĻžāύ", "send_welcome_email": "āĻ¸ā§āĻŦāĻžāĻ—āϤ āχāĻŽā§‡āϞ āĻĒāĻžāĻ āĻžāύ",
"server_external_domain_settings": "āĻāĻ•ā§āϏāϟāĻžāĻ°ā§āύāĻžāϞ āĻĄā§‹āĻŽā§‡āχāύ (External Domain)", "server_external_domain_settings": "āĻāĻ•ā§āϏāϟāĻžāĻ°ā§āύāĻžāϞ āĻĄā§‹āĻŽā§‡āχāύ (External Domain)",
"server_external_domain_settings_description": "āĻŦāĻžāχāϰ⧇āϰ āϞāĻŋāĻ™ā§āϕ⧇āϰ āϜāĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšā§ƒāϤ āĻĄā§‹āĻŽā§‡āχāύ", "server_external_domain_settings_description": "āĻĒāĻžāĻŦāϞāĻŋāĻ• āĻļ⧇āϝāĻŧāĻžāϰāĻŋāĻ‚ āϞāĻŋāĻ™ā§āϕ⧇āϰ āϜāĻ¨ā§āϝ āĻĄā§‹āĻŽā§‡āχāύ (http(s):// āϏāĻš)",
"server_public_users": "āĻĒāĻžāĻŦāϞāĻŋāĻ• āχāωāϜāĻžāϰ (Public Users)", "server_public_users": "āĻĒāĻžāĻŦāϞāĻŋāĻ• āχāωāϜāĻžāϰ (Public Users)",
"server_public_users_description": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϕ⧋āύ⧋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϕ⧇ āϝ⧋āĻ— āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āϏāĻŽāĻ¸ā§āϤ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ (āύāĻžāĻŽ āĻāĻŦāĻ‚ āχāĻŽā§‡āϞ) āϤāĻžāϞāĻŋāĻ•āĻž āĻĻ⧇āĻ–āĻžāύ⧋ āĻšāϝāĻŧāĨ¤ āĻāϟāĻŋ āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ (Disabled) āĻ•āϰāĻž āĻšāϞ⧇, āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āϤāĻžāϞāĻŋāĻ•āĻž āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύāĻĻ⧇āϰ āϜāĻ¨ā§āϝ āωāĻĒāϞāĻŦā§āϧ āĻšāĻŦ⧇āĨ¤", "server_public_users_description": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϕ⧋āύ⧋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϕ⧇ āϝ⧋āĻ— āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āϏāĻŽāĻ¸ā§āϤ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ (āύāĻžāĻŽ āĻāĻŦāĻ‚ āχāĻŽā§‡āϞ) āϤāĻžāϞāĻŋāĻ•āĻž āĻĻ⧇āĻ–āĻžāύ⧋ āĻšāϝāĻŧāĨ¤ āĻāϟāĻŋ āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ (Disabled) āĻ•āϰāĻž āĻšāϞ⧇, āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āϤāĻžāϞāĻŋāĻ•āĻž āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύāĻĻ⧇āϰ āϜāĻ¨ā§āϝ āωāĻĒāϞāĻŦā§āϧ āĻšāĻŦ⧇āĨ¤",
"server_settings": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āϏ⧇āϟāĻŋāĻ‚āϏ (Server Settings)", "server_settings": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āϏ⧇āϟāĻŋāĻ‚āϏ (Server Settings)",
@@ -333,26 +317,12 @@
"storage_template_migration_description": "āĻĒā§‚āĻ°ā§āĻŦ⧇ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋āϤ⧇ āĻŦāĻ°ā§āϤāĻŽāĻžāύ <link>{template}</link> āĻĒā§āϰāϝāĻŧā§‹āĻ— āĻ•āϰ⧁āύ", "storage_template_migration_description": "āĻĒā§‚āĻ°ā§āĻŦ⧇ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋āϤ⧇ āĻŦāĻ°ā§āϤāĻŽāĻžāύ <link>{template}</link> āĻĒā§āϰāϝāĻŧā§‹āĻ— āĻ•āϰ⧁āύ",
"storage_template_migration_info": "āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟāϟāĻŋ āϏāĻŽāĻ¸ā§āϤ āĻāĻ•ā§āϏāĻŸā§‡āύāĻļāύāϕ⧇ āϛ⧋āϟ āĻšāĻžāϤ⧇āϰ āĻ…āĻ•ā§āώāϰ⧇ (lowercase) āϰ⧂āĻĒāĻžāĻ¨ā§āϤāϰ āĻ•āϰāĻŦ⧇āĨ¤ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āĻŸā§‡āϰ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύāϗ⧁āϞ⧋ āϕ⧇āĻŦāϞ āύāϤ⧁āύ āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋āϰ āĻ•ā§āώ⧇āĻ¤ā§āϰ⧇ āĻĒā§āϰāϝ⧋āĻœā§āϝ āĻšāĻŦ⧇āĨ¤ āĻĒā§‚āĻ°ā§āĻŦ⧇ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋āϤ⧇ āĻāχ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟāϟāĻŋ āĻ­ā§‚āϤāĻžāĻĒ⧇āĻ•ā§āώāĻ­āĻžāĻŦ⧇ (retroactively) āĻĒā§āϰāϝāĻŧā§‹āĻ— āĻ•āϰāϤ⧇ <link>{job}</link> āϰāĻžāύ āĻ•āϰ⧁āύāĨ¤", "storage_template_migration_info": "āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟāϟāĻŋ āϏāĻŽāĻ¸ā§āϤ āĻāĻ•ā§āϏāĻŸā§‡āύāĻļāύāϕ⧇ āϛ⧋āϟ āĻšāĻžāϤ⧇āϰ āĻ…āĻ•ā§āώāϰ⧇ (lowercase) āϰ⧂āĻĒāĻžāĻ¨ā§āϤāϰ āĻ•āϰāĻŦ⧇āĨ¤ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āĻŸā§‡āϰ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύāϗ⧁āϞ⧋ āϕ⧇āĻŦāϞ āύāϤ⧁āύ āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋āϰ āĻ•ā§āώ⧇āĻ¤ā§āϰ⧇ āĻĒā§āϰāϝ⧋āĻœā§āϝ āĻšāĻŦ⧇āĨ¤ āĻĒā§‚āĻ°ā§āĻŦ⧇ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋āϤ⧇ āĻāχ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟāϟāĻŋ āĻ­ā§‚āϤāĻžāĻĒ⧇āĻ•ā§āώāĻ­āĻžāĻŦ⧇ (retroactively) āĻĒā§āϰāϝāĻŧā§‹āĻ— āĻ•āϰāϤ⧇ <link>{job}</link> āϰāĻžāύ āĻ•āϰ⧁āύāĨ¤",
"storage_template_migration_job": "āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ āĻŽāĻžāχāĻ—ā§āϰ⧇āĻļāύ āϜāĻŦ", "storage_template_migration_job": "āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ āĻŽāĻžāχāĻ—ā§āϰ⧇āĻļāύ āϜāĻŦ",
"storage_template_more_details": "āĻāχ āĻĢāĻŋāϚāĻžāϰ āϏāĻŽā§āĻĒāĻ°ā§āϕ⧇ āφāϰāĻ“ āĻŦāĻŋāĻ¸ā§āϤāĻžāϰāĻŋāϤāĻ­āĻžāĻŦ⧇ āϜāĻžāύāϤ⧇ <template-link>Storage Template</template-link> āĻāĻŦāĻ‚ āĻāϰ <implications-link>āĻĒā§āϰāĻ­āĻžāĻŦ</implications-link> āĻĻ⧇āϖ⧁āύ", "storage_template_more_details": "āĻāχ āĻĢāĻŋāϚāĻžāϰāϟāĻŋ āϏāĻŽā§āĻĒāĻ°ā§āϕ⧇ āφāϰāĻ“ āĻŦāĻŋāĻ¸ā§āϤāĻžāϰāĻŋāϤ āϜāĻžāύāϤ⧇, <template-link>Storage Template</template-link> āĻāĻŦāĻ‚ āĻāϰ <implications-link>āĻĒā§āϰāĻ­āĻžāĻŦāϗ⧁āϞ⧋ (implications)</implications-link> āĻĻ⧇āϖ⧁āύāĨ¤",
"storage_template_onboarding_description_v2": "āĻāϟāĻŋ āϏāĻ•ā§āϰāĻŋ⧟ āĻĨāĻžāĻ•āϞ⧇, āĻĢāĻŋāϚāĻžāϰāϟāĻŋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŋāϤ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ āĻ…āύ⧁āϝāĻžā§Ÿā§€ āĻĢāĻžāχāϞāϗ⧁āϞ⧋āϕ⧇ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āĻ…āĻ°ā§āĻ—āĻžāύāĻžāχāϜ (Auto-organize) āĻ•āϰāĻŦ⧇āĨ¤ āφāϰāĻ“ āϤāĻĨā§āϝ⧇āϰ āϜāĻ¨ā§āϝ āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ <link>āĻĄāϕ⧁āĻŽā§‡āĻ¨ā§āĻŸā§‡āĻļāύ</link> āĻĻ⧇āϖ⧁āύāĨ¤", "storage_template_onboarding_description_v2": "āĻāϟāĻŋ āϏāĻ•ā§āϰāĻŋ⧟ āĻĨāĻžāĻ•āϞ⧇, āĻĢāĻŋāϚāĻžāϰāϟāĻŋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŋāϤ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ āĻ…āύ⧁āϝāĻžā§Ÿā§€ āĻĢāĻžāχāϞāϗ⧁āϞ⧋āϕ⧇ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āĻ…āĻ°ā§āĻ—āĻžāύāĻžāχāϜ (Auto-organize) āĻ•āϰāĻŦ⧇āĨ¤ āφāϰāĻ“ āϤāĻĨā§āϝ⧇āϰ āϜāĻ¨ā§āϝ āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ <link>āĻĄāϕ⧁āĻŽā§‡āĻ¨ā§āĻŸā§‡āĻļāύ</link> āĻĻ⧇āϖ⧁āύāĨ¤",
"storage_template_path_length": "āφāύ⧁āĻŽāĻžāύāĻŋāĻ•āĻ­āĻžāĻŦ⧇ āĻĒāĻĨ⧇āϰ āĻĻ⧈āĻ°ā§āĻ˜ā§āϝ⧇āϰ āϏ⧀āĻŽāĻž: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "āφāύ⧁āĻŽāĻžāύāĻŋāĻ• āĻĒāĻžāĻĨ āϞ⧇āĻ¨ā§āĻĨ āϞāĻŋāĻŽāĻŋāϟ (Path length limit): <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ (Storage Template)", "storage_template_settings": "āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ (Storage Template)",
"storage_template_settings_description": "āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡āϰ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻ¸ā§āĻŸā§āϰāĻžāĻ•āϚāĻžāϰ āĻāĻŦāĻ‚ āĻĢāĻžāχāϞ āύ⧇āĻŽ āĻŽā§āϝāĻžāύ⧇āϜ āĻ•āϰ⧁āύ", "storage_template_settings_description": "āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡āϰ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻ¸ā§āĻŸā§āϰāĻžāĻ•āϚāĻžāϰ āĻāĻŦāĻ‚ āĻĢāĻžāχāϞ āύ⧇āĻŽ āĻŽā§āϝāĻžāύ⧇āϜ āĻ•āϰ⧁āύ",
"storage_template_user_label": "<code>{label}</code> āĻšāϞ⧋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āϞ⧇āĻŦ⧇āϞ (Storage Label)", "storage_template_user_label": "<code>{label}</code> āĻšāϞ⧋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āϞ⧇āĻŦ⧇āϞ (Storage Label)",
"system_settings": "āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āϏ⧇āϟāĻŋāĻ‚āϏ",
"tag_cleanup_job": "āĻŸā§āϝāĻžāĻ— āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž",
"template_email_available_tags": "āφāĻĒāύāĻŋ āφāĻĒāύāĻžāϰ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āĻŸā§‡ āύāĻŋāĻŽā§āύāϞāĻŋāĻ–āĻŋāϤ āϭ⧇āϰāĻŋāϝāĻŧ⧇āĻŦāϞāϗ⧁āϞ⧋ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇āύ: {tags}",
"template_email_if_empty": "āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟāϟāĻŋ āĻ–āĻžāϞāĻŋ āĻĨāĻžāĻ•āϞ⧇ āĻĄāĻŋāĻĢāĻ˛ā§āϟ āχāĻŽā§‡āϞ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤",
"template_email_invite_album": "āχāύāĻ­āĻžāχāϟ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ",
"template_email_preview": "āĻĒā§āϰāĻŋāĻ­āĻŋāω",
"template_email_settings": "āχāĻŽā§‡āχāϞ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ",
"template_email_update_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ āφāĻĒāĻĄā§‡āϟ āĻ•āϰ⧁āύ",
"template_email_welcome": "āĻ¸ā§āĻŦāĻžāĻ—āϤāĻŽ āχāĻŽā§‡āχāϞ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ",
"template_settings": "āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ",
"template_settings_description": "āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ⧇āϰ āϜāĻ¨ā§āϝ āĻ•āĻžāĻ¸ā§āϟāĻŽ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ",
"theme_custom_css_settings": "āĻ•āĻžāĻ¸ā§āϟāĻŽ CSS",
"theme_custom_css_settings_description": "āĻ•ā§āϝāĻžāϏāϕ⧇āĻĄāĻŋāĻ‚ āĻ¸ā§āϟāĻžāχāϞ āĻļā§€āϟ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ Immich āĻāϰ āĻĄāĻŋāϜāĻžāχāύ āĻ•āĻžāĻ¸ā§āϟāĻŽāĻžāχāϜ āĻ•āϰāĻž āϝāĻžāϝāĻŧāĨ¤",
"theme_settings": "āĻĨā§€āĻŽ āϏ⧇āϟāĻŋāĻ‚āϏ",
"theme_settings_description": "āχāĻŽāĻŋāϚ (Immich) āĻ“āϝāĻŧ⧇āĻŦ āχāĻ¨ā§āϟāĻžāϰāĻĢ⧇āϏ⧇āϰ āĻ•āĻžāĻ¸ā§āϟāĻŽāĻžāχāĻœā§‡āĻļāύ āĻŽā§āϝāĻžāύ⧇āϜ āĻ•āϰ⧁āύ", "theme_settings_description": "āχāĻŽāĻŋāϚ (Immich) āĻ“āϝāĻŧ⧇āĻŦ āχāĻ¨ā§āϟāĻžāϰāĻĢ⧇āϏ⧇āϰ āĻ•āĻžāĻ¸ā§āϟāĻŽāĻžāχāĻœā§‡āĻļāύ āĻŽā§āϝāĻžāύ⧇āϜ āĻ•āϰ⧁āύ",
"thumbnail_generation_job": "āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ (Generate Thumbnails)", "thumbnail_generation_job": "āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ (Generate Thumbnails)",
"thumbnail_generation_job_description": "āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡āϰ āϜāĻ¨ā§āϝ āĻŦ⧜, āϛ⧋āϟ āĻāĻŦāĻ‚ āĻŦā§āϞāĻžāϰ (āĻ…āĻ¸ā§āĻĒāĻˇā§āϟ) āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ, āϏ⧇āχ āϏāĻžāĻĨ⧇ āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ āϜāĻ¨ā§āϝāĻ“ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύāĨ¤", "thumbnail_generation_job_description": "āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡āϰ āϜāĻ¨ā§āϝ āĻŦ⧜, āϛ⧋āϟ āĻāĻŦāĻ‚ āĻŦā§āϞāĻžāϰ (āĻ…āĻ¸ā§āĻĒāĻˇā§āϟ) āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ, āϏ⧇āχ āϏāĻžāĻĨ⧇ āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ āϜāĻ¨ā§āϝāĻ“ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύāĨ¤",
@@ -364,283 +334,8 @@
"transcoding_acceleration_vaapi": "VA-API (āĻ­āĻŋāĻĄāĻŋāĻ“ āĻ…ā§āϝāĻžāĻ•ā§āϏāĻŋāϞāĻžāϰ⧇āĻļāύ āĻāĻĒāĻŋāφāχ)", "transcoding_acceleration_vaapi": "VA-API (āĻ­āĻŋāĻĄāĻŋāĻ“ āĻ…ā§āϝāĻžāĻ•ā§āϏāĻŋāϞāĻžāϰ⧇āĻļāύ āĻāĻĒāĻŋāφāχ)",
"transcoding_accepted_audio_codecs": "āĻ—ā§āϰāĻšāĻŖāϝ⧋āĻ—ā§āϝ āĻ…āĻĄāĻŋāĻ“ āϕ⧋āĻĄā§‡āĻ•āϏāĻŽā§‚āĻš (Accepted audio codecs)", "transcoding_accepted_audio_codecs": "āĻ—ā§āϰāĻšāĻŖāϝ⧋āĻ—ā§āϝ āĻ…āĻĄāĻŋāĻ“ āϕ⧋āĻĄā§‡āĻ•āϏāĻŽā§‚āĻš (Accepted audio codecs)",
"transcoding_accepted_audio_codecs_description": "āϕ⧋āύ āĻ…āĻĄāĻŋāĻ“ āϕ⧋āĻĄā§‡āĻ•āϗ⧁āϞ⧋ āĻŸā§āϰāĻžāύāϏāϕ⧋āĻĄ āĻ•āϰāĻžāϰ āĻĒā§āĻ°ā§Ÿā§‹āϜāύ āύ⧇āχ āϤāĻž āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύāĨ¤ āĻāϟāĻŋ āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻŸā§āϰāĻžāύāϏāϕ⧋āĻĄ āĻĒāϞāĻŋāϏāĻŋāϰ (transcode policies) āϜāĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšā§ƒāϤ āĻšā§ŸāĨ¤", "transcoding_accepted_audio_codecs_description": "āϕ⧋āύ āĻ…āĻĄāĻŋāĻ“ āϕ⧋āĻĄā§‡āĻ•āϗ⧁āϞ⧋ āĻŸā§āϰāĻžāύāϏāϕ⧋āĻĄ āĻ•āϰāĻžāϰ āĻĒā§āĻ°ā§Ÿā§‹āϜāύ āύ⧇āχ āϤāĻž āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύāĨ¤ āĻāϟāĻŋ āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻŸā§āϰāĻžāύāϏāϕ⧋āĻĄ āĻĒāϞāĻŋāϏāĻŋāϰ (transcode policies) āϜāĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšā§ƒāϤ āĻšā§ŸāĨ¤",
"transcoding_accepted_containers": "āĻ—ā§āϰāĻšāĻŖāϝ⧋āĻ—ā§āϝ āĻ•āĻ¨ā§āĻŸā§‡āχāύāĻžāϰāϏāĻŽā§‚āĻš (Accepted containers)", "transcoding_accepted_containers": "āĻ—ā§āϰāĻšāĻŖāϝ⧋āĻ—ā§āϝ āĻ•āĻ¨ā§āĻŸā§‡āχāύāĻžāϰāϏāĻŽā§‚āĻš (Accepted containers)"
"transcoding_accepted_containers_description": "āϕ⧋āύ āĻ•āĻ¨ā§āĻŸā§‡āχāύāĻžāϰ āĻĢāϰāĻŽā§āϝāĻžāϟāϗ⧁āϞ⧋āϕ⧇ MP4-āĻ āϰāĻŋāĻŽā§āĻ•ā§āϏ āĻ•āϰāĻžāϰ āĻĒā§āϰāϝāĻŧā§‹āϜāύ āύ⧇āχ āϤāĻž āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύāĨ¤ āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻĒāϞāĻŋāϏāĻŋāϰ āϜāĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšā§ƒāϤ āĻšāϝāĻŧāĨ¤",
"transcoding_accepted_video_codecs": "āϏāĻŽāĻ°ā§āĻĨāĻŋāϤ āĻ­āĻŋāĻĄāĻŋāĻ“ āϕ⧋āĻĄā§‡āĻ•āϗ⧁āϞ⧋",
"transcoding_accepted_video_codecs_description": "āϕ⧋āύ āĻ­āĻŋāĻĄāĻŋāĻ“ āϕ⧋āĻĄā§‡āĻ•āϗ⧁āϞ⧋ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻ•āϰāĻžāϰ āĻĒā§āϰāϝāĻŧā§‹āϜāύ āύ⧇āχ āϤāĻž āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύāĨ¤ āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āύ⧀āϤāĻŋāϰ āϜāĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšā§ƒāϤ āĻšāϝāĻŧāĨ¤",
"transcoding_advanced_options_description": "āĻŦ⧇āĻļāĻŋāϰāĻ­āĻžāĻ— āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻžāϰ āĻĒā§āĻ°ā§Ÿā§‹āϜāύ āύ⧇āχ āĻāĻŽāύ āĻ…āĻĒāĻļāύāϏāĻŽā§‚āĻš",
"transcoding_audio_codec": "āĻ…āĻĄāĻŋāĻ“ āϕ⧋āĻĄā§‡āĻ•",
"transcoding_audio_codec_description": "Opus āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āĻŽāĻžāύ⧇āϰ āĻ…āĻĒāĻļāύ, āϤāĻŦ⧇ āĻĒ⧁āϰ⧋āύ⧋ āĻĄāĻŋāĻ­āĻžāχāϏ āĻŦāĻž āϏāĻĢāϟāĻ“ā§Ÿā§āϝāĻžāϰ⧇āϰ āϏāĻžāĻĨ⧇ āĻāϰ āϏāĻžāĻŽāĻžā§āϜāĻ¸ā§āϝ āĻ•āĻŽāĨ¤",
"transcoding_bitrate_description": "āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āĻŦāĻŋāϟāϰ⧇āĻŸā§‡āϰ āĻšā§‡ā§Ÿā§‡ āĻŦ⧇āĻļāĻŋ āĻŦāĻž āϏāĻŽāĻ°ā§āĻĨāĻŋāϤ āĻĢāϰāĻŽā§āϝāĻžāĻŸā§‡ āύ⧟ āĻāĻŽāύ āĻ­āĻŋāĻĄāĻŋāĻ“",
"transcoding_codecs_learn_more": "āĻāĻ–āĻžāύ⧇ āĻŦā§āϝāĻŦāĻšā§ƒāϤ āĻĒāϰāĻŋāĻ­āĻžāώāĻž āϏāĻŽā§āĻĒāĻ°ā§āϕ⧇ āφāϰāĻ“ āϜāĻžāύāϤ⧇ FFmpeg āĻĄāϕ⧁āĻŽā§‡āĻ¨ā§āĻŸā§‡āĻļāύ āĻĻ⧇āϖ⧁āύ, <h264-link>H.264 āϕ⧋āĻĄā§‡āĻ•</h264-link>, <hevc-link>HEVC āϕ⧋āĻĄā§‡āĻ•</hevc-link> āĻāĻŦāĻ‚ <vp9-link>VP9 āϕ⧋āĻĄā§‡āĻ•</vp9-link>āĨ¤",
"transcoding_constant_quality_mode": "āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻŽāĻžāύ āĻŽā§‹āĻĄ",
"transcoding_constant_quality_mode_description": "ICQ, CQP-āĻāϰ āĻšā§‡ā§Ÿā§‡ āĻ­āĻžāϞ⧋ āĻŽāĻžāύ āĻĻā§‡ā§Ÿ, āĻ•āĻŋāĻ¨ā§āϤ⧁ āϏāĻŦ āĻšāĻžāĻ°ā§āĻĄāĻ“ā§Ÿā§āϝāĻžāϰ āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϞāĻžāϰ⧇āĻļāύ āĻĄāĻŋāĻ­āĻžāχāϏ⧇ āĻ•āĻžāϜ āĻ•āϰ⧇ āύāĻžāĨ¤ āĻāχ āĻ…āĻĒāĻļāύ āϚāĻžāϞ⧁ āĻĨāĻžāĻ•āϞ⧇ āĻ•ā§‹ā§ŸāĻžāϞāĻŋāϟāĻŋ-āĻ­āĻŋāĻ¤ā§āϤāĻŋāĻ• āĻāύāϕ⧋āĻĄāĻŋāĻ‚ā§Ÿā§‡ āĻāϟāĻŋ āĻĒā§āϰāĻžāϧāĻžāĻ¨ā§āϝ āĻĒāĻžāĻŦ⧇āĨ¤ NVENC āĻāϟāĻŋ āϏāĻŽāĻ°ā§āĻĨāύ āĻ•āϰ⧇ āύāĻž, āϤāĻžāχ āĻāϟāĻŋ āωāĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤",
"transcoding_constant_rate_factor": "āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āϰ⧇āϟ āĻĢā§āϝāĻžāĻ•ā§āϟāϰ (-crf)",
"transcoding_constant_rate_factor_description": "āĻ­āĻŋāĻĄāĻŋāĻ“āϰ āϗ⧁āĻŖāĻŽāĻžāύ⧇āϰ āĻ¸ā§āϤāϰāĨ¤ āϏāĻžāϧāĻžāϰāĻŖ āĻŽāĻžāύāϗ⧁āϞ⧋ āĻšāϞ⧋ H.264-āĻāϰ āϜāĻ¨ā§āϝ ā§¨ā§Š, HEVC-āĻāϰ āϜāĻ¨ā§āϝ ā§¨ā§Ž, VP9-āĻāϰ āϜāĻ¨ā§āϝ ā§Šā§§ āĻāĻŦāĻ‚ AV1-āĻāϰ āϜāĻ¨ā§āϝ ā§Šā§ĢāĨ¤ āĻŽāĻžāύ āϝāϤ āĻ•āĻŽ āĻšāĻŦ⧇, āĻ­āĻŋāĻĄāĻŋāĻ“āϰ āϗ⧁āĻŖāĻŽāĻžāύ āϤāϤ āωāĻ¨ā§āύāϤ āĻšāĻŦ⧇, āϤāĻŦ⧇ āĻĢāĻžāχāϞ⧇āϰ āφāĻ•āĻžāϰ āϤāϤ āĻŦ⧜ āĻšāĻŦ⧇āĨ¤",
"transcoding_disabled_description": "āϕ⧋āύ⧋ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻ•āϰāĻŦ⧇āύ āύāĻž, āĻāϤ⧇ āĻ•āĻŋāϛ⧁ āĻ•ā§āϞāĻžāϝāĻŧ⧇āĻ¨ā§āĻŸā§‡ āĻĒā§āϞ⧇āĻŦā§āϝāĻžāĻ• āύāĻˇā§āϟ āĻšāϤ⧇ āĻĒāĻžāϰ⧇",
"transcoding_encoding_options": "āĻāύāϕ⧋āĻĄāĻŋāĻ‚ āĻāϰ āĻ…āĻĒāĻļāύāϗ⧁āϞāĻŋ",
"transcoding_encoding_options_description": "āĻāύāϕ⧋āĻĄ āĻ•āϰāĻž āĻ­āĻŋāĻĄāĻŋāĻ“āϗ⧁āϞāĻŋāϰ āϜāĻ¨ā§āϝ āϕ⧋āĻĄā§‡āĻ•, āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ, āϕ⧋āϝāĻŧāĻžāϞāĻŋāϟāĻŋ āĻāĻŦāĻ‚ āĻ…āĻ¨ā§āϝāĻžāĻ¨ā§āϝ āĻ…āĻĒāĻļāύ āϏ⧇āϟ āĻ•āϰ⧁āύ",
"transcoding_hardware_acceleration": "āĻšāĻžāĻ°ā§āĻĄāĻ“ā§Ÿā§āϝāĻžāϰ āĻāĻ•ā§āϏāĻŋāϞāĻžāϰ⧇āϏāύ (Acceleration)",
"transcoding_hardware_acceleration_description": "āĻĒāϰ⧀āĻ•ā§āώāĻžāĻŽā§‚āϞāĻ•: āĻĻā§āϰ⧁āϤāϤāϰ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄāĻŋāĻ‚, āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻāĻ•āχ āĻŦāĻŋāϟāϰ⧇āĻŸā§‡ āϗ⧁āĻŖāĻŽāĻžāύ āĻšā§āϰāĻžāϏ āĻĒ⧇āϤ⧇ āĻĒāĻžāϰ⧇",
"transcoding_hardware_decoding": "āĻšāĻžāĻ°ā§āĻĄāĻ“ā§Ÿā§āϝāĻžāϰ āĻĄāĻŋāϕ⧋āĻĄāĻŋāĻ‚",
"transcoding_hardware_decoding_setting_description": "āĻļ⧁āϧ⧁ āĻāύāϕ⧋āĻĄāĻŋāĻ‚ āĻ…ā§āϝāĻžāĻ•ā§āϏāĻŋāϞāĻžāϰ⧇āĻļāύ āĻ•āϰāĻžāϰ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤ⧇ āĻāϟāĻŋ āĻāĻ¨ā§āĻĄ-āϟ⧁-āĻāĻ¨ā§āĻĄ āĻ…ā§āϝāĻžāĻ•ā§āϏāĻŋāϞāĻžāϰ⧇āĻļāύ āϏāĻ•ā§āώāĻŽ āĻ•āϰ⧇āĨ¤ āϏāĻŦ āĻ­āĻŋāĻĄāĻŋāĻ“āϤ⧇ āĻ•āĻžāϜ āύāĻžāĻ“ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤",
"transcoding_max_b_frames": "āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āĻŦāĻŋ-āĻĢā§āϰ⧇āĻŽ (B-frames)",
"transcoding_max_b_frames_description": "āĻŽāĻžāύ āϝāϤ āĻŦ⧇āĻļāĻŋ āĻšāĻŦ⧇, āĻ•āĻŽāĻĒā§āϰ⧇āĻļāύ āϤāϤ āĻ­āĻžāϞ⧋ āĻšāĻŦ⧇ āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻāύāϕ⧋āĻĄāĻŋāĻ‚ āϧ⧀āϰ⧇ āϚāϞāĻŦ⧇āĨ¤ āĻĒ⧁āϰ⧋āύ⧋ āĻĄāĻŋāĻ­āĻžāχāϏ⧇ āĻšāĻžāĻ°ā§āĻĄāĻ“ā§Ÿā§āϝāĻžāϰ āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϞāĻžāϰ⧇āĻļāύ āĻ•āĻžāϜ āύāĻžāĻ“ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤ ā§Ļ āĻĻāĻŋāϞ⧇ B-frames āĻŦāĻ¨ā§āϧ āĻĨāĻžāĻ•āĻŦ⧇, -ā§§ āĻĻāĻŋāϞ⧇ āĻāϟāĻŋ āύāĻŋāĻœā§‡ āĻĨ⧇āϕ⧇āχ āĻ āĻŋāĻ• āĻšāĻŦ⧇āĨ¤",
"transcoding_max_bitrate": "āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āĻŦāĻŋāϟāϰ⧇āϟ",
"transcoding_max_bitrate_description": "āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āĻŦāĻŋāϟāϰ⧇āϟ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰāϞ⧇ āĻĢāĻžāχāϞ⧇āϰ āφāĻ•āĻžāϰ āφāϰāĻ“ āĻ…āύ⧁āĻŽāĻžāύāϝ⧋āĻ—ā§āϝ āĻšāϤ⧇ āĻĒāĻžāϰ⧇, āϤāĻŦ⧇ āĻāϰ āĻĢāϞ⧇ āϕ⧋āϝāĻŧāĻžāϞāĻŋāϟāĻŋāϰ āĻ•āĻŋāϛ⧁āϟāĻž āĻ…āĻŦāύāϤāĻŋ āϘāĻŸā§‡āĨ¤ 720p-āϤ⧇, VP9 āĻŦāĻž HEVC-āĻāϰ āϜāĻ¨ā§āϝ āϏāĻžāϧāĻžāϰāĻŖ āĻŽāĻžāύ āĻšāϞ⧋ 2600 kbit/s, āĻ…āĻĨāĻŦāĻž H.264-āĻāϰ āϜāĻ¨ā§āϝ 4500 kbit/sāĨ¤āĻāϰ āĻŽāĻžāύ 0 āϏ⧇āϟ āĻ•āϰāĻž āĻšāϞ⧇ āĻāϟāĻŋ āĻŦāĻ¨ā§āϧ āĻĨāĻžāϕ⧇āĨ¤ āϝāĻ–āύ āϕ⧋āύ⧋ āĻāĻ•āĻ• āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻ•āϰāĻž āĻĨāĻžāϕ⧇ āύāĻž, āϤāĻ–āύ k (kbit/s-āĻāϰ āϜāĻ¨ā§āϝ) āϧāϰ⧇ āύ⧇āĻ“āϝāĻŧāĻž āĻšāϝāĻŧ; āϤāĻžāχ 5000, 5000k, āĻāĻŦāĻ‚ 5M (Mbit/s-āĻāϰ āϜāĻ¨ā§āϝ) āϏāĻŽāϤ⧁āĻ˛ā§āϝāĨ¤",
"transcoding_max_keyframe_interval": "āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āϕ⧀āĻĢā§āϰ⧇āĻŽ āĻŦā§āϝāĻŦāϧāĻžāύ",
"transcoding_max_keyframe_interval_description": "āϕ⧀āĻĢā§āϰ⧇āĻŽā§‡āϰ āĻŽāĻ§ā§āϝ⧇ āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āĻĢā§āϰ⧇āĻŽ āĻĻā§‚āϰāĻ¤ā§āĻŦ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧇āĨ¤ āĻŽāĻžāύ āĻ•āĻŽ āĻšāϞ⧇ āĻ•āĻŽāĻĒā§āϰ⧇āĻļāύ āĻĻāĻ•ā§āώāϤāĻž āĻ•āĻŽā§‡, āϤāĻŦ⧇ āĻ­āĻŋāĻĄāĻŋāĻ“āϤ⧇ āϖ⧁āρāĻœā§‡ āĻŦ⧇āϰ āĻ•āϰāĻž āĻĻā§āϰ⧁āϤ āĻšā§Ÿ āĻāĻŦāĻ‚ āĻĻā§āϰ⧁āϤ āϚāϞāĻŽāĻžāύ āĻĻ⧃āĻļā§āϝ⧇ āĻŽāĻžāύāĻ“ āĻ•āĻŋāϛ⧁āϟāĻž āĻ­āĻžāϞ⧋ āĻšāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤ ā§Ļ āĻĻāĻŋāϞ⧇ āĻāχ āĻŽāĻžāύ āĻ¸ā§āĻŦ⧟āĻ‚āĻ•ā§āϰāĻŋ⧟āĻ­āĻžāĻŦ⧇ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŋāϤ āĻšā§ŸāĨ¤",
"transcoding_optimal_description": "āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ⧇āϰ āĻšā§‡ā§Ÿā§‡ āĻŦ⧜ āĻŦāĻž āϏāĻŽāĻ°ā§āĻĨāĻŋāϤ āĻĢāϰāĻŽā§āϝāĻžāĻŸā§‡ āύ⧟ āĻāĻŽāύ āĻ­āĻŋāĻĄāĻŋāĻ“",
"transcoding_policy": "āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āύ⧀āϤāĻŋ",
"transcoding_policy_description": "āĻ­āĻŋāĻĄāĻŋāĻ“ āĻ•āĻ–āύ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻ•āϰāĻž āĻšāĻŦ⧇ āϤāĻž āϏ⧇āϟ āĻ•āϰ⧁āύ",
"transcoding_preferred_hardware_device": "āĻĒāĻ›āĻ¨ā§āĻĻ⧇āϰ āĻšāĻžāĻ°ā§āĻĄāĻ“āϝāĻŧā§āϝāĻžāϰ āĻĄāĻŋāĻ­āĻžāχāϏ",
"transcoding_preferred_hardware_device_description": "āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ VAAPI āĻāĻŦāĻ‚ QSV-āĻāϰ āĻ•ā§āώ⧇āĻ¤ā§āϰ⧇ āĻĒā§āϰāϝ⧋āĻœā§āϝāĨ¤ āĻšāĻžāĻ°ā§āĻĄāĻ“āϝāĻŧā§āϝāĻžāϰ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄāĻŋāĻ‚āϝāĻŧ⧇āϰ āϜāĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšā§ƒāϤ dri āύ⧋āĻĄ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧇āĨ¤",
"transcoding_preset_preset": "āĻĒā§āϰāĻŋāϏ⧇āϟ (-preset)",
"transcoding_preset_preset_description": "āĻ•āĻŽā§āĻĒā§āϰ⧇āĻļāύ āĻ¸ā§āĻĒāĻŋāĻĄāĨ¤ āϧ⧀āϰāĻ—āϤāĻŋāϰ āĻĒā§āϰāĻŋāϏ⧇āϟāϗ⧁āϞ⧋ āϛ⧋āϟ āĻĢāĻžāχāϞ āϤ⧈āϰāĻŋ āĻ•āϰ⧇ āĻāĻŦāĻ‚ āĻāĻ•āϟāĻŋ āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻŦāĻŋāϟāϰ⧇āϟ āϞāĻ•ā§āĻˇā§āϝ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āϗ⧁āĻŖāĻŽāĻžāύ āĻŦ⧃āĻĻā§āϧāĻŋ āĻ•āϰ⧇āĨ¤ VP9 'faster'-āĻāϰ āĻšā§‡āϝāĻŧ⧇ āĻŦ⧇āĻļāĻŋ āĻ—āϤāĻŋ āωāĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰ⧇āĨ¤",
"transcoding_reference_frames": "āϰ⧇āĻĢāĻžāϰ⧇āĻ¨ā§āϏ āĻĢā§āϰ⧇āĻŽ",
"transcoding_reference_frames_description": "āĻāĻ•āϟāĻŋ āĻĢā§āϰ⧇āĻŽ āĻ•āĻŽā§āĻĒā§āϰ⧇āϏ āĻ•āϰāĻžāϰ āϏāĻŽā§Ÿ āĻ•āϤāϟāĻŋ āĻĢā§āϰ⧇āĻŽāϕ⧇ āϰ⧇āĻĢāĻžāϰ⧇āĻ¨ā§āϏ āĻšāĻŋāϏ⧇āĻŦ⧇ āύ⧇āĻ“ā§ŸāĻž āĻšāĻŦ⧇āĨ¤ āĻŽāĻžāύ āϝāϤ āĻŦ⧇āĻļāĻŋ āĻšāĻŦ⧇, āĻ•āĻŽāĻĒā§āϰ⧇āĻļāύ āĻĻāĻ•ā§āώāϤāĻž āϤāϤ āĻ­āĻžāϞ⧋ āĻšāĻŦ⧇, āϤāĻŦ⧇ āĻāύāϕ⧋āĻĄāĻŋāĻ‚ āϧ⧀āϰ āĻšāĻŦ⧇āĨ¤ ā§Ļ āĻĻāĻŋāϞ⧇ āĻāχ āĻŽāĻžāύ āĻ¸ā§āĻŦ⧟āĻ‚āĻ•ā§āϰāĻŋ⧟āĻ­āĻžāĻŦ⧇ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŋāϤ āĻšāĻŦ⧇āĨ¤",
"transcoding_required_description": "āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āĻ…āύ⧁āĻŽā§‹āĻĻāĻŋāϤ āĻĢāϰāĻŽā§āϝāĻžāĻŸā§‡ āύ⧇āχ āĻāĻŽāύ āĻ­āĻŋāĻĄāĻŋāĻ“",
"transcoding_settings": "āĻ­āĻŋāĻĄāĻŋāĻ“ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄāĻŋāĻ‚ āϏ⧇āϟāĻŋāĻ‚āϏ",
"transcoding_settings_description": "āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧁āύ āϕ⧋āύ āĻ­āĻŋāĻĄāĻŋāĻ“āϗ⧁āϞ⧋āϕ⧇ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻ•āϰāϤ⧇ āĻšāĻŦ⧇ āĻāĻŦāĻ‚ āĻ•āĻŋāĻ­āĻžāĻŦ⧇ āĻĒā§āϰāĻ•ā§āϰāĻŋ⧟āĻž āĻ•āϰāϤ⧇ āĻšāĻŦ⧇",
"transcoding_target_resolution": "āϟāĻžāĻ°ā§āϗ⧇āϟ āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ",
"transcoding_target_resolution_description": "āωāĻšā§āϚ āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ āĻŦ⧇āĻļāĻŋ āĻŦāĻŋāĻ¸ā§āϤāĻžāϰāĻŋāϤ āϰāĻžāϖ⧇, āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻāύāϕ⧋āĻĄāĻŋāĻ‚ āϧ⧀āϰ⧇ āĻšā§Ÿ, āĻĢāĻžāχāϞ āĻŦ⧜ āĻšā§Ÿ, āĻāĻŦāĻ‚ āĻ…ā§āϝāĻžāĻĒ āϧ⧀āϰ āĻĒā§āϰāϤāĻŋāĻ•ā§āϰāĻŋ⧟āĻž āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤",
"transcoding_temporal_aq": "āĻŸā§‡āĻŽā§āĻĒā§‹āϰāĻžāϞ AQ",
"transcoding_temporal_aq_description": "āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ NVENC-āĻāϰ āĻ•ā§āώ⧇āĻ¤ā§āϰ⧇ āĻĒā§āϰāϝ⧋āĻœā§āϝāĨ¤ āĻŸā§‡āĻŽā§āĻĒā§‹āϰāĻžāϞ āĻ…ā§āϝāĻžāĻĄāĻžāĻĒāϟāĻŋāĻ­ āϕ⧋āϝāĻŧāĻžāĻ¨ā§āϟāĻžāχāĻœā§‡āĻļāύ (Adaptive Quantization) āωāĻšā§āϚ-āĻŦāĻŋāĻ¸ā§āϤāĻžāϰāĻŋāϤ āĻ“ āĻ¸ā§āĻŦāĻ˛ā§āĻĒ-āĻ—āϤāĻŋāϰ āĻĻ⧃āĻļā§āϝ⧇āϰ āĻŽāĻžāύ āĻŦ⧃āĻĻā§āϧāĻŋ āĻ•āϰ⧇āĨ¤ āĻĒ⧁āϰ⧋āύ⧋ āĻĄāĻŋāĻ­āĻžāχāϏāϗ⧁āϞ⧋āϰ āϏāĻžāĻĨ⧇ āϏāĻžāĻŽāĻžā§āϜāĻ¸ā§āϝāĻĒā§‚āĻ°ā§āĻŖ āύāĻžāĻ“ āĻšāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤",
"transcoding_threads": "āĻĨā§āϰ⧇āĻĄ",
"transcoding_threads_description": "āωāĻšā§āϚ āĻŽāĻžāύ⧇ āĻāύāϕ⧋āĻĄāĻŋāĻ‚ āĻĻā§āϰ⧁āϤ āĻšā§Ÿ, āĻ•āĻŋāĻ¨ā§āϤ⧁ āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻ•āĻŽ āĻ•āĻžāϜ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤ CPU āϕ⧋āϰ⧇āϰ āĻŦ⧇āĻļāĻŋ āĻŽāĻžāύ āĻĻ⧇āĻ“ā§ŸāĻž āωāϚāĻŋāϤ āύ⧟āĨ¤ ā§Ļ āĻĻāĻŋāϞ⧇ āϏāĻ°ā§āĻŦāĻžāϧāĻŋāĻ• āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻšāĻŦ⧇āĨ¤",
"transcoding_tone_mapping": "āĻŸā§‹āύ-āĻŽā§āϝāĻžāĻĒāĻŋāĻ‚",
"transcoding_tone_mapping_description": "āĻāχāϚāĻĄāĻŋāφāϰ (HDR) āĻ­āĻŋāĻĄāĻŋāĻ“āϕ⧇ āĻāϏāĻĄāĻŋāφāϰ (SDR)-āĻ āϰ⧂āĻĒāĻžāĻ¨ā§āϤāϰ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āĻāϰ āĻŦāĻžāĻšā§āϝāĻŋāĻ• āϰ⧂āĻĒ āĻ…āĻ•ā§āώ⧁āĻŖā§āĻŖ āϰāĻžāĻ–āĻžāϰ āĻšā§‡āĻˇā§āϟāĻž āĻ•āϰāĻž āĻšāϝāĻŧāĨ¤ āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻ…ā§āϝāĻžāϞāĻ—āϰāĻŋāĻĻāĻŽ āϰāĻ™, āĻĄāĻŋāĻŸā§‡āχāϞ āĻāĻŦāĻ‚ āωāĻœā§āĻœā§āĻŦāϞāϤāĻžāϰ āϜāĻ¨ā§āϝ āĻ­āĻŋāĻ¨ā§āύ āĻ­āĻŋāĻ¨ā§āύ āϏāĻŽāĻ¨ā§āĻŦāϝāĻŧ āĻ•āϰ⧇āĨ¤ āĻšā§‡āĻŦāϞ āĻĄāĻŋāĻŸā§‡āχāϞ, āĻŽā§‹āĻŦāĻŋāϝāĻŧāĻžāϏ āϰāĻ™ āĻāĻŦāĻ‚ āϰāĻžāχāύāĻšāĻžāĻ°ā§āĻĄ āωāĻœā§āĻœā§āĻŦāϞāϤāĻž āĻ…āĻ•ā§āώ⧁āĻŖā§āĻŖ āϰāĻžāϖ⧇āĨ¤",
"transcoding_transcode_policy": "āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āύ⧀āϤāĻŋ",
"transcoding_transcode_policy_description": "āĻ•āĻ–āύ āĻāĻ•āϟāĻŋ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻ•āϰāĻž āĻšāĻŦ⧇ āϤāĻžāϰ āύ⧀āϤāĻŋāĻŽāĻžāϞāĻžāĨ¤ HDR āĻ­āĻŋāĻĄāĻŋāĻ“ āĻāĻŦāĻ‚ YUV 4:2:0 āĻŦā§āϝāϤ⧀āϤ āĻ…āĻ¨ā§āϝ āĻĒāĻŋāĻ•ā§āϏ⧇āϞ āĻĢāϰāĻŽā§āϝāĻžāĻŸā§‡āϰ āĻ­āĻŋāĻĄāĻŋāĻ“ āϏāĻ°ā§āĻŦāĻĻāĻž āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻ•āϰāĻž āĻšāĻŦ⧇ (āϝāĻĻāĻŋ āύāĻž āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄāĻŋāĻ‚ āĻŦāĻ¨ā§āϧ āĻ•āϰāĻž āĻĨāĻžāϕ⧇)āĨ¤",
"transcoding_two_pass_encoding": "āϟ⧁-āĻĒāĻžāϏ āĻāύāϕ⧋āĻĄāĻŋāĻ‚",
"transcoding_two_pass_encoding_setting_description": "āφāϰāĻ“ āωāĻ¨ā§āύāϤ āĻŽāĻžāύ⧇āϰ āĻāύāϕ⧋āĻĄā§‡āĻĄ āĻ­āĻŋāĻĄāĻŋāĻ“ āϤ⧈āϰāĻŋ āĻ•āϰāϤ⧇ āĻĻ⧁āχ āϧāĻžāĻĒ⧇ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻ•āϰ⧁āύāĨ¤ āϝāĻ–āύ āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āĻŦāĻŋāϟāϰ⧇āϟ āϏāĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰāĻž āĻšāϝāĻŧ (āϝāĻž H.264 āĻāĻŦāĻ‚ HEVC-āĻāϰ āϏāĻžāĻĨ⧇ āĻ•āĻžāϜ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āφāĻŦāĻļā§āϝāĻ•), āϤāĻ–āύ āĻāχ āĻŽā§‹āĻĄāϟāĻŋ āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āĻŦāĻŋāϟāϰ⧇āĻŸā§‡āϰ āωāĻĒāϰ āĻ­āĻŋāĻ¤ā§āϤāĻŋ āĻ•āϰ⧇ āĻāĻ•āϟāĻŋ āĻŦāĻŋāϟāϰ⧇āϟ āϰ⧇āĻžā§āϜ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻāĻŦāĻ‚ CRF āωāĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰ⧇āĨ¤ VP9-āĻāϰ āĻ•ā§āώ⧇āĻ¤ā§āϰ⧇, āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āĻŦāĻŋāϟāϰ⧇āϟ āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻĨāĻžāĻ•āϞ⧇āĻ“ CRF āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāĻž āϝ⧇āϤ⧇ āĻĒāĻžāϰ⧇āĨ¤",
"transcoding_video_codec": "āĻ­āĻŋāĻĄāĻŋāĻ“ āϕ⧋āĻĄā§‡āĻ•",
"transcoding_video_codec_description": "VP9 āωāĻšā§āϚ āĻ•āĻ°ā§āĻŽāĻĻāĻ•ā§āώāϤāĻž āϏāĻŽā§āĻĒāĻ¨ā§āύ āĻāĻŦāĻ‚ āĻ“āϝāĻŧ⧇āĻŦ⧇āϰ āϏāĻžāĻĨ⧇ āϏāĻžāĻŽāĻžā§āϜāĻ¸ā§āϝāĻĒā§‚āĻ°ā§āĻŖ, āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻ•āϰāϤ⧇ āĻŦ⧇āĻļāĻŋ āϏāĻŽāϝāĻŧ āϞāĻžāϗ⧇āĨ¤ HEVC-āĻāϰ āĻ•āĻ°ā§āĻŽāĻ•ā§āώāĻŽāϤāĻžāĻ“ āĻĒā§āϰāĻžāϝāĻŧ āĻāĻ•āχ āϰāĻ•āĻŽ, āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻāϰ āĻ“āϝāĻŧ⧇āĻŦ āϏāĻžāĻŽāĻžā§āϜāĻ¸ā§āϝāϤāĻž āĻ•āĻŽāĨ¤ H.264 āĻŦā§āϝāĻžāĻĒāĻ•āĻ­āĻžāĻŦ⧇ āϏāĻžāĻŽāĻžā§āϜāĻ¸ā§āϝāĻĒā§‚āĻ°ā§āĻŖ āĻāĻŦāĻ‚ āĻĻā§āϰ⧁āϤ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻ•āϰāĻž āϝāĻžāϝāĻŧ, āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻāϟāĻŋ āĻ…āύ⧇āĻ• āĻŦāĻĄāĻŧ āĻĢāĻžāχāϞ āϤ⧈āϰāĻŋ āĻ•āϰ⧇āĨ¤ AV1 āϏāĻŦāĻšā§‡āϝāĻŧ⧇ āĻ•āĻ°ā§āĻŽāĻĻāĻ•ā§āώ āϕ⧋āĻĄā§‡āĻ•, āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻĒ⧁āϰ⧋āύ⧋ āĻĄāĻŋāĻ­āĻžāχāϏāϗ⧁āϞ⧋āϤ⧇ āĻāϰ āϏāĻŽāĻ°ā§āĻĨāύ āύ⧇āχāĨ¤",
"trash_enabled_description": "āĻŸā§āĻ°ā§āϝāĻžāĻļ āĻĢāĻŋāϚāĻžāϰ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ",
"trash_number_of_days": "āĻĻāĻŋāύ⧇āϰ āϏāĻ‚āĻ–ā§āϝāĻž",
"trash_number_of_days_description": "āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āĻĨāĻžāĻ•āĻž āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋ āĻ¸ā§āĻĨāĻžā§Ÿā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻžāϰ āφāϗ⧇ āϰāĻžāĻ–āĻžāϰ āĻĻāĻŋāύ āϏāĻ‚āĻ–ā§āϝāĻž",
"trash_settings": "āĻŸā§āĻ°ā§āϝāĻžāĻļ āϏ⧇āϟāĻŋāĻ‚āϏ",
"trash_settings_description": "āĻŸā§āĻ°ā§āϝāĻžāĻļ āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ",
"unlink_all_oauth_accounts": "āϏāĻ•āϞ OAuth āĻ…ā§āϝāĻžāĻ•āĻžāωāĻ¨ā§āϟ āφāύāϞāĻŋāĻ™ā§āĻ• āĻ•āϰ⧁āύ",
"unlink_all_oauth_accounts_description": "āύāϤ⧁āύ āĻĒā§āϰ⧋āĻ­āĻžāχāĻĄāĻžāϰ⧇ āĻŽāĻžāχāĻ—ā§āϰ⧇āϟ āĻ•āϰāĻžāϰ āφāϗ⧇ āϏāĻŦ OAuth āĻ…ā§āϝāĻžāĻ•āĻžāωāĻ¨ā§āϟ āφāύāϞāĻŋāĻ™ā§āĻ• āĻ•āϰ⧁āύāĨ¤",
"unlink_all_oauth_accounts_prompt": "āφāĻĒāύāĻŋ āĻ•āĻŋ āϏāĻŦ OAuth āĻ…ā§āϝāĻžāĻ•āĻžāωāĻ¨ā§āϟ āφāύāϞāĻŋāĻ™ā§āĻ• āĻ•āϰāϤ⧇ āύāĻŋāĻļā§āϚāĻŋāϤ? āĻāϟāĻŋ āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ OAuth āφāχāĻĄāĻŋ āϰāĻŋāϏ⧇āϟ āĻ•āϰ⧇ āĻĻ⧇āĻŦ⧇ āĻāĻŦāĻ‚ āĻāϟāĻŋ āφāϰ āĻĒā§‚āĻ°ā§āĻŦāĻžāĻŦāĻ¸ā§āĻĨāĻžāϝāĻŧ āĻĢ⧇āϰāĻžāύ⧋ āϝāĻžāĻŦ⧇ āύāĻžāĨ¤",
"user_cleanup_job": "āχāωāϜāĻžāϰ āĻ•ā§āϞāĻŋāύāφāĻĒ",
"user_delete_delay": "<b>{user}</b>-āĻāϰ āĻ…ā§āϝāĻžāĻ•āĻžāωāĻ¨ā§āϟ āĻāĻŦāĻ‚ āĻ…ā§āϝāĻžāϏ⧇āϟ {delay, plural, one {# day} other {# days}} āĻĒāϰ āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻžāϰ āϜāĻ¨ā§āϝ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŋāϤ āĻšāĻŦ⧇āĨ¤",
"user_delete_delay_settings": "āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻžāϰ āϏāĻŽā§Ÿ āĻŦāĻŋāϞāĻŽā§āĻŦ",
"user_delete_delay_settings_description": "āĻ…ā§āϝāĻžāĻ•āĻžāωāĻ¨ā§āϟ āĻāĻŦāĻ‚ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻžāϰ āĻĒāϰ āĻ•āϤ āĻĻāĻŋāύ⧇āϰ āĻŽāĻ§ā§āϝ⧇ āĻ¸ā§āĻĨāĻžā§Ÿā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāĻŦ⧇āĨ¤ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻžāϰ āĻ•āĻžāϜ āĻŽāĻ§ā§āϝāϰāĻžāϤ⧇ āϚāĻžāϞāĻžāύ⧋ āĻšā§Ÿ āĻāĻŦāĻ‚ āĻĻ⧇āĻ–āĻž āĻšā§Ÿ āϕ⧋āύ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āĻ¸ā§āĻĨāĻžā§Ÿā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻžāϰ āϜāĻ¨ā§āϝ āĻĒā§āϰāĻ¸ā§āϤ⧁āϤāĨ¤ āĻāχ āϏ⧇āϟāĻŋāĻ‚ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāϞ⧇ āĻĒāϰāĻŦāĻ°ā§āϤ⧀ āĻāĻ•ā§āϏāĻŋāĻ•āĻŋāωāĻļāύ⧇āϰ āϏāĻŽā§Ÿ āϤāĻž āĻĒā§āϰāϝ⧋āĻœā§āϝ āĻšāĻŦ⧇āĨ¤",
"user_delete_immediately": "<b>{user}</b>-āĻāϰ āĻ…ā§āϝāĻžāĻ•āĻžāωāĻ¨ā§āϟ āĻāĻŦāĻ‚ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻžāϰ āϜāĻ¨ā§āϝ <b>immediately</b> āĻ•āĻŋāωāϤ⧇ āĻ…āĻ¨ā§āϤāĻ°ā§āϭ⧁āĻ•ā§āϤ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤",
"user_delete_immediately_checkbox": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āĻ“ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻ¤ā§ŽāĻ•ā§āώāĻŖāĻžā§Ž āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻžāϰ āϜāĻ¨ā§āϝ āĻ•āĻŋāω",
"user_details": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϤāĻĨā§āϝ",
"user_management": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āĻŽā§āϝāĻžāύ⧇āϜāĻŽā§‡āĻ¨ā§āϟ",
"user_password_has_been_reset": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ āϰāĻŋāϏ⧇āϟ āĻ•āϰāĻž āĻšā§Ÿā§‡āϛ⧇:",
"user_password_reset_description": "āĻĻāϝāĻŧāĻž āĻ•āϰ⧇ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āϜāĻ¨ā§āϝ āϏāĻžāĻŽāϝāĻŧāĻŋāĻ• āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĻāĻŋāύ āĻāĻŦāĻ‚ āϜāĻžāύāĻŋā§Ÿā§‡ āĻĻāĻŋāύ āϝ⧇ āϤāĻžāϰāĻž āĻĒāϰāĻŦāĻ°ā§āϤ⧀ āϞāĻ—āχāύ⧇ āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻŦ⧇āύāĨ¤",
"user_restore_description": "<b>{user}</b> āĻāϰ āĻ…ā§āϝāĻžāĻ•āĻžāωāĻ¨ā§āϟ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤",
"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": "āĻ­āĻžāĻ°ā§āϏāύ āϝāĻžāϚāĻžāχ",
"version_check_settings_description": "āύāϤ⧁āύ āĻ­āĻžāĻ°ā§āϏāύ⧇āϰ āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āϚāĻžāϞ⧁/āĻŦāĻ¨ā§āϧ āĻ•āϰ⧁āύ",
"video_conversion_job": "āĻ­āĻŋāĻĄāĻŋāĻ“ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻ•āϰ⧁āύ",
"video_conversion_job_description": "āĻŦā§āϰāĻžāωāϜāĻžāϰ āĻāĻŦāĻ‚ āĻĄāĻŋāĻ­āĻžāχāϏ⧇ āφāϰāĻ“ āĻ­āĻžāϞ⧋āĻ­āĻžāĻŦ⧇ āϚāϞāĻžāϰ āϜāĻ¨ā§āϝ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻ•āϰ⧁āύ"
}, },
"admin_email": "āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ⧇āϰ āχāĻŽā§‡āχāϞ",
"admin_password": "āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ⧇āϰ āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ",
"administration": "āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ",
"advanced": "āĻ…ā§āϝāĻžāĻĄāĻ­āĻžāĻ¨ā§āϏāĻĄ",
"age_months": "āĻŦ⧟āϏ {months, plural, one {# month} other {# months}}",
"age_year_months": "āĻŦ⧟āϏ ā§§ āĻŦāĻ›āϰ, {months, plural, one {# month} other {# months}}",
"album_added": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϝ⧁āĻ•ā§āϤ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇",
"album_added_notification_setting_description": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧁āĻ•ā§āϤ āĻšāϞ⧇ āχāĻŽā§‡āχāϞ āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āĻĒāĻžāύ",
"album_cover_updated": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āĻ•āĻ­āĻžāϰ āφāĻĒāĻĄā§‡āϟ āĻšāϝāĻŧ⧇āϛ⧇",
"album_delete_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āϏāĻ¤ā§āϝāĻŋāχ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ {album} āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ?",
"album_delete_confirmation_description": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽāϟāĻŋ āĻļā§‡ā§ŸāĻžāϰ āĻ•āϰāĻž āĻĨāĻžāĻ•āϞ⧇āĻ“ āĻ…āĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰāĻž āφāϰ āĻāϟāĻŋ āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϏ āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŦ⧇āύ āύāĻžāĨ¤",
"album_info_updated": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āϤāĻĨā§āϝ āφāĻĒāĻĄā§‡āϟ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇",
"album_leave": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻĨ⧇āϕ⧇ āĻŦ⧇āϰāĻŋā§Ÿā§‡ āϝ⧇āϤ⧇ āϚāĻžāύ ?",
"album_leave_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ {album} āϛ⧇āĻĄāĻŧ⧇ āϝ⧇āϤ⧇ āϚāĻžāύ?",
"album_name": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āύāĻžāĻŽ",
"album_options": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āĻ…āĻĒāĻļāύāϏāĻŽā§‚āĻš",
"album_remove_user": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϏāϰāĻžāϤ⧇ āϚāĻžāύ?",
"album_remove_user_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ {user}-āϕ⧇ āϏāϰāĻžāϤ⧇ āϚāĻžāύ?",
"album_share_no_users": "āĻāχ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽāϟāĻŋ āϏāĻŦ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āϏāĻ™ā§āϗ⧇ āĻļā§‡ā§ŸāĻžāϰ āĻ•āϰāĻž āĻšā§Ÿā§‡āϛ⧇, āĻŦāĻž āĻļā§‡ā§ŸāĻžāϰ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āϕ⧋āύ⧋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āύ⧇āχāĨ¤",
"album_updated": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āφāĻĒāĻĄā§‡āϟ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇",
"album_updated_setting_description": "āύāϤ⧁āύ āĻ…ā§āϝāĻžāϏ⧇āϟ āϝ⧁āĻ•ā§āϤ āĻšāϞ⧇ āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āϜāĻ¨ā§āϝ āχāĻŽā§‡āχāϞ āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āĻĒāĻžāύ",
"album_user_left": "āĻŦāĻžāĻŽ {album}",
"album_user_removed": "{user} āϕ⧇ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇",
"album_with_link_access": "āϞāĻŋāĻ™ā§āĻ• āĻĨāĻžāĻ•āĻž āϝ⧇ āϕ⧇āω āĻāχ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āĻ›āĻŦāĻŋ āĻ“ āĻŽāĻžāύ⧁āώāϜāύāϕ⧇ āĻĻ⧇āĻ–āϤ⧇ āĻĒāĻžāϰāĻŦ⧇āĨ¤",
"albums": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽāϏāĻŽā§‚āĻš",
"all": "āϏāĻŦ",
"all_albums": "āϏāĻ•āϞ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽāϏāĻŽā§‚āĻš",
"all_people": "āϏāĻŦ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀",
"all_videos": "āϏāĻŦ āĻ­āĻŋāĻĄāĻŋāĻ“",
"allow_dark_mode": "āĻĄāĻžāĻ°ā§āĻ• āĻŽā§‹āĻĄ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ",
"allow_edits": "āĻāĻĄāĻŋāĻŸā§‡āϰ āĻ…āύ⧁āĻŽāϤāĻŋ āĻĻāĻŋāύ",
"allow_public_user_to_download": "āϏāĻžāϧāĻžāϰāĻŖ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āĻĄāĻžāωāύāϞ⧋āĻĄ āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŦ⧇",
"allow_public_user_to_upload": "āϏāĻžāϧāĻžāϰāĻŖ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŦ⧇",
"anti_clockwise": "āĻŦāĻŋāĻĒāϰ⧀āϤ āĻĻāĻŋāĻ•",
"api_key": "API āϕ⧀",
"api_key_description": "āĻāχ āĻŽāĻžāύ āĻāĻ•āĻŦāĻžāϰāχ āĻĻ⧇āĻ–āĻžāύ⧋ āĻšāĻŦ⧇āĨ¤ āωāχāĻ¨ā§āĻĄā§‹ āĻŦāĻ¨ā§āϧ āĻ•āϰāĻžāϰ āφāϗ⧇ āĻ…āĻŦāĻļā§āϝāχ āĻāϟāĻŋ āĻ•āĻĒāĻŋ āĻ•āϰ⧁āύāĨ¤",
"api_key_empty": "API āϕ⧀-āĻāϰ āύāĻžāĻŽ āĻ–āĻžāϞāĻŋ āϰāĻžāĻ–āĻž āϝāĻžāĻŦ⧇ āύāĻž",
"api_keys": "API āϕ⧀ āϏāĻŽā§‚āĻš",
"app_settings": "āĻ…ā§āϝāĻžāĻĒ āϏ⧇āϟāĻŋāĻ‚āϏ",
"appears_in": "v1.106.4 āĻĨ⧇āϕ⧇, āĻ…ā§āϝāĻžāϏ⧇āϟ āϏāĻžāχāĻĄāĻŦāĻžāϰ⧇ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻšā§Ÿ ‘[albums]-āĻ āωāĻĒāĻ¸ā§āĻĨāĻŋāĻ¤â€™ āĻŦā§‹āĻāĻžāϤ⧇",
"archive": "āφāĻ°ā§āĻ•āĻžāχāĻ­",
"archive_or_unarchive_photo": "āĻĢāĻŸā§‹ āφāĻ°ā§āĻ•āĻžāχāĻ­ āĻ…āĻĨāĻŦāĻž āφāύāφāĻ°ā§āĻ•āĻžāχāĻ­ āĻ•āϰ⧁āύ",
"archive_size": "āφāĻ°ā§āĻ•āĻžāχāĻ­ āϏāĻžāχāϜ",
"archive_size_description": "āĻĄāĻžāωāύāϞ⧋āĻĄā§‡āϰ āφāĻ°ā§āĻ•āĻžāχāĻ­ āϏāĻžāχāϜ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧁āύ (GiB)",
"are_these_the_same_person": "āĻāϰāĻž āĻ•āĻŋ āĻāĻ•āχ āĻŦā§āϝāĻ•ā§āϤāĻŋ?",
"are_you_sure_to_do_this": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āĻāϟāĻŋ āĻ•āϰāϤ⧇ āϚāĻžāύ?",
"asset_added_to_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧁āĻ•ā§āϤ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇",
"asset_adding_to_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧁āĻ•ā§āϤ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇â€Ļ",
"asset_description_updated": "āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡āϰ āĻŦāĻŋāĻŦāϰāĻŖ āφāĻĒāĻĄā§‡āϟ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇",
"asset_filename_is_offline": "{filename} āĻ…ā§āϝāĻžāϏ⧇āϟāϟāĻŋ āĻŦāĻ°ā§āϤāĻŽāĻžāύ⧇ āĻ…āĻĢāϞāĻžāχāύ",
"asset_has_unassigned_faces": "āĻ…ā§āϝāĻžāϏ⧇āϟāϟāĻŋāϰ āĻ•āĻŋāϛ⧁ āĻŽā§āĻ– āĻ…āύāĻŋāĻ°ā§āϧāĻžāϰāĻŋāϤ āĻĢ⧇āϏ āϰāϝāĻŧ⧇āϛ⧇",
"asset_hashing": "āĻšā§āϝāĻžāĻļāĻŋāĻ‚ āϚāϞāϛ⧇â€Ļ",
"asset_offline": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻŦāĻ°ā§āϤāĻŽāĻžāύ⧇ āĻ…āĻĢāϞāĻžāχāύ",
"asset_offline_description": "āĻāχ āĻāĻ•ā§āϏāϟāĻžāĻ°ā§āύāĻžāϞ āĻ…ā§āϝāĻžāϏ⧇āϟāϟāĻŋ āĻāĻ–āύ āĻĄāĻŋāĻ¸ā§āϕ⧇ āύ⧇āχāĨ¤ āϏāĻšāĻžāϝāĻŧāϤāĻžāϰ āϜāĻ¨ā§āϝ Immich āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύāĻŋāĻ¸ā§āĻŸā§āϰ⧇āϟāϰ⧇āϰ āϏāĻžāĻĨ⧇ āϝ⧋āĻ—āĻžāϝ⧋āĻ— āĻ•āϰ⧁āύāĨ¤",
"asset_skipped": "āĻāĻĄāĻŧāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇",
"asset_skipped_in_trash": "āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇",
"asset_uploaded": "āφāĻĒāϞ⧋āĻĄ āϏāĻŽā§āĻĒāĻ¨ā§āύ",
"asset_uploading": "āφāĻĒāϞ⧋āĻĄ āϚāϞāϛ⧇â€Ļ",
"assets": "āĻ…ā§āϝāĻžāϏ⧇āϟāϏāĻŽā§‚āĻš",
"assets_added_to_album_count": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ {count, plural, one {# asset} other {# assets}} āϝ⧁āĻ•ā§āϤ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇",
"assets_moved_to_trash_count": "{count, plural, one {# asset} other {# assets}} āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇",
"assets_permanently_deleted_count": "{count, plural, one {# asset} other {# assets}} āĻ¸ā§āĻĨāĻžā§Ÿā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāϝāĻŧ⧇āϛ⧇",
"assets_removed_count": "{count, plural, one {# asset} other {# assets}} āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇",
"assets_restore_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āϏāĻ¤ā§āϝāĻŋāχ āφāĻĒāύāĻžāϰ āϏāĻŦ āĻŸā§āĻ°ā§āϝāĻžāĻļ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāϤ⧇ āϚāĻžāύ? āĻāϟāĻŋ āĻĒā§‚āĻ°ā§āĻŦāĻžāĻŦāĻ¸ā§āĻĨāĻžāϝāĻŧ āĻĢāĻŋāϰāĻžāύ⧋ āϝāĻžāĻŦ⧇ āύāĻžāĨ¤ āϤāĻŦ⧇ āĻ…āĻĢāϞāĻžāχāύ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻāχāĻ­āĻžāĻŦ⧇ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻšāĻŦ⧇ āύāĻžāĨ¤",
"assets_restored_count": "{count, plural, one {# asset} other {# assets}} āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇",
"assets_trashed_count": "{count, plural, one {# asset} other {# assets}} āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āĻĒāĻžāĻ āĻžāύ⧋ āĻšā§Ÿā§‡āϛ⧇",
"assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}} āφāϗ⧇āχ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧁āĻ•ā§āϤ āĻ›āĻŋāϞ",
"authorized_devices": "āĻ…āύ⧁āĻŽā§‹āĻĻāĻŋāϤ āĻĄāĻŋāĻ­āĻžāχāϏ",
"back": "āĻĢāĻŋāϰ⧇ āϝāĻžāύ",
"back_close_deselect": "āĻĢāĻŋāϰ⧇ āϝāĻžāύ, āĻŦāĻ¨ā§āϧ āĻ•āϰ⧁āύ āĻŦāĻž āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻŦāĻžāϤāĻŋāϞ āĻ•āϰ⧁āύ",
"backward": "āĻĒāĻŋāĻ›āύ⧇",
"birthdate_saved": "āϜāĻ¨ā§āĻŽ āϤāĻžāϰāĻŋāĻ– āϏāĻ‚āϰāĻ•ā§āώāĻŖ āϏāĻŽā§āĻĒāĻ¨ā§āύ",
"birthdate_set_description": "āĻāĻ•āϟāĻŋ āĻ›āĻŦāĻŋāϰ āϏāĻŽā§Ÿā§‡ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ āĻŦ⧟āϏ āĻ—āĻŖāύāĻžāϰ āϜāĻ¨ā§āϝ āϜāĻ¨ā§āĻŽ āϤāĻžāϰāĻŋāĻ– āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāĻž āĻšā§ŸāĨ¤",
"blurred_background": "āĻŦā§āϞāĻžāϰāĻĄ āĻŦā§āϝāĻžāĻ•āĻ—ā§āϰāĻžāωāĻ¨ā§āĻĄ",
"bugs_and_feature_requests": "āĻŦāĻžāĻ— āĻ“ āĻĢāĻŋāϚāĻžāϰ āϰāĻŋāĻ•ā§‹ā§Ÿā§‡āĻ¸ā§āϟ",
"build": "āĻŦāĻŋāĻ˛ā§āĻĄ",
"build_image": "āĻŦāĻŋāĻ˛ā§āĻĄ āχāĻŽā§‡āϜ",
"bulk_delete_duplicates_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āϏāĻ¤ā§āϝāĻŋāχ {count, plural, one {# duplicate asset} other {# duplicate assets}} āĻāĻ•āϏāĻžāĻĨ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ? āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻ—ā§āϰ⧁āĻĒ⧇āϰ āϏāĻŦāĻšā§‡ā§Ÿā§‡ āĻŦ⧜ āĻ…ā§āϝāĻžāϏ⧇āϟ āϰāĻžāĻ–āĻž āĻšāĻŦ⧇, āĻŦāĻžāĻ•āĻŋāϗ⧁āϞ⧋ āĻ¸ā§āĻĨāĻžā§Ÿā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āϝāĻžāĻŦ⧇āĨ¤ āĻāϟāĻŋ āĻĒā§‚āĻ°ā§āĻŦāĻžāĻŦāĻ¸ā§āĻĨāĻžāϝāĻŧ āĻĢāĻŋāϰāĻžāύ⧋ āϝāĻžāĻŦ⧇ āύāĻž!",
"bulk_keep_duplicates_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āϏāĻ¤ā§āϝāĻŋāχ {count, plural, one {# duplicate asset} other {# duplicate assets}} āϰāĻžāĻ–āϤ⧇ āϚāĻžāύ? āϏāĻŦ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āĻ—ā§āϰ⧁āĻĒ āĻ āĻŋāĻ• āĻ•āϰāĻž āĻšāĻŦ⧇, āϕ⧋āύ⧋ āĻ•āĻŋāϛ⧁ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāĻŦ⧇ āύāĻžāĨ¤",
"bulk_trash_duplicates_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āϏāĻ¤ā§āϝāĻŋāχ {count, plural, one {# duplicate asset} other {# duplicate assets}} āĻāĻ•āϏāĻžāĻĨ⧇ āĻŸā§āĻ°ā§āϝāĻžāĻļ āĻ•āϰāϤ⧇ āϚāĻžāύ? āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻ—ā§āϰ⧁āĻĒ⧇āϰ āϏāĻŦāĻšā§‡ā§Ÿā§‡ āĻŦ⧜ āĻ…ā§āϝāĻžāϏ⧇āϟ āϰāĻžāĻ–āĻž āĻšāĻŦ⧇, āĻŦāĻžāĻ•āĻŋāϗ⧁āϞ⧋ āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āϝāĻžāĻŦ⧇āĨ¤",
"buy": "Immich āĻ•ā§āϰ⧟ āĻ•āϰ⧁āύ",
"camera": "āĻ•ā§āϝāĻžāĻŽā§‡āϰāĻž",
"camera_brand": "āĻ•ā§āϝāĻžāĻŽā§‡āϰāĻž āĻŦā§āĻ°ā§āϝāĻžāĻ¨ā§āĻĄ",
"camera_model": "āĻ•ā§āϝāĻžāĻŽā§‡āϰāĻž āĻŽāĻĄā§‡āϞ",
"cancel": "āĻŦāĻžāϤāĻŋāϞ",
"cancel_search": "āϏāĻžāĻ°ā§āϚ āĻŦāĻ¨ā§āϧ āĻ•āϰ⧁āύ",
"cannot_merge_people": "āĻŦā§āϝāĻ•ā§āϤāĻŋāĻĻ⧇āϰ āĻāĻ•āĻ¤ā§āϰ āĻ•āϰāĻž āϏāĻŽā§āĻ­āĻŦ āύāϝāĻŧ",
"cannot_undo_this_action": "āĻāχ āĻ•āĻžāϜ āĻĒā§‚āĻ°ā§āĻŦāĻžāĻŦāĻ¸ā§āĻĨāĻžāϝāĻŧ āĻĢ⧇āϰāĻžāύ⧋ āϝāĻžāĻŦ⧇ āύāĻž!",
"cannot_update_the_description": "āĻŦāĻŋāĻŦāϰāĻŖ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āϏāĻŽā§āĻ­āĻŦ āύāϝāĻŧ",
"change_date": "āϤāĻžāϰāĻŋāĻ– āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ",
"change_expiration_time": "āĻŽā§‡āϝāĻŧāĻžāĻĻ āĻļ⧇āώ⧇āϰ āϏāĻŽāϝāĻŧ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ",
"change_location": "āϞ⧋āϕ⧇āĻļāύ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ",
"change_name": "āύāĻžāĻŽ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ",
"change_name_successfully": "āύāĻžāĻŽ āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻšāϝāĻŧ⧇āϛ⧇",
"change_password": "āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ",
"change_password_description": "āφāĻĒāύāĻŋ āĻšāϝāĻŧāϤ⧋ āĻĒā§āϰāĻĨāĻŽāĻŦāĻžāϰ āϞāĻ—āχāύ āĻ•āϰāϛ⧇āύ āĻŦāĻž āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ⧇āϰ āĻ…āύ⧁āϰ⧋āϧ āĻ•āϰ⧇āϛ⧇āύāĨ¤ āύāĻŋāĻšā§‡ āύāϤ⧁āύ āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĻāĻŋāύāĨ¤",
"change_your_password": "āφāĻĒāύāĻžāϰ āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ",
"changed_visibility_successfully": "āĻ­āĻŋāϏāĻŋāĻŦāĻŋāϞāĻŋāϟāĻŋ āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻšāϝāĻŧ⧇āϛ⧇",
"check_logs": "āϞāĻ— āĻĻ⧇āϖ⧁āύ",
"choose_matching_people_to_merge": "āĻāĻ•āĻ¤ā§āϰ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āĻŽāĻŋāϞ āĻĨāĻžāĻ•āĻž āĻŦā§āϝāĻ•ā§āϤāĻŋāĻĻ⧇āϰ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ",
"city": "āĻļāĻšāϰ",
"clear": "āĻŽā§āϛ⧁āύ",
"clear_all": "āϏāĻŦ āĻŽā§āϛ⧁āύ",
"clear_all_recent_searches": "āϏāĻžāĻŽā§āĻĒā§āϰāϤāĻŋāĻ• āϏāĻŦ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻĒāϰāĻŋāĻˇā§āĻ•āĻžāϰ āĻ•āϰ⧁āύ",
"clear_message": "āĻŽā§‡āϏ⧇āϜ āĻĒāϰāĻŋāĻˇā§āĻ•āĻžāϰ āĻ•āϰ⧁āύ",
"clear_value": "āĻ­ā§āϝāĻžāϞ⧁ āĻŽā§āϛ⧁āύ",
"clockwise": "āϘ⧜āĻŋāϰ āĻ•āĻžāρāϟāĻžāϰ āĻĻāĻŋāϕ⧇",
"close": "āĻŦāĻ¨ā§āϧ",
"collapse": "āϏāĻ‚āϕ⧁āϚāĻŋāϤ āĻ•āϰ⧁āύ",
"collapse_all": "āϏāĻŦ āϏāĻ‚āϕ⧁āϚāĻŋāϤ",
"color": "āϰāĻ‚",
"color_theme": "āĻ•āĻžāϞāĻžāϰ āĻĨāĻŋāĻŽ",
"comment_deleted": "āĻŽāĻ¨ā§āϤāĻŦā§āϝ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāϝāĻŧ⧇āϛ⧇",
"comment_options": "āĻŽāĻ¨ā§āϤāĻŦā§āϝ āĻ…āĻĒāĻļāύ",
"comments_and_likes": "āĻŽāĻ¨ā§āϤāĻŦā§āϝ āĻ“ āϞāĻžāχāĻ•",
"comments_are_disabled": "āĻŽāĻ¨ā§āϤāĻŦā§āϝ āĻŦāĻ¨ā§āϧ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇",
"confirm": "āύāĻŋāĻļā§āϚāĻŋāϤ",
"confirm_admin_password": "āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĒ⧁āύāϰāĻžā§Ÿ āϞāĻŋāϖ⧁āύ",
"confirm_delete_shared_link": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āĻāχ āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āϞāĻŋāĻ™ā§āĻ•āϟāĻŋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ?",
"confirm_keep_this_delete_others": "āĻ¸ā§āĻŸā§āϝāĻžāϕ⧇āϰ āĻāχ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻ›āĻžā§œāĻž āϏāĻŦ āĻ…āĻ¨ā§āϝāĻžāĻ¨ā§āϝ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻŽā§āϛ⧇ āϝāĻžāĻŦ⧇āĨ¤ āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āϚāĻžāϞāĻŋāϝāĻŧ⧇ āϝ⧇āϤ⧇ āϚāĻžāύ?",
"confirm_password": "āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĒ⧁āύāϰāĻžā§Ÿ āϞāĻŋāϖ⧁āύ",
"contain": "āĻŽāĻžāĻĒāĻŽāϤ",
"context": "āĻĒā§āϰāϏāĻ™ā§āĻ—",
"continue": "āĻāĻ—āĻŋā§Ÿā§‡ āϝāĻžāύ",
"copied_image_to_clipboard": "āĻ›āĻŦāĻŋ āĻ•ā§āϞāĻŋāĻĒāĻŦā§‹āĻ°ā§āĻĄā§‡ āĻ•āĻĒāĻŋ āĻšāϝāĻŧ⧇āϛ⧇āĨ¤",
"copied_to_clipboard": "āĻ•ā§āϞāĻŋāĻĒāĻŦā§‹āĻ°ā§āĻĄā§‡ āĻ•āĻĒāĻŋ āĻšāϝāĻŧ⧇āϛ⧇!",
"copy_error": "Error-āϟāĻŋ āĻ•āĻĒāĻŋ āĻ•āϰ⧁āύ",
"copy_file_path": "āĻĢāĻžāχāϞ āĻĒāĻžāĻĨ āĻ•āĻĒāĻŋ",
"copy_image": "āĻ›āĻŦāĻŋ āĻ•āĻĒāĻŋ",
"copy_link": "āϞāĻŋāĻ™ā§āĻ• āĻ•āĻĒāĻŋ",
"copy_link_to_clipboard": "āĻ•ā§āϞāĻŋāĻĒāĻŦā§‹āĻ°ā§āĻĄā§‡ āϞāĻŋāĻ™ā§āĻ• āĻ•āĻĒāĻŋ āĻ•āϰ⧁āύ",
"copy_password": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻ•āĻĒāĻŋ āĻ•āϰ⧁āύ",
"copy_to_clipboard": "āĻ•ā§āϞāĻŋāĻĒāĻŦā§‹āĻ°ā§āĻĄā§‡ āĻ•āĻĒāĻŋ āĻ•āϰ⧁āύ",
"country": "āĻĻ⧇āĻļ",
"cover": "āϏāĻŽā§āĻĒā§‚āĻ°ā§āĻŖāĻ­āĻžāĻŦ⧇",
"covers": "āĻ•āĻ­āĻžāϰāϏ",
"create": "āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ",
"create_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϤ⧈āϰāĻŋ",
"create_library": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āϤ⧈āϰāĻŋ",
"create_link": "āϞāĻŋāĻ™ā§āĻ• āϤ⧈āϰāĻŋ",
"create_link_to_share": "āĻļ⧇āϝāĻŧāĻžāϰ āϞāĻŋāĻ™ā§āĻ• āϤ⧈āϰāĻŋ",
"create_link_to_share_description": "āϞāĻŋāĻ™ā§āϕ⧇āϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āϏāĻŦāĻžāχ āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻ›āĻŦāĻŋ āĻĻ⧇āĻ–āϤ⧇ āĻĒāĻžāϰāĻŦ⧇",
"create_new_person": "āύāϤ⧁āύ āĻŦā§āϝāĻ•ā§āϤāĻŋ āϝ⧋āĻ— āĻ•āϰ⧁āύ",
"create_new_person_hint": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻ…ā§āϝāĻžāϏ⧇āϟ āύāϤ⧁āύ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ āϏāĻ™ā§āϗ⧇ āϝ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ",
"create_new_user": "āύāϤ⧁āύ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϝ⧋āĻ— āĻ•āϰ⧁āύ",
"create_tag": "āĻŸā§āϝāĻžāĻ— āϤ⧈āϰāĻŋ",
"create_tag_description": "āύāϤ⧁āύ āĻŸā§āϝāĻžāĻ— āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύāĨ¤ āύ⧇āĻ¸ā§āĻŸā§‡āĻĄ āĻŸā§āϝāĻžāϗ⧇āϰ āĻ•ā§āώ⧇āĻ¤ā§āϰ⧇ āϏāĻŽā§āĻĒā§‚āĻ°ā§āĻŖ āĻĒāĻžāĻĨ - āĻĢāϰāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻ¸ā§āĻ˛ā§āϝāĻžāĻļāϏāĻš āĻĻāĻŋāύāĨ¤",
"create_user": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϝ⧋āĻ— āĻ•āϰ⧁āύ",
"created": "āϝ⧋āĻ— āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇",
"current_device": "āϚāϞāϤāĻŋ āĻĄāĻŋāĻ­āĻžāχāϏ",
"custom_locale": "āĻ•āĻžāĻ¸ā§āϟāĻŽ āϞ⧋āϕ⧇āϞ",
"custom_locale_description": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻ­āĻžāώāĻž āĻāĻŦāĻ‚ āĻ…āĻžā§āϚāϞ⧇āϰ āĻ­āĻŋāĻ¤ā§āϤāĻŋāϤ⧇ āϤāĻžāϰāĻŋāĻ–, āϏāĻŽā§Ÿ āĻāĻŦāĻ‚ āϏāĻ‚āĻ–ā§āϝāĻž āĻĢāϰāĻŽā§āϝāĻžāϟ āĻ•āϰ⧁āύ",
"dark": "āĻĄāĻžāĻ°ā§āĻ•",
"date_after": "āĻāϰ āĻĒāϰ⧇āϰ āϤāĻžāϰāĻŋāĻ–",
"date_and_time": "āϤāĻžāϰāĻŋāĻ– āĻāĻŦāĻ‚ āϏāĻŽā§Ÿ",
"date_before": "āĻāϰ āφāϗ⧇āϰ āϤāĻžāϰāĻŋāĻ–",
"date_of_birth_saved": "āϜāĻ¨ā§āĻŽ āϤāĻžāϰāĻŋāĻ– āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āϏāĻ‚āϰāĻ•ā§āώāĻŖ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇",
"delete": "āĻŽā§āϛ⧁āύ",
"delete_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻŽā§āϛ⧁āύ",
"delete_api_key_prompt": "āφāĻĒāύāĻŋ āĻ•āĻŋ āϏāĻ¤ā§āϝāĻŋāχ āĻāχ API key āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ?",
"delete_duplicates_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āϏāĻ¤ā§āϝāĻŋāχ āĻāχ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟāϗ⧁āϞ⧋ āĻ¸ā§āĻĨāĻžā§Ÿā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āĻ›āϤ⧇ āϚāĻžāύ?",
"delete_key": "key āĻŽā§āϛ⧁āύ",
"delete_library": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻŽā§āϛ⧁āύ",
"delete_link": "āϞāĻŋāĻ™ā§āĻ• āĻŽā§āϛ⧁āύ",
"delete_others": "āĻŦāĻžāĻ•āĻŋāϗ⧁āϞ⧋ āĻŽā§āϛ⧁āύ",
"delete_shared_link": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āϞāĻŋāĻ™ā§āĻ• āĻŽā§āϛ⧁āύ",
"delete_tag": "āĻŸā§āϝāĻžāĻ— āĻŽā§āϛ⧁āύ",
"delete_tag_confirmation_prompt": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤāĻ­āĻžāĻŦ⧇ {tagName} āĻŸā§āϝāĻžāĻ—āϟāĻŋ āĻŽā§āĻ›āϤ⧇ āϚāĻžāύ?",
"delete_user": "āχāωāϜāĻžāϰ āĻŽā§āϛ⧁āύ",
"deleted_shared_link": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āϞāĻŋāĻ™ā§āĻ•āϟāĻŋ āĻŽā§āϛ⧁āύ",
"deletes_missing_assets": "āĻĄāĻŋāĻ¸ā§āĻ• āĻĨ⧇āϕ⧇ āĻšāĻžāϰāĻžāύ⧋ āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋ āĻŽā§āϛ⧇",
"description": "āĻŦāĻŋāĻŦāϰāύ",
"details": "āĻŦāĻŋāĻ¸ā§āϤāĻžāϰāĻŋāϤ",
"direction": "āĻĻāĻŋāĻ•āύāĻŋāĻ°ā§āĻĻ⧇āĻļāύāĻž",
"disabled": "āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ",
"disallow_edits": "āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰāĻžāϰ āĻ…āύ⧁āĻŽāϤāĻŋ āĻĻ⧇āĻŦ⧇āύ āύāĻž",
"discord": "āĻĄāĻŋāϏāĻ•āĻ°ā§āĻĄ",
"discover": "āĻĄāĻŋāϏāĻ•āĻ­āĻžāϰ",
"dismiss_all_errors": "āϏāĻŦ āĻ¤ā§āϰ⧁āϟāĻŋ āĻŦāĻžāϤāĻŋāϞ āĻ•āϰ⧁āύ",
"dismiss_error": "āĻ¤ā§āϰ⧁āϟāĻŋ āĻŦāĻžāϤāĻŋāϞ āĻ•āϰ⧁āύ",
"display_options": "āĻĄāĻŋāϏāĻĒā§āϞ⧇ āĻ…āĻĒāĻļāύ",
"display_order": "āĻĄāĻŋāϏāĻĒā§āϞ⧇ āĻ…āĻ°ā§āĻĄāĻžāϰ",
"display_original_photos": "āĻ…āϰāĻŋāϜāĻŋāύāĻžāϞ āĻ›āĻŦāĻŋ āĻĻ⧇āĻ–āĻžāύ",
"display_original_photos_setting_description": "āĻ…āϰāĻŋāϜāĻŋāύāĻžāϞ āĻ…ā§āϝāĻžāϏ⧇āϟāϟāĻŋ āĻ“āϝāĻŧ⧇āĻŦ-āϏāĻžāĻŽāĻžā§āϜāĻ¸ā§āϝāĻĒā§‚āĻ°ā§āĻŖ (web-compatible) āĻšāϞ⧇ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĻ⧇āĻ–āĻžāϰ āϏāĻŽāϝāĻŧ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ⧇āϰ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤ⧇ āĻŽā§‚āϞ āĻĢāĻŸā§‹āϟāĻŋ āĻĒā§āϰāĻĻāĻ°ā§āĻļāύ āĻ•āϰāϤ⧇ āĻ…āĻ—ā§āϰāĻžāϧāĻŋāĻ•āĻžāϰ āĻĻāĻŋāύāĨ¤ āĻāϰ āĻĢāϞ⧇ āĻĢāĻŸā§‹ āĻĒā§āϰāĻĻāĻ°ā§āĻļāύ⧇āϰ āĻ—āϤāĻŋ āĻ•āĻŋāϛ⧁āϟāĻž āϧ⧀āϰ āĻšāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤",
"do_not_show_again": "āĻāχ āĻŽā§‡āϏ⧇āϜāϟāĻŋ āφāϰ āĻĻ⧇āĻ–āĻžāĻŦ⧇āύ āύāĻž",
"documentation": "āϏāĻšāĻžā§ŸāĻ• āύāĻŋāĻ°ā§āĻĻ⧇āĻļāĻŋāĻ•āĻž",
"done": "āϏāĻŽā§āĻĒāĻ¨ā§āύ",
"download": "āĻĄāĻžāωāύāϞ⧋āĻĄ",
"download_include_embedded_motion_videos": "āĻāĻŽāĻŦ⧇āĻĄā§‡āĻĄ āĻ­āĻŋāĻĄāĻŋāĻ“",
"download_include_embedded_motion_videos_description": "āĻŽā§‹āĻļāύ āĻĢāĻŸā§‹āϰ (motion photos) āĻŽāĻ§ā§āϝ⧇ āĻĨāĻžāĻ•āĻž āĻ­āĻŋāĻĄāĻŋāĻ“āϗ⧁āϞ⧋āϕ⧇ āφāϞāĻžāĻĻāĻž āĻĢāĻžāχāϞ āĻšāĻŋāϏ⧇āĻŦ⧇ āĻ…āĻ¨ā§āϤāĻ°ā§āϭ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ",
"download_settings": "āĻĄāĻžāωāύāϞ⧋āĻĄ",
"download_settings_description": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĄāĻžāωāύāϞ⧋āĻĄā§‡āϰ āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ",
"open_in_browser": "āĻŦā§āϰāĻžāωāϜāĻžāϰ⧇ āĻ“āĻĒ⧇āύ āĻ•āϰ⧁āύ",
"user_usage_stats": "āĻ…ā§āϝāĻžāĻ•āĻžāωāĻ¨ā§āϟ āĻŦā§āϝāĻŦāĻšāĻžāϰ⧇āϰ āĻĒāϰāĻŋāϏāĻ‚āĻ–ā§āϝāĻžāύ",
"user_usage_stats_description": "āĻ…ā§āϝāĻžāĻ•āĻžāωāĻ¨ā§āϟ āĻŦā§āϝāĻŦāĻšāĻžāϰ⧇āϰ āĻĒāϰāĻŋāϏāĻ‚āĻ–ā§āϝāĻžāύ āĻĻ⧇āϖ⧁āύ",
"yes": "āĻšā§āϝāĻžāρ", "yes": "āĻšā§āϝāĻžāρ",
"you_dont_have_any_shared_links": "āφāĻĒāύāĻžāϰ āϕ⧋āύ⧋ āĻļā§‡ā§ŸāĻžāϰ āĻ•āϰāĻž āϞāĻŋāĻ™ā§āĻ• āύ⧇āχ (You don't have any shared links)", "you_dont_have_any_shared_links": "āφāĻĒāύāĻžāϰ āϕ⧋āύ⧋ āĻļā§‡ā§ŸāĻžāϰ āĻ•āϰāĻž āϞāĻŋāĻ™ā§āĻ• āύ⧇āχ (You don't have any shared links)",
"your_wifi_name": "āφāĻĒāύāĻžāϰ āĻ“āϝāĻŧāĻžāχ-āĻĢāĻžāχ āĻāϰ āύāĻžāĻŽ (Your Wi-Fi name)", "your_wifi_name": "āφāĻĒāύāĻžāϰ āĻ“āϝāĻŧāĻžāχ-āĻĢāĻžāχ āĻāϰ āύāĻžāĻŽ (Your Wi-Fi name)",

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