mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c087b7c063 |
+1
-1
@@ -1 +1 @@
|
|||||||
24.14.1
|
24.13.1
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}'
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}'
|
||||||
@@ -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}}'
|
||||||
|
|||||||
@@ -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) }}
|
||||||
|
|||||||
@@ -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/
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
@@ -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'
|
||||||
|
|||||||
@@ -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
@@ -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'
|
||||||
|
|||||||
@@ -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) }}
|
||||||
|
|||||||
Vendored
+1
-8
@@ -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"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+13
-35
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
@@ -1 +1 @@
|
|||||||
24.14.1
|
24.13.1
|
||||||
|
|||||||
+15
-14
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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);
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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 |
@@ -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]
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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/
|
||||||
|
|||||||
@@ -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`.
|
||||||
|
|
||||||
|
|||||||
@@ -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/).
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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. |
|
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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: | |
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 |
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
-1
@@ -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
|
||||||
|
|||||||
Vendored
-4
@@ -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"
|
||||||
|
|||||||
@@ -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
@@ -1 +1 @@
|
|||||||
24.14.1
|
24.13.1
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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 () => {
|
||||||
|
|||||||
@@ -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`)
|
||||||
|
|||||||
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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!);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()],
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user