Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b42ca61e1f | ||
|
|
197baf3473 | ||
|
|
3161eb7d16 | ||
|
|
bbbdd463fd | ||
|
|
73ad0d468f | ||
|
|
74d34b4f6c | ||
|
|
bf3b38a7f2 | ||
|
|
52d0c5fc73 | ||
|
|
fb20381f98 | ||
|
|
a678590ccd | ||
|
|
bd226e9e2c | ||
|
|
d023d5b6b4 | ||
|
|
9b30640e67 | ||
|
|
d17b24eea3 | ||
|
|
d38d0b8de0 | ||
|
|
f10b74f1e2 | ||
|
|
5c63d8f07a | ||
|
|
cb437829f3 | ||
|
|
7173af60e4 | ||
|
|
afccb37a3b | ||
|
|
c55ef7c383 | ||
|
|
47ea47ce14 | ||
|
|
fd6ade2b5d | ||
|
|
77e38abe91 | ||
|
|
5d1011b482 | ||
|
|
4b11e925d9 | ||
|
|
258b98c262 | ||
|
|
f1db257628 | ||
|
|
3edade6761 | ||
|
|
efcc66d63b | ||
|
|
ca96da22d0 | ||
|
|
85b98cf4c6 | ||
|
|
a404fb6cb5 | ||
|
|
3432b4625f | ||
|
|
b8777d7739 | ||
|
|
fb477627c7 | ||
|
|
fd78b89c92 | ||
|
|
45f9c52e7f | ||
|
|
0a24ff90bb | ||
|
|
e1eae00b35 | ||
|
|
15bfceb05a | ||
|
|
c1f4fe65bb | ||
|
|
a4a6a97aa8 | ||
|
|
608543da0b | ||
|
|
b4fa60d4fd | ||
|
|
b1467bd1da | ||
|
|
3cf0f5f11b | ||
|
|
454737ca79 | ||
|
|
26bc889f8d | ||
|
|
54775b896f | ||
|
|
9217fb4094 | ||
|
|
04d4a30471 | ||
|
|
90f9501902 | ||
|
|
f8d26bd865 | ||
|
|
816d040d81 | ||
|
|
2069293cc1 | ||
|
|
4bd77d5899 | ||
|
|
f8ff342852 | ||
|
|
67ac686704 | ||
|
|
4e5bf7ae2e | ||
|
|
b7fd5dcb4a | ||
|
|
bea287c5b3 | ||
|
|
46c716d450 | ||
|
|
9539a361e4 | ||
|
|
ca35e5557b | ||
|
|
a26ed3d1a6 | ||
|
|
c7d53a5006 | ||
|
|
41461e0d5d | ||
|
|
c0a48d7357 | ||
|
|
66cc744c22 | ||
|
|
58ae734fc2 | ||
|
|
54b2779b79 | ||
|
|
df26e12db6 | ||
|
|
343d89c032 |
12
.github/workflows/build-mobile.yml
vendored
12
.github/workflows/build-mobile.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
build-sign-android:
|
build-sign-android:
|
||||||
name: Build and sign Android
|
name: Build and sign Android
|
||||||
# Skip when PR from a fork
|
# Skip when PR from a fork
|
||||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
if: ${{ !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }}
|
||||||
runs-on: macos-12
|
runs-on: macos-12
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
ref="${input_ref:-$github_ref}"
|
ref="${input_ref:-$github_ref}"
|
||||||
echo "ref=$ref" >> $GITHUB_OUTPUT
|
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ steps.get-ref.outputs.ref }}
|
ref: ${{ steps.get-ref.outputs.ref }}
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
flutter-version: "3.13.0"
|
flutter-version: "3.13.3"
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Create the Keystore
|
- name: Create the Keystore
|
||||||
@@ -64,10 +64,12 @@ jobs:
|
|||||||
ALIAS: ${{ secrets.ALIAS }}
|
ALIAS: ${{ secrets.ALIAS }}
|
||||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||||
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
|
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
|
||||||
run: flutter build apk --release
|
run: |
|
||||||
|
flutter build apk --release
|
||||||
|
flutter build apk --release --split-per-abi --target-platform android-arm,android-arm64,android-x64
|
||||||
|
|
||||||
- name: Publish Android Artifact
|
- name: Publish Android Artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: release-apk-signed
|
name: release-apk-signed
|
||||||
path: mobile/build/app/outputs/flutter-apk/app-release.apk
|
path: mobile/build/app/outputs/flutter-apk/*.apk
|
||||||
|
|||||||
2
.github/workflows/cache-cleanup.yml
vendored
2
.github/workflows/cache-cleanup.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Cleanup
|
- name: Cleanup
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
|
|||||||
4
.github/workflows/docker-cleanup.yml
vendored
4
.github/workflows/docker-cleanup.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Clean temporary images
|
name: Clean temporary images
|
||||||
if: "${{ env.TOKEN != '' }}"
|
if: "${{ env.TOKEN != '' }}"
|
||||||
uses: stumpylog/image-cleaner-action/ephemeral@v0.2.0
|
uses: stumpylog/image-cleaner-action/ephemeral@v0.3.0
|
||||||
with:
|
with:
|
||||||
token: "${{ env.TOKEN }}"
|
token: "${{ env.TOKEN }}"
|
||||||
owner: "immich-app"
|
owner: "immich-app"
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Clean untagged images
|
name: Clean untagged images
|
||||||
if: "${{ env.TOKEN != '' }}"
|
if: "${{ env.TOKEN != '' }}"
|
||||||
uses: stumpylog/image-cleaner-action/untagged@v0.2.0
|
uses: stumpylog/image-cleaner-action/untagged@v0.3.0
|
||||||
with:
|
with:
|
||||||
token: "${{ env.TOKEN }}"
|
token: "${{ env.TOKEN }}"
|
||||||
owner: "immich-app"
|
owner: "immich-app"
|
||||||
|
|||||||
28
.github/workflows/docker.yml
vendored
28
.github/workflows/docker.yml
vendored
@@ -36,13 +36,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2.2.0
|
uses: docker/setup-qemu-action@v3.0.0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2.10.0
|
uses: docker/setup-buildx-action@v3.0.0
|
||||||
# Workaround to fix error:
|
# Workaround to fix error:
|
||||||
# failed to push: failed to copy: io: read/write on closed pipe
|
# failed to push: failed to copy: io: read/write on closed pipe
|
||||||
# See https://github.com/docker/build-push-action/issues/761
|
# See https://github.com/docker/build-push-action/issues/761
|
||||||
@@ -53,13 +53,13 @@ jobs:
|
|||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
# Only push to Docker Hub when making a release
|
# Only push to Docker Hub when making a release
|
||||||
if: ${{ github.event_name == 'release' }}
|
if: ${{ github.event_name == 'release' }}
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
# Skip when PR from a fork
|
# Skip when PR from a fork
|
||||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||||
with:
|
with:
|
||||||
@@ -69,7 +69,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate docker image tags
|
- name: Generate docker image tags
|
||||||
id: metadata
|
id: metadata
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
flavor: |
|
flavor: |
|
||||||
# Disable latest tag
|
# Disable latest tag
|
||||||
@@ -97,7 +97,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build and push image
|
- name: Build and push image
|
||||||
uses: docker/build-push-action@v4.1.1
|
uses: docker/build-push-action@v5.0.0
|
||||||
with:
|
with:
|
||||||
context: ${{ matrix.context }}
|
context: ${{ matrix.context }}
|
||||||
platforms: ${{ matrix.platforms }}
|
platforms: ${{ matrix.platforms }}
|
||||||
@@ -120,13 +120,13 @@ jobs:
|
|||||||
platforms: "linux/arm64,linux/amd64"
|
platforms: "linux/arm64,linux/amd64"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2.2.0
|
uses: docker/setup-qemu-action@v3.0.0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2.10.0
|
uses: docker/setup-buildx-action@v3.0.0
|
||||||
# Workaround to fix error:
|
# Workaround to fix error:
|
||||||
# failed to push: failed to copy: io: read/write on closed pipe
|
# failed to push: failed to copy: io: read/write on closed pipe
|
||||||
# See https://github.com/docker/build-push-action/issues/761
|
# See https://github.com/docker/build-push-action/issues/761
|
||||||
@@ -137,13 +137,13 @@ jobs:
|
|||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
# Only push to Docker Hub when making a release
|
# Only push to Docker Hub when making a release
|
||||||
if: ${{ github.event_name == 'release' }}
|
if: ${{ github.event_name == 'release' }}
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
# Skip when PR from a fork
|
# Skip when PR from a fork
|
||||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||||
with:
|
with:
|
||||||
@@ -153,7 +153,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate docker image tags
|
- name: Generate docker image tags
|
||||||
id: metadata
|
id: metadata
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
flavor: |
|
flavor: |
|
||||||
# Disable latest tag
|
# Disable latest tag
|
||||||
@@ -181,7 +181,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build and push image
|
- name: Build and push image
|
||||||
uses: docker/build-push-action@v4.1.1
|
uses: docker/build-push-action@v5.0.0
|
||||||
with:
|
with:
|
||||||
context: ${{ matrix.context }}
|
context: ${{ matrix.context }}
|
||||||
platforms: ${{ matrix.platforms }}
|
platforms: ${{ matrix.platforms }}
|
||||||
|
|||||||
4
.github/workflows/prepare-release.yml
vendored
4
.github/workflows/prepare-release.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.ORG_RELEASE_TOKEN }}
|
token: ${{ secrets.ORG_RELEASE_TOKEN }}
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.ORG_RELEASE_TOKEN }}
|
token: ${{ secrets.ORG_RELEASE_TOKEN }}
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/static_analysis.yml
vendored
4
.github/workflows/static_analysis.yml
vendored
@@ -17,13 +17,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Flutter SDK
|
- name: Setup Flutter SDK
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
flutter-version: "3.13.0"
|
flutter-version: "3.13.3"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: dart pub get
|
run: dart pub get
|
||||||
|
|||||||
22
.github/workflows/test.yml
vendored
22
.github/workflows/test.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run npm install
|
- name: Run npm install
|
||||||
run: npm ci
|
run: npm ci
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run npm install
|
- name: Run npm install
|
||||||
run: npm ci
|
run: npm ci
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run npm install
|
- name: Run npm install
|
||||||
run: npm ci
|
run: npm ci
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run npm install
|
- name: Run npm install
|
||||||
run: npm ci
|
run: npm ci
|
||||||
@@ -115,7 +115,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run npm install
|
- name: Run npm install
|
||||||
run: npm ci
|
run: npm ci
|
||||||
@@ -144,12 +144,12 @@ jobs:
|
|||||||
name: Run mobile unit tests
|
name: Run mobile unit tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Setup Flutter SDK
|
- name: Setup Flutter SDK
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
flutter-version: "3.13.0"
|
flutter-version: "3.13.3"
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
run: flutter test -j 1
|
run: flutter test -j 1
|
||||||
@@ -161,7 +161,7 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./machine-learning
|
working-directory: ./machine-learning
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Install poetry
|
- name: Install poetry
|
||||||
run: pipx install poetry
|
run: pipx install poetry
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
@@ -189,7 +189,7 @@ jobs:
|
|||||||
name: Check generated files are up-to-date
|
name: Check generated files are up-to-date
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Run API generation
|
- name: Run API generation
|
||||||
run: npm --prefix server run api:generate
|
run: npm --prefix server run api:generate
|
||||||
- name: Find file changes
|
- name: Find file changes
|
||||||
@@ -224,7 +224,7 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Install server dependencies
|
- name: Install server dependencies
|
||||||
run: npm --prefix server ci
|
run: npm --prefix server ci
|
||||||
- name: Run existing migrations
|
- name: Run existing migrations
|
||||||
@@ -249,7 +249,7 @@ jobs:
|
|||||||
# name: Run mobile end-to-end integration tests
|
# name: Run mobile end-to-end integration tests
|
||||||
# runs-on: macos-latest
|
# runs-on: macos-latest
|
||||||
# steps:
|
# steps:
|
||||||
# - uses: actions/checkout@v3
|
# - uses: actions/checkout@v4
|
||||||
# - uses: actions/setup-java@v3
|
# - uses: actions/setup-java@v3
|
||||||
# with:
|
# with:
|
||||||
# distribution: 'zulu'
|
# distribution: 'zulu'
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
<a href="README_tr_TR.md">Türkçe</a>
|
<a href="README_tr_TR.md">Türkçe</a>
|
||||||
<a href="README_ca_ES.md">Català</a>
|
<a href="README_ca_ES.md">Català</a>
|
||||||
<a href="README_es_ES.md">Español</a>
|
<a href="README_es_ES.md">Español</a>
|
||||||
|
<a href="README_fr_FR.md">Français</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
@@ -85,7 +86,7 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
|
|||||||
| User-defined storage structure | Yes | Yes |
|
| User-defined storage structure | Yes | Yes |
|
||||||
| Public Sharing | No | Yes |
|
| Public Sharing | No | Yes |
|
||||||
| Archive and Favorites | Yes | Yes |
|
| Archive and Favorites | Yes | Yes |
|
||||||
| Global Map | No | Yes |
|
| Global Map | Yes | Yes |
|
||||||
| Partner Sharing | Yes | Yes |
|
| Partner Sharing | Yes | Yes |
|
||||||
| Facial recognition and clustering | Yes | Yes |
|
| Facial recognition and clustering | Yes | Yes |
|
||||||
| Memories (x years ago) | Yes | Yes |
|
| Memories (x years ago) | Yes | Yes |
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
<a href="README_zh_CN.md">中文</a>
|
<a href="README_zh_CN.md">中文</a>
|
||||||
<a href="README_tr_TR.md">Türkçe</a>
|
<a href="README_tr_TR.md">Türkçe</a>
|
||||||
<a href="README_ca_ES.md">Español</a>
|
<a href="README_ca_ES.md">Español</a>
|
||||||
|
<a href="README_fr_FR.md">Français</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Avís legal
|
## Avís legal
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
<a href="README_zh_CN.md">中文</a>
|
<a href="README_zh_CN.md">中文</a>
|
||||||
<a href="README_tr_TR.md">Türkçe</a>
|
<a href="README_tr_TR.md">Türkçe</a>
|
||||||
<a href="README_ca_ES.md">Català</a>
|
<a href="README_ca_ES.md">Català</a>
|
||||||
|
<a href="README_fr_FR.md">Français</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Descargo de responsabilidad
|
## Descargo de responsabilidad
|
||||||
|
|||||||
110
README_fr_FR.md
Normal file
110
README_fr_FR.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<p align="center">
|
||||||
|
<br/>
|
||||||
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-green.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec" alt="License: MIT"></a>
|
||||||
|
<a href="https://discord.gg/D8JsnBEuKb">
|
||||||
|
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" atl="Discord"/>
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="design/immich-logo.svg" width="150" title="Login With Custom URL">
|
||||||
|
</p>
|
||||||
|
<h3 align="center">Immich - Solution de sauvegarde performante et auto-hébergée des photos et des vidéos</h3>
|
||||||
|
<br/>
|
||||||
|
<a href="https://immich.app">
|
||||||
|
<img src="design/immich-screenshots.png" title="Main Screenshot">
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
<p align="center">
|
||||||
|
<a href="README_zh_CN.md">中文</a>
|
||||||
|
<a href="README_tr_TR.md">Türkçe</a>
|
||||||
|
<a href="README_ca_ES.md">Català</a>
|
||||||
|
<a href="README_es_ES.md">Español</a>
|
||||||
|
<a href="README_fr_FR.md">Français</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Clause de non-responsabilité
|
||||||
|
|
||||||
|
- ⚠️ Le projet est en **très fort** développement.
|
||||||
|
- ⚠️ Attendez-vous à rencontrer des bugs et des changements importants.
|
||||||
|
- ⚠️ **N'utilisez pas cette application comme seule façon de sauvegarder vos photos et vos vidéos.**
|
||||||
|
- ⚠️ Ayez toujours un plan de sauvegarde en [3-2-1](https://www.seagate.com/fr/fr/blog/what-is-a-3-2-1-backup-strategy/) pour vos précieuses photos et vidéos !
|
||||||
|
|
||||||
|
## Sommaire
|
||||||
|
|
||||||
|
- [Documentation officielle](https://immich.app/docs)
|
||||||
|
- [Feuille de route](https://github.com/orgs/immich-app/projects/1)
|
||||||
|
- [Démo](#demo)
|
||||||
|
- [Fonctionnalités](#features)
|
||||||
|
- [Introduction](https://immich.app/docs/overview/introduction)
|
||||||
|
- [Installation](https://immich.app/docs/install/requirements)
|
||||||
|
- [Contribution](https://immich.app/docs/overview/support-the-project)
|
||||||
|
- [Soutenir le projet](#support-the-project)
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Vous pouvez trouver la documentation principale ainsi que les guides d'installation sur https://immich.app/.
|
||||||
|
|
||||||
|
## Démo
|
||||||
|
|
||||||
|
Vous pouvez accéder à la démo Web sur https://demo.immich.app
|
||||||
|
|
||||||
|
Pour l'application mobile, vous pouvez utiliser `https://demo.immich.app/api` dans le champ 'URL du point d'accès au serveur'
|
||||||
|
|
||||||
|
```bash title="Demo Credential"
|
||||||
|
Les identifiants
|
||||||
|
email: demo@immich.app
|
||||||
|
mot de passe: demo
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
Caractéristiques: Plan gratuit Oracle VM - Amsterdam - 2.4Ghz quatre-cœurs ARM64 CPU, 24GB RAM
|
||||||
|
```
|
||||||
|
|
||||||
|
# Fonctionnalités
|
||||||
|
|
||||||
|
| Fonctionnalités | Mobile | Web |
|
||||||
|
| ---------------------------------------------------------------- | ------ | --- |
|
||||||
|
| Téléverser et voir les vidéos et photos | Oui | Oui |
|
||||||
|
| Sauvegarde automatique quand l'application est ouverte | Oui | N/A |
|
||||||
|
| Sélection des albums à sauvegarder | Oui | N/A |
|
||||||
|
| Télécharger les photos et les vidéos sur l'appareil | Oui | Oui |
|
||||||
|
| Support multi-utilisateur | Oui | Oui |
|
||||||
|
| Albums et albums partagés | Oui | Oui |
|
||||||
|
| Barre de défilement mobile | Oui | Oui |
|
||||||
|
| Support des formats raw | Oui | Oui |
|
||||||
|
| Vue sur les métadonnées (EXIF, carte) | Oui | Oui |
|
||||||
|
| Rechercher par métadonnées, objets, faces et CLIP | Oui | Oui |
|
||||||
|
| Fonctions d'administration (gestion des utilisateurs) | Non | Oui |
|
||||||
|
| Sauvegarde en tâche de fond | Oui | N/A |
|
||||||
|
| Défilement virtuel | Oui | Oui |
|
||||||
|
| Support de l'OAuth | Oui | Oui |
|
||||||
|
| Clés d'API | N/A | Oui |
|
||||||
|
| Sauvegarde et lecture des LivePhotos | iOS | Oui |
|
||||||
|
| Structure de stockage définissable | Oui | Oui |
|
||||||
|
| Partage public | Non | Oui |
|
||||||
|
| Archives et favoris | Oui | Oui |
|
||||||
|
| Carte globale | Non | Oui |
|
||||||
|
| Partage entre utilisateurs | Oui | Oui |
|
||||||
|
| Reconnaissance et regroupement facial | Oui | Oui |
|
||||||
|
| Souvenirs (il y a x années) | Oui | Oui |
|
||||||
|
| Support hors-ligne | Oui | Non |
|
||||||
|
| Gallerie en lecture seule | Oui | Oui |
|
||||||
|
|
||||||
|
# Soutenir le projet
|
||||||
|
|
||||||
|
Je me suis engagé sur ce projet, et je ne compte pas m'arrêter. Je continuerai à mettre à jour les documentations, d'ajouter de nouvelles fonctionnalités et de résoudre des bugs. Mais je ne peux pas faire cela seul. Donc j'ai besoin de votre aide pour me donner encore plus de motivation et ainsi continuer.
|
||||||
|
|
||||||
|
Comme l'ont dit nos hôtes dans le [selfhosted.show - Dans l'épisode 'The-organization-must-not-be-name is a Hostile Actor'](https://selfhosted.show/79?t=1418), c'est un travail colossal ce que l'équipe et moi faisons. J'aimerais un jour être capable de faire ça à temps plein, c'est pourquoi je vous demande votre aide pour rendre cela possible.
|
||||||
|
|
||||||
|
Si vous estimez que c'est pour la bonne cause et que vous prévoyez d'utiliser l'application pour un moment, s'il-vous-plaît, pensez à soutenir le projet avec les moyens ci-dessous.
|
||||||
|
|
||||||
|
## Donation
|
||||||
|
|
||||||
|
- [Donation mensuelle](https://github.com/sponsors/alextran1502) via GitHub Sponsors
|
||||||
|
- [Donation occasionnelle](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) via GitHub Sponsors
|
||||||
|
- [Librepay](https://liberapay.com/alex.tran1502/)
|
||||||
|
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
|
||||||
|
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
<a href="README_zh_CN.md">中文</a>
|
<a href="README_zh_CN.md">中文</a>
|
||||||
<a href="README_ca_ES.md">Català</a>
|
<a href="README_ca_ES.md">Català</a>
|
||||||
<a href="README_es_ES.md">Español</a>
|
<a href="README_es_ES.md">Español</a>
|
||||||
|
<a href="README_fr_FR.md">Français</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Feragatname
|
## Feragatname
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
<a href="README_tr_TR.md">Türkçe</a>
|
<a href="README_tr_TR.md">Türkçe</a>
|
||||||
<a href="README_ca_ES.md">Català</a>
|
<a href="README_ca_ES.md">Català</a>
|
||||||
<a href="README_es_ES.md">Español</a>
|
<a href="README_es_ES.md">Español</a>
|
||||||
|
<a href="README_fr_FR.md">Français</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
321
cli/src/api/open-api/api.ts
generated
321
cli/src/api/open-api/api.ts
generated
@@ -4,7 +4,7 @@
|
|||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.76.0
|
* The version of the OpenAPI document: 1.78.0
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
@@ -645,6 +645,12 @@ export interface AssetResponseDto {
|
|||||||
* @memberof AssetResponseDto
|
* @memberof AssetResponseDto
|
||||||
*/
|
*/
|
||||||
'originalPath': string;
|
'originalPath': string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {UserResponseDto}
|
||||||
|
* @memberof AssetResponseDto
|
||||||
|
*/
|
||||||
|
'owner'?: UserResponseDto;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
@@ -909,6 +915,21 @@ export const CLIPMode = {
|
|||||||
export type CLIPMode = typeof CLIPMode[keyof typeof CLIPMode];
|
export type CLIPMode = typeof CLIPMode[keyof typeof CLIPMode];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const CQMode = {
|
||||||
|
Auto: 'auto',
|
||||||
|
Cqp: 'cqp',
|
||||||
|
Icq: 'icq'
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type CQMode = typeof CQMode[keyof typeof CQMode];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
@@ -1031,6 +1052,20 @@ export interface ClassificationConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const Colorspace = {
|
||||||
|
Srgb: 'srgb',
|
||||||
|
P3: 'p3'
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type Colorspace = typeof Colorspace[keyof typeof Colorspace];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
@@ -1861,6 +1896,19 @@ export const ModelType = {
|
|||||||
export type ModelType = typeof ModelType[keyof typeof ModelType];
|
export type ModelType = typeof ModelType[keyof typeof ModelType];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface OAuthAuthorizeResponseDto
|
||||||
|
*/
|
||||||
|
export interface OAuthAuthorizeResponseDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof OAuthAuthorizeResponseDto
|
||||||
|
*/
|
||||||
|
'url': string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
@@ -1969,7 +2017,7 @@ export interface PeopleUpdateDto {
|
|||||||
*/
|
*/
|
||||||
export interface PeopleUpdateItem {
|
export interface PeopleUpdateItem {
|
||||||
/**
|
/**
|
||||||
* Person date of birth.
|
* Person date of birth. Note: the mobile app cannot currently set the birth date to null.
|
||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof PeopleUpdateItem
|
* @memberof PeopleUpdateItem
|
||||||
*/
|
*/
|
||||||
@@ -2043,7 +2091,7 @@ export interface PersonResponseDto {
|
|||||||
*/
|
*/
|
||||||
export interface PersonUpdateDto {
|
export interface PersonUpdateDto {
|
||||||
/**
|
/**
|
||||||
* Person date of birth.
|
* Person date of birth. Note: the mobile app cannot currently set the birth date to null.
|
||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof PersonUpdateDto
|
* @memberof PersonUpdateDto
|
||||||
*/
|
*/
|
||||||
@@ -2295,6 +2343,31 @@ export interface SearchResponseDto {
|
|||||||
*/
|
*/
|
||||||
'assets': SearchAssetResponseDto;
|
'assets': SearchAssetResponseDto;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface ServerConfigDto
|
||||||
|
*/
|
||||||
|
export interface ServerConfigDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof ServerConfigDto
|
||||||
|
*/
|
||||||
|
'loginPageMessage': string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof ServerConfigDto
|
||||||
|
*/
|
||||||
|
'mapTileUrl': string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof ServerConfigDto
|
||||||
|
*/
|
||||||
|
'oauthButtonText': string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
@@ -2319,6 +2392,12 @@ export interface ServerFeaturesDto {
|
|||||||
* @memberof ServerFeaturesDto
|
* @memberof ServerFeaturesDto
|
||||||
*/
|
*/
|
||||||
'facialRecognition': boolean;
|
'facialRecognition': boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof ServerFeaturesDto
|
||||||
|
*/
|
||||||
|
'map': boolean;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@@ -2762,6 +2841,12 @@ export interface SystemConfigDto {
|
|||||||
* @memberof SystemConfigDto
|
* @memberof SystemConfigDto
|
||||||
*/
|
*/
|
||||||
'machineLearning': SystemConfigMachineLearningDto;
|
'machineLearning': SystemConfigMachineLearningDto;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {SystemConfigMapDto}
|
||||||
|
* @memberof SystemConfigDto
|
||||||
|
*/
|
||||||
|
'map': SystemConfigMapDto;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {SystemConfigOAuthDto}
|
* @type {SystemConfigOAuthDto}
|
||||||
@@ -2799,24 +2884,54 @@ export interface SystemConfigFFmpegDto {
|
|||||||
* @memberof SystemConfigFFmpegDto
|
* @memberof SystemConfigFFmpegDto
|
||||||
*/
|
*/
|
||||||
'accel': TranscodeHWAccel;
|
'accel': TranscodeHWAccel;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof SystemConfigFFmpegDto
|
||||||
|
*/
|
||||||
|
'bframes': number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {CQMode}
|
||||||
|
* @memberof SystemConfigFFmpegDto
|
||||||
|
*/
|
||||||
|
'cqMode': CQMode;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {number}
|
* @type {number}
|
||||||
* @memberof SystemConfigFFmpegDto
|
* @memberof SystemConfigFFmpegDto
|
||||||
*/
|
*/
|
||||||
'crf': number;
|
'crf': number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof SystemConfigFFmpegDto
|
||||||
|
*/
|
||||||
|
'gopSize': number;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof SystemConfigFFmpegDto
|
* @memberof SystemConfigFFmpegDto
|
||||||
*/
|
*/
|
||||||
'maxBitrate': string;
|
'maxBitrate': string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof SystemConfigFFmpegDto
|
||||||
|
*/
|
||||||
|
'npl': number;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof SystemConfigFFmpegDto
|
* @memberof SystemConfigFFmpegDto
|
||||||
*/
|
*/
|
||||||
'preset': string;
|
'preset': string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof SystemConfigFFmpegDto
|
||||||
|
*/
|
||||||
|
'refs': number;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {AudioCodec}
|
* @type {AudioCodec}
|
||||||
@@ -2835,6 +2950,12 @@ export interface SystemConfigFFmpegDto {
|
|||||||
* @memberof SystemConfigFFmpegDto
|
* @memberof SystemConfigFFmpegDto
|
||||||
*/
|
*/
|
||||||
'targetVideoCodec': VideoCodec;
|
'targetVideoCodec': VideoCodec;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof SystemConfigFFmpegDto
|
||||||
|
*/
|
||||||
|
'temporalAQ': boolean;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {number}
|
* @type {number}
|
||||||
@@ -2966,6 +3087,25 @@ export interface SystemConfigMachineLearningDto {
|
|||||||
*/
|
*/
|
||||||
'url': string;
|
'url': string;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface SystemConfigMapDto
|
||||||
|
*/
|
||||||
|
export interface SystemConfigMapDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof SystemConfigMapDto
|
||||||
|
*/
|
||||||
|
'enabled': boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof SystemConfigMapDto
|
||||||
|
*/
|
||||||
|
'tileUrl': string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
@@ -3120,12 +3260,24 @@ export interface SystemConfigTemplateStorageOptionDto {
|
|||||||
* @interface SystemConfigThumbnailDto
|
* @interface SystemConfigThumbnailDto
|
||||||
*/
|
*/
|
||||||
export interface SystemConfigThumbnailDto {
|
export interface SystemConfigThumbnailDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {Colorspace}
|
||||||
|
* @memberof SystemConfigThumbnailDto
|
||||||
|
*/
|
||||||
|
'colorspace': Colorspace;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {number}
|
* @type {number}
|
||||||
* @memberof SystemConfigThumbnailDto
|
* @memberof SystemConfigThumbnailDto
|
||||||
*/
|
*/
|
||||||
'jpegSize': number;
|
'jpegSize': number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof SystemConfigThumbnailDto
|
||||||
|
*/
|
||||||
|
'quality': number;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {number}
|
* @type {number}
|
||||||
@@ -3133,6 +3285,8 @@ export interface SystemConfigThumbnailDto {
|
|||||||
*/
|
*/
|
||||||
'webpSize': number;
|
'webpSize': number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
@@ -3325,12 +3479,6 @@ export interface UpdateAssetDto {
|
|||||||
* @memberof UpdateAssetDto
|
* @memberof UpdateAssetDto
|
||||||
*/
|
*/
|
||||||
'isFavorite'?: boolean;
|
'isFavorite'?: boolean;
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {Array<string>}
|
|
||||||
* @memberof UpdateAssetDto
|
|
||||||
*/
|
|
||||||
'tagIds'?: Array<string>;
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -6207,7 +6355,7 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Update an asset
|
*
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
* @param {UpdateAssetDto} updateAssetDto
|
* @param {UpdateAssetDto} updateAssetDto
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
@@ -6686,7 +6834,7 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
|||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Update an asset
|
*
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
* @param {UpdateAssetDto} updateAssetDto
|
* @param {UpdateAssetDto} updateAssetDto
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
@@ -6943,7 +7091,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
|||||||
return localVarFp.serveFile(requestParameters.id, requestParameters.isThumb, requestParameters.isWeb, requestParameters.key, options).then((request) => request(axios, basePath));
|
return localVarFp.serveFile(requestParameters.id, requestParameters.isThumb, requestParameters.isWeb, requestParameters.key, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Update an asset
|
*
|
||||||
* @param {AssetApiUpdateAssetRequest} requestParameters Request parameters.
|
* @param {AssetApiUpdateAssetRequest} requestParameters Request parameters.
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
@@ -7860,7 +8008,7 @@ export class AssetApi extends BaseAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update an asset
|
*
|
||||||
* @param {AssetApiUpdateAssetRequest} requestParameters Request parameters.
|
* @param {AssetApiUpdateAssetRequest} requestParameters Request parameters.
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
@@ -8890,6 +9038,41 @@ export class JobApi extends BaseAPI {
|
|||||||
*/
|
*/
|
||||||
export const OAuthApiAxiosParamCreator = function (configuration?: Configuration) {
|
export const OAuthApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {OAuthConfigDto} oAuthConfigDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
authorizeOAuth: async (oAuthConfigDto: OAuthConfigDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
// verify required parameter 'oAuthConfigDto' is not null or undefined
|
||||||
|
assertParamExists('authorizeOAuth', 'oAuthConfigDto', oAuthConfigDto)
|
||||||
|
const localVarPath = `/oauth/authorize`;
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
localVarRequestOptions.data = serializeDataIfNeeded(oAuthConfigDto, localVarRequestOptions, configuration)
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {OAuthCallbackDto} oAuthCallbackDto
|
* @param {OAuthCallbackDto} oAuthCallbackDto
|
||||||
@@ -8926,9 +9109,10 @@ export const OAuthApiAxiosParamCreator = function (configuration?: Configuration
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
* @deprecated use feature flags and /oauth/authorize
|
||||||
* @param {OAuthConfigDto} oAuthConfigDto
|
* @param {OAuthConfigDto} oAuthConfigDto
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
* @deprecated
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
generateConfig: async (oAuthConfigDto: OAuthConfigDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
generateConfig: async (oAuthConfigDto: OAuthConfigDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
@@ -9081,6 +9265,16 @@ export const OAuthApiAxiosParamCreator = function (configuration?: Configuration
|
|||||||
export const OAuthApiFp = function(configuration?: Configuration) {
|
export const OAuthApiFp = function(configuration?: Configuration) {
|
||||||
const localVarAxiosParamCreator = OAuthApiAxiosParamCreator(configuration)
|
const localVarAxiosParamCreator = OAuthApiAxiosParamCreator(configuration)
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {OAuthConfigDto} oAuthConfigDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async authorizeOAuth(oAuthConfigDto: OAuthConfigDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<OAuthAuthorizeResponseDto>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.authorizeOAuth(oAuthConfigDto, options);
|
||||||
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {OAuthCallbackDto} oAuthCallbackDto
|
* @param {OAuthCallbackDto} oAuthCallbackDto
|
||||||
@@ -9092,9 +9286,10 @@ export const OAuthApiFp = function(configuration?: Configuration) {
|
|||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
* @deprecated use feature flags and /oauth/authorize
|
||||||
* @param {OAuthConfigDto} oAuthConfigDto
|
* @param {OAuthConfigDto} oAuthConfigDto
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
* @deprecated
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async generateConfig(oAuthConfigDto: OAuthConfigDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<OAuthConfigResponseDto>> {
|
async generateConfig(oAuthConfigDto: OAuthConfigDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<OAuthConfigResponseDto>> {
|
||||||
@@ -9139,6 +9334,15 @@ export const OAuthApiFp = function(configuration?: Configuration) {
|
|||||||
export const OAuthApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
export const OAuthApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
||||||
const localVarFp = OAuthApiFp(configuration)
|
const localVarFp = OAuthApiFp(configuration)
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {OAuthApiAuthorizeOAuthRequest} requestParameters Request parameters.
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
authorizeOAuth(requestParameters: OAuthApiAuthorizeOAuthRequest, options?: AxiosRequestConfig): AxiosPromise<OAuthAuthorizeResponseDto> {
|
||||||
|
return localVarFp.authorizeOAuth(requestParameters.oAuthConfigDto, options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {OAuthApiCallbackRequest} requestParameters Request parameters.
|
* @param {OAuthApiCallbackRequest} requestParameters Request parameters.
|
||||||
@@ -9149,9 +9353,10 @@ export const OAuthApiFactory = function (configuration?: Configuration, basePath
|
|||||||
return localVarFp.callback(requestParameters.oAuthCallbackDto, options).then((request) => request(axios, basePath));
|
return localVarFp.callback(requestParameters.oAuthCallbackDto, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
* @deprecated use feature flags and /oauth/authorize
|
||||||
* @param {OAuthApiGenerateConfigRequest} requestParameters Request parameters.
|
* @param {OAuthApiGenerateConfigRequest} requestParameters Request parameters.
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
* @deprecated
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
generateConfig(requestParameters: OAuthApiGenerateConfigRequest, options?: AxiosRequestConfig): AxiosPromise<OAuthConfigResponseDto> {
|
generateConfig(requestParameters: OAuthApiGenerateConfigRequest, options?: AxiosRequestConfig): AxiosPromise<OAuthConfigResponseDto> {
|
||||||
@@ -9185,6 +9390,20 @@ export const OAuthApiFactory = function (configuration?: Configuration, basePath
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request parameters for authorizeOAuth operation in OAuthApi.
|
||||||
|
* @export
|
||||||
|
* @interface OAuthApiAuthorizeOAuthRequest
|
||||||
|
*/
|
||||||
|
export interface OAuthApiAuthorizeOAuthRequest {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {OAuthConfigDto}
|
||||||
|
* @memberof OAuthApiAuthorizeOAuth
|
||||||
|
*/
|
||||||
|
readonly oAuthConfigDto: OAuthConfigDto
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request parameters for callback operation in OAuthApi.
|
* Request parameters for callback operation in OAuthApi.
|
||||||
* @export
|
* @export
|
||||||
@@ -9234,6 +9453,17 @@ export interface OAuthApiLinkRequest {
|
|||||||
* @extends {BaseAPI}
|
* @extends {BaseAPI}
|
||||||
*/
|
*/
|
||||||
export class OAuthApi extends BaseAPI {
|
export class OAuthApi extends BaseAPI {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {OAuthApiAuthorizeOAuthRequest} requestParameters Request parameters.
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof OAuthApi
|
||||||
|
*/
|
||||||
|
public authorizeOAuth(requestParameters: OAuthApiAuthorizeOAuthRequest, options?: AxiosRequestConfig) {
|
||||||
|
return OAuthApiFp(this.configuration).authorizeOAuth(requestParameters.oAuthConfigDto, options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {OAuthApiCallbackRequest} requestParameters Request parameters.
|
* @param {OAuthApiCallbackRequest} requestParameters Request parameters.
|
||||||
@@ -9246,9 +9476,10 @@ export class OAuthApi extends BaseAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @deprecated use feature flags and /oauth/authorize
|
||||||
* @param {OAuthApiGenerateConfigRequest} requestParameters Request parameters.
|
* @param {OAuthApiGenerateConfigRequest} requestParameters Request parameters.
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
* @deprecated
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
* @memberof OAuthApi
|
* @memberof OAuthApi
|
||||||
*/
|
*/
|
||||||
@@ -10650,6 +10881,35 @@ export class SearchApi extends BaseAPI {
|
|||||||
*/
|
*/
|
||||||
export const ServerInfoApiAxiosParamCreator = function (configuration?: Configuration) {
|
export const ServerInfoApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getServerConfig: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
const localVarPath = `/server-info/config`;
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
@@ -10852,6 +11112,15 @@ export const ServerInfoApiAxiosParamCreator = function (configuration?: Configur
|
|||||||
export const ServerInfoApiFp = function(configuration?: Configuration) {
|
export const ServerInfoApiFp = function(configuration?: Configuration) {
|
||||||
const localVarAxiosParamCreator = ServerInfoApiAxiosParamCreator(configuration)
|
const localVarAxiosParamCreator = ServerInfoApiAxiosParamCreator(configuration)
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async getServerConfig(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ServerConfigDto>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getServerConfig(options);
|
||||||
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
@@ -10916,6 +11185,14 @@ export const ServerInfoApiFp = function(configuration?: Configuration) {
|
|||||||
export const ServerInfoApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
export const ServerInfoApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
||||||
const localVarFp = ServerInfoApiFp(configuration)
|
const localVarFp = ServerInfoApiFp(configuration)
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getServerConfig(options?: AxiosRequestConfig): AxiosPromise<ServerConfigDto> {
|
||||||
|
return localVarFp.getServerConfig(options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
@@ -10974,6 +11251,16 @@ export const ServerInfoApiFactory = function (configuration?: Configuration, bas
|
|||||||
* @extends {BaseAPI}
|
* @extends {BaseAPI}
|
||||||
*/
|
*/
|
||||||
export class ServerInfoApi extends BaseAPI {
|
export class ServerInfoApi extends BaseAPI {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof ServerInfoApi
|
||||||
|
*/
|
||||||
|
public getServerConfig(options?: AxiosRequestConfig) {
|
||||||
|
return ServerInfoApiFp(this.configuration).getServerConfig(options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
|||||||
2
cli/src/api/open-api/base.ts
generated
2
cli/src/api/open-api/base.ts
generated
@@ -4,7 +4,7 @@
|
|||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.76.0
|
* The version of the OpenAPI document: 1.78.0
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|||||||
2
cli/src/api/open-api/common.ts
generated
2
cli/src/api/open-api/common.ts
generated
@@ -4,7 +4,7 @@
|
|||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.76.0
|
* The version of the OpenAPI document: 1.78.0
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|||||||
2
cli/src/api/open-api/configuration.ts
generated
2
cli/src/api/open-api/configuration.ts
generated
@@ -4,7 +4,7 @@
|
|||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.76.0
|
* The version of the OpenAPI document: 1.78.0
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|||||||
2
cli/src/api/open-api/index.ts
generated
2
cli/src/api/open-api/index.ts
generated
@@ -4,7 +4,7 @@
|
|||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.76.0
|
* The version of the OpenAPI document: 1.78.0
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 3003:3003
|
- 3003:3003
|
||||||
volumes:
|
volumes:
|
||||||
- ../machine-learning/app:/usr/src/app
|
- ../machine-learning:/usr/src/app
|
||||||
- model-cache:/cache
|
- model-cache:/cache
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
|
|||||||
@@ -39,15 +39,40 @@ This often happens when using a reverse proxy or cloudflare tunnel in front of I
|
|||||||
|
|
||||||
### Why is Immich slow on low-memory systems like the Raspberry Pi?
|
### Why is Immich slow on low-memory systems like the Raspberry Pi?
|
||||||
|
|
||||||
Immich uses optional machine-learning features to enhance search results. This feature, however, can be too heavy to run on a Raspberry Pi. To disable machine learning, comment out the `immich-machine-learning` section of your docker-compose.yml and set `IMMICH_MACHINE_LEARNING_ENABLED=false` in your .env file.
|
Immich optionally uses machine learning for several features. However, it can be too heavy to run on a Raspberry Pi. You can [mitigate](/docs/FAQ#how-can-i-lower-immichs-cpu-usage) this or [disable](/docs/FAQ.md#how-can-i-disable-machine-learning) machine learning entirely.
|
||||||
|
|
||||||
### How to disable machine-learning and TypeSense?
|
### How can I lower Immich's CPU usage?
|
||||||
|
|
||||||
:::warning
|
The initial backup is the most intensive due to the number of jobs running. The most CPU-intensive ones are transcoding and machine learning jobs (Tag Images, Encode CLIP, Recognize Faces), and to a lesser extent thumbnail generation. Here are some ways to lower their CPU usage:
|
||||||
Disabling both will result in poor search experience and typesense utilizes CLIP embeddings which are generated by machine-learning.
|
|
||||||
|
- Lower the job concurrency for these jobs to 1.
|
||||||
|
- Under Settings > Transcoding Settings > Threads, set the number of threads to a low number like 1 or 2.
|
||||||
|
- Set the `TYPESENSE_THREAD_POOL_SIZE` environmental variable and restart the Typesense container. For instance, `TYPESENSE_THREAD_POOL_SIZE=8` will limit it to 8 threads.
|
||||||
|
- Under Settings > Machine Learning Settings > Facial Recognition > Model Name, you can change the facial recognition model to `buffalo_s` instead of `buffalo_l`. The former is a smaller and faster model, albeit not as good.
|
||||||
|
- You _must_ re-run the Recognize Faces job for all images after this for facial recognition on new images to work properly.
|
||||||
|
- If these changes are not enough, see [below](/docs/FAQ.md#how-can-i-disable-machine-learning) for how you can disable machine learning.
|
||||||
|
|
||||||
|
### How can I disable machine learning?
|
||||||
|
|
||||||
|
:::info
|
||||||
|
Disabling machine learning will result in a poor experience for searching and the 'Explore' page, as these are reliant on it to work as intended.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
These features can be disabled by commenting out `immich-typesense` and `immich-machine-learning` sections of the docker-compose.yml and setting `IMMICH_MACHINE_LEARNING_ENABLED=false` & `TYPESENSE_ENABLED=false` in your .env file.
|
Machine learning can be disabled under Settings > Machine Learning Settings, either entirely or by model type. For instance, you can choose to disable smart search with CLIP, but keep facial recognition enabled. This means that the machine learning service will only process the enabled jobs.
|
||||||
|
|
||||||
|
However, disabling all jobs will not disable the machine learning service itself. To prevent it from starting up at all in this case, you can comment out the `immich-machine-learning` section of the docker-compose.yml.
|
||||||
|
|
||||||
|
### How can I disable TypeSense?
|
||||||
|
|
||||||
|
:::info
|
||||||
|
Disabling Typesense will result in a poor search experience since searching is reliant on it.
|
||||||
|
:::
|
||||||
|
|
||||||
|
You can disable Typesense by commenting out the `immich-typesense` section of the docker-compose.yml and setting `TYPESENSE_ENABLED=false` in your .env file.
|
||||||
|
|
||||||
|
### I'm getting errors about models being corrupt or failing to download. What do I do?
|
||||||
|
|
||||||
|
You can delete the model cache volume, which is where models are downloaded. This will give the service a clean environment to download the model again.
|
||||||
|
|
||||||
### What happens to existing files after I choose a new [Storage Template](/docs/administration/storage-template.mdx)?
|
### What happens to existing files after I choose a new [Storage Template](/docs/administration/storage-template.mdx)?
|
||||||
|
|
||||||
@@ -59,7 +84,7 @@ This is fixed by running the storage migration job.
|
|||||||
|
|
||||||
### Why is object detection not very good?
|
### Why is object detection not very good?
|
||||||
|
|
||||||
The model we used for machine learning is a prebuilt model, so the accuracy is not very good. It will hopefully be replaced with a better solution in the future.
|
The default image tagging model is relatively small. You can change this for a larger model like `google/vit-base-patch16-224` by setting the model name under Settings > Machine Learning Settings > Image Tagging. You can then re-run the Image Tagging job to get improved tags.
|
||||||
|
|
||||||
### How can I see Immich logs?
|
### How can I see Immich logs?
|
||||||
|
|
||||||
@@ -96,10 +121,6 @@ docker-compose down -v
|
|||||||
|
|
||||||
After removing the containers and volumes, the **Files** can be cleaned up (if necessary) from the `UPLOAD_LOCATION` by simply deleting an unwanted files or folders.
|
After removing the containers and volumes, the **Files** can be cleaned up (if necessary) from the `UPLOAD_LOCATION` by simply deleting an unwanted files or folders.
|
||||||
|
|
||||||
### Why iOS app shows duplicate photos on the timeline while the web doesn't?
|
|
||||||
|
|
||||||
If you are using `My Photo Stream`, the Photos app temporarily creates duplicates of photos taken in the last 30 days. These photos are included in the `Recents` album and thus shown up twice. To fix this, you can disable `My Photo Stream` in the native Photos app or choose a different album in the backup screen in Immich.
|
|
||||||
|
|
||||||
### How can I move all data (photos, persons, albums) from one user to another?
|
### How can I move all data (photos, persons, albums) from one user to another?
|
||||||
|
|
||||||
This requires some database queries. You can do this on the command line (in the PostgreSQL container using the psql command), or you can add for example an [Adminer](https://www.adminer.org/) container to the `docker-compose.yml` file, so that you can use a web-interface.
|
This requires some database queries. You can do this on the command line (in the PostgreSQL container using the psql command), or you can add for example an [Adminer](https://www.adminer.org/) container to the `docker-compose.yml` file, so that you can use a web-interface.
|
||||||
|
|||||||
@@ -89,6 +89,12 @@ The machine learning service is written in [Python](https://www.python.org/) and
|
|||||||
|
|
||||||
All machine learning related operations have been externalized to this service, `immich-machine-learning`. Python is a natural choice for AI and machine learning. It also has some pretty specific hardware requirements. Running it as a separate container makes it possible to run the container on a separate machine, or easily disable it entirely.
|
All machine learning related operations have been externalized to this service, `immich-machine-learning`. Python is a natural choice for AI and machine learning. It also has some pretty specific hardware requirements. Running it as a separate container makes it possible to run the container on a separate machine, or easily disable it entirely.
|
||||||
|
|
||||||
|
Each request to the machine learning service contains the relevant metadata for the model task, model name, and so on. These settings are stored in Postgres along with other system configs. For each request, the microservices container fetches these settings in order to attach them to the request.
|
||||||
|
|
||||||
|
Internally, the machine learning service downloads, loads and configures the specified model for a given request before processing the text or image payload with it. Models that have been loaded are cached and reused across requests. A thread pool is used to process each request in a different thread so as not to block the async event loop.
|
||||||
|
|
||||||
|
All models are in ONNX format. This format has wide industry support, meaning that most other model formats can be exported to it and many hardware APIs support it. It's also quite fast.
|
||||||
|
|
||||||
Machine learning models are also quite _large_, requiring _quite a bit_ of memory. We are always looking for ways to improve and optimize this aspect of this container specifically.
|
Machine learning models are also quite _large_, requiring _quite a bit_ of memory. We are always looking for ways to improve and optimize this aspect of this container specifically.
|
||||||
|
|
||||||
### Postgres
|
### Postgres
|
||||||
|
|||||||
@@ -68,16 +68,16 @@ Be aware that as this runs inside a container, you need to mount the folder from
|
|||||||
|
|
||||||
```bash title="Upload current directory"
|
```bash title="Upload current directory"
|
||||||
cd /DIRECTORY/WITH/IMAGES
|
cd /DIRECTORY/WITH/IMAGES
|
||||||
docker run -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
|
docker run -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest upload --recursive --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash title="Upload target directory"
|
```bash title="Upload target directory"
|
||||||
docker run -it --rm -v "/DIRECTORY/WITH/IMAGES:/import" ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
|
docker run -it --rm -v "/DIRECTORY/WITH/IMAGES:/import" ghcr.io/immich-app/immich-cli:latest upload --recursive --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash title="Create an alias"
|
```bash title="Create an alias"
|
||||||
alias immich='docker run -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest'
|
alias immich='docker run -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest'
|
||||||
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
|
immich upload --recursive --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tip Internal networking
|
:::tip Internal networking
|
||||||
@@ -88,7 +88,7 @@ If you are running the CLI container on the same machine as your Immich server,
|
|||||||
3. Use `--server http://immich-server:3001` for the upload command instead of the external address.
|
3. Use `--server http://immich-server:3001` for the upload command instead of the external address.
|
||||||
|
|
||||||
```bash title="Upload to internal address"
|
```bash title="Upload to internal address"
|
||||||
docker run --network immich_default -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://immich-server:3001
|
docker run --network immich_default -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest upload --recursive --key HFEJ38DNSDUEG --server http://immich-server:3001
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|||||||
@@ -54,6 +54,25 @@ The default configuration looks like this:
|
|||||||
"concurrency": 1
|
"concurrency": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"machineLearning": {
|
||||||
|
"classification": {
|
||||||
|
"minScore": 0.7,
|
||||||
|
"enabled": true,
|
||||||
|
"modelName": "microsoft/resnet-50"
|
||||||
|
},
|
||||||
|
"enabled": true,
|
||||||
|
"url": "http://immich-machine-learning:3003",
|
||||||
|
"clip": {
|
||||||
|
"enabled": true,
|
||||||
|
"modelName": "ViT-B-32::openai"
|
||||||
|
},
|
||||||
|
"facialRecognition": {
|
||||||
|
"enabled": true,
|
||||||
|
"modelName": "buffalo_l",
|
||||||
|
"minScore": 0.7,
|
||||||
|
"maxDistance": 0.6
|
||||||
|
}
|
||||||
|
},
|
||||||
"oauth": {
|
"oauth": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"issuerUrl": "",
|
"issuerUrl": "",
|
||||||
@@ -75,7 +94,9 @@ The default configuration looks like this:
|
|||||||
},
|
},
|
||||||
"thumbnail": {
|
"thumbnail": {
|
||||||
"webpSize": 250,
|
"webpSize": 250,
|
||||||
"jpegSize": 1440
|
"jpegSize": 1440,
|
||||||
|
"quality": 90,
|
||||||
|
"colorspace": "p3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -4,6 +4,17 @@ sidebar_position: 90
|
|||||||
|
|
||||||
# Environment Variables
|
# Environment Variables
|
||||||
|
|
||||||
|
:::caution
|
||||||
|
|
||||||
|
To change environment variables, you must recreate the Immich containers.
|
||||||
|
Just restarting the containers does not replace the environment within the container!
|
||||||
|
|
||||||
|
In order to recreate the container using docker compose, run `docker compose up -d`.
|
||||||
|
In most cases docker will recognize that the `.env` file has changed and recreate the affected containers.
|
||||||
|
If this should not work, try running `docker compose up -d --force-recreate`.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## Docker Compose
|
## Docker Compose
|
||||||
|
|
||||||
| Variable | Description | Default | Services |
|
| Variable | Description | Default | Services |
|
||||||
@@ -46,23 +57,22 @@ These environment variables are used by the `docker-compose.yml` file and do **N
|
|||||||
|
|
||||||
## Ports
|
## Ports
|
||||||
|
|
||||||
| Variable | Description | Default | Services |
|
| Variable | Description | Default | Services |
|
||||||
| :---------------------- | :-------------------- | :-----: | :--------------- |
|
| :---------------------- | :-------------------- | :-------: | :--------------- |
|
||||||
| `PORT` | Web Port | `3000` | web |
|
| `PORT` | Web Port | `3000` | web |
|
||||||
| `SERVER_PORT` | Server Port | `3001` | server |
|
| `SERVER_PORT` | Server Port | `3001` | server |
|
||||||
| `MICROSERVICES_PORT` | Microservices Port | `3002` | microservices |
|
| `MICROSERVICES_PORT` | Microservices Port | `3002` | microservices |
|
||||||
| `MACHINE_LEARNING_PORT` | Machine Learning Port | `3003` | machine learning |
|
| `MACHINE_LEARNING_HOST` | Machine Learning Host | `0.0.0.0` | machine learning |
|
||||||
|
| `MACHINE_LEARNING_PORT` | Machine Learning Port | `3003` | machine learning |
|
||||||
|
|
||||||
## URLs
|
## URLs
|
||||||
|
|
||||||
| Variable | Description | Default | Services |
|
| Variable | Description | Default | Services |
|
||||||
| :-------------------------------- | :--------------------------- | :-----------------------------------: | :-------------------- |
|
| :------------------------- | :---------------------- | :-------------------------: | :--------- |
|
||||||
| `IMMICH_WEB_URL` | Immich Web URL | `http://immich-web:3000` | proxy |
|
| `IMMICH_WEB_URL` | Immich Web URL | `http://immich-web:3000` | proxy |
|
||||||
| `IMMICH_SERVER_URL` | Immich Server URL | `http://immich-server:3001` | web, proxy |
|
| `IMMICH_SERVER_URL` | Immich Server URL | `http://immich-server:3001` | web, proxy |
|
||||||
| `IMMICH_MACHINE_LEARNING_ENABLED` | Enabled machine learning | `true` | server, microservices |
|
| `PUBLIC_IMMICH_SERVER_URL` | Public Immich URL | `http://immich-server:3001` | web |
|
||||||
| `IMMICH_MACHINE_LEARNING_URL` | Immich Machine Learning URL, | `http://immich-machine-learning:3003` | server, microservices |
|
| `IMMICH_API_URL_EXTERNAL` | Immich API URL External | `/api` | web |
|
||||||
| `PUBLIC_IMMICH_SERVER_URL` | Public Immich URL | `http://immich-server:3001` | web |
|
|
||||||
| `IMMICH_API_URL_EXTERNAL` | Immich API URL External | `/api` | web |
|
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
|
|
||||||
@@ -178,18 +188,27 @@ Typesense URL example JSON before encoding:
|
|||||||
|
|
||||||
## Machine Learning
|
## Machine Learning
|
||||||
|
|
||||||
| Variable | Description | Default | Services |
|
| Variable | Description | Default | Services |
|
||||||
| :------------------------------------------ | :----------------------------- | :-------------------: | :--------------- |
|
| :----------------------------------------------- | :---------------------------------------------------------------- | :-----------------: | :--------------- |
|
||||||
| `MACHINE_LEARNING_MIN_FACE_SCORE` | Minimum Face Score | `0.7` | machine learning |
|
| `MACHINE_LEARNING_MODEL_TTL`<sup>\*1</sup> | Inactivity time (s) before a model is unloaded (disabled if <= 0) | `0` | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_TTL` | Model TTL | `300` | machine learning |
|
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning |
|
||||||
| `MACHINE_LEARNING_EAGER_STARTUP` | Eager Startup | `true` | machine learning |
|
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*2</sup> | Thread count of the request thread pool (disabled if <= 0) | number of CPU cores | machine learning |
|
||||||
| `MACHINE_LEARNING_MIN_TAG_SCORE` | Minimum Tag Score | `0.9` | machine learning |
|
| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
|
||||||
| `MACHINE_LEARNING_FACIAL_RECOGNITION_MODEL` | Facial Recognition Model | `buffalo_l` | machine learning |
|
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
|
||||||
| `MACHINE_LEARNING_CLIP_TEXT_MODEL` | Clip Text Model | `clip-ViT-B-32` | machine learning |
|
| `MACHINE_LEARNING_WORKERS`<sup>\*3</sup> | Number of worker processes to spawn | `1` | machine learning |
|
||||||
| `MACHINE_LEARNING_CLIP_IMAGE_MODEL` | Clip Image Model | `clip-ViT-B-32` | machine learning |
|
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` | machine learning |
|
||||||
| `MACHINE_LEARNING_CLASSIFICATION_MODEL` | Classification Model | `microsoft/resnet-50` | machine learning |
|
|
||||||
| `MACHINE_LEARNING_CACHE_FOLDER` | ML Cache Location | `/cache` | machine learning |
|
\*1: This is an experimental feature. It may result in increased memory use over time when loading models repeatedly.
|
||||||
| `TRANSFORMERS_CACHE` | ML Transformers Cache Location | `/cache` | machine learning |
|
|
||||||
|
\*2: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
|
||||||
|
|
||||||
|
\*3: Since each process duplicates models in memory, changing this is not recommended unless you have abundant memory to go around.
|
||||||
|
|
||||||
|
:::info
|
||||||
|
|
||||||
|
Other machine learning parameters can be tuned from the admin UI.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## Docker Secrets
|
## Docker Secrets
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.11.4-bullseye@sha256:5b401676aff858495a5c9c726c60b8b73fe52833e9e16eccdb59e93d52741727 as builder
|
FROM python:3.11-bookworm as builder
|
||||||
|
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
@@ -14,9 +14,9 @@ COPY poetry.lock pyproject.toml requirements.txt ./
|
|||||||
RUN poetry install --sync --no-interaction --no-ansi --no-root --only main
|
RUN poetry install --sync --no-interaction --no-ansi --no-root --only main
|
||||||
RUN pip install --no-deps -r requirements.txt
|
RUN pip install --no-deps -r requirements.txt
|
||||||
|
|
||||||
FROM python:3.11.4-slim-bullseye@sha256:91d194f58f50594cda71dcd2e8fdefd90e7ecc57d07823813b67c8521e565dcd
|
FROM python:3.11-slim-bookworm
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends tini && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y --no-install-recommends tini libmimalloc2.0 && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production \
|
||||||
@@ -27,6 +27,7 @@ ENV NODE_ENV=production \
|
|||||||
PYTHONPATH=/usr/src
|
PYTHONPATH=/usr/src
|
||||||
|
|
||||||
COPY --from=builder /opt/venv /opt/venv
|
COPY --from=builder /opt/venv /opt/venv
|
||||||
|
COPY start.sh log_conf.json ./
|
||||||
COPY app .
|
COPY app .
|
||||||
ENTRYPOINT ["tini", "--"]
|
ENTRYPOINT ["tini", "--"]
|
||||||
CMD ["python", "-m", "app.main"]
|
CMD ["./start.sh"]
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ Be sure to commit the `poetry.lock` and `pyproject.toml` files to reflect any ch
|
|||||||
|
|
||||||
To measure inference throughput and latency, you can use [Locust](https://locust.io/) using the provided `locustfile.py`.
|
To measure inference throughput and latency, you can use [Locust](https://locust.io/) using the provided `locustfile.py`.
|
||||||
Locust works by querying the model endpoints and aggregating their statistics, meaning the app must be deployed.
|
Locust works by querying the model endpoints and aggregating their statistics, meaning the app must be deployed.
|
||||||
You can run `load_test.sh` to automatically deploy the app locally and start Locust, optionally adjusting its env variables as needed.
|
You can change the models or adjust options like score thresholds through the Locust UI.
|
||||||
|
|
||||||
Alternatively, for more custom testing, you may also run `locust` directly: see the [documentation](https://docs.locust.io/en/stable/index.html). Note that in Locust's jargon, concurrency is measured in `users`, and each user runs one task at a time. To achieve a particular per-endpoint concurrency, multiply that number by the number of endpoints to be queried. For example, if there are 3 endpoints and you want each of them to receive 8 requests at a time, you should set the number of users to 24.
|
To get started, you can simply run `locust --web-host 127.0.0.1` and open `localhost:8089` in a browser to access the UI. See the [Locust documentation](https://docs.locust.io/en/stable/index.html) for more info on running Locust.
|
||||||
|
|
||||||
|
Note that in Locust's jargon, concurrency is measured in `users`, and each user runs one task at a time. To achieve a particular per-endpoint concurrency, multiply that number by the number of endpoints to be queried. For example, if there are 3 endpoints and you want each of them to receive 8 requests at a time, you should set the number of users to 24.
|
||||||
22
machine-learning/README_fr_FR.md
Normal file
22
machine-learning/README_fr_FR.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Immich Apprentissage machine
|
||||||
|
|
||||||
|
- Classification d'images
|
||||||
|
- Embarquement de CLIP
|
||||||
|
- Reconnaissance faciale
|
||||||
|
|
||||||
|
# Mise en place
|
||||||
|
|
||||||
|
Ce projet utilise [Poetry](https://python-poetry.org/docs/#installation), donc soyez certain de l'installer en premier.
|
||||||
|
Exécuter `poetry install --no-root --with dev` installera tout ce dont vous avez besoin dans un environnement virtuel isolé.
|
||||||
|
|
||||||
|
Pour ajouter ou supprimer des dépendances, vous pouvez utiliser les commandes `poetry add $PACKAGE_NAME` et `poetry remove $PACKAGE_NAME` respectivement.
|
||||||
|
Soyez sûr de commit les fichiers `poetry.lock` et `pyproject.toml` pour refléter les changements de dépendances.
|
||||||
|
|
||||||
|
|
||||||
|
# Test de charge
|
||||||
|
|
||||||
|
Pour mesurer le débit d'inférence et la latence, vous pouvez utiliser [Locust](https://locust.io/) avec le fichier fourni `locustfile.py`.
|
||||||
|
Locust fonctionne en interrogeant les endpoints des modèles et en aggrégeant leurs statistiques, signifiant que l'application doit être déployée.
|
||||||
|
Vous pouvez exécuter `load_test.sh` pour automatiquement déployer l'application localement et démarrer Locust, en ajustant si besoin ses variables d'environnement.
|
||||||
|
|
||||||
|
En alternative, pour réaliser plus de tests customisés, vous pourriez aussi exécuter `locust` directement : voir la [documentation](https://docs.locust.io/en/stable/index.html). Notez que dans le jargon de Locust, la concurrence est mesurée en `users` et que chaque user exécute une tâche après l'autre. Pour parvenir à une concurrence par endpoint, multipliez ce nombre par le nombre d'endpoints à interroger. Par exemple, s'il y a 3 endpoints et que vous voulez que chacun d'entre eux reçoive 8 requêtes à la fois, vous devrez mettre ce nombre d'users à 24.
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import gunicorn
|
||||||
|
import starlette
|
||||||
from pydantic import BaseSettings
|
from pydantic import BaseSettings
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.logging import RichHandler
|
||||||
|
|
||||||
from .schemas import ModelType
|
from .schemas import ModelType
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
cache_folder: str = "/cache"
|
cache_folder: str = "/cache"
|
||||||
eager_startup: bool = False
|
|
||||||
model_ttl: int = 0
|
model_ttl: int = 0
|
||||||
host: str = "0.0.0.0"
|
host: str = "0.0.0.0"
|
||||||
port: int = 3003
|
port: int = 3003
|
||||||
@@ -23,6 +27,14 @@ class Settings(BaseSettings):
|
|||||||
case_sensitive = False
|
case_sensitive = False
|
||||||
|
|
||||||
|
|
||||||
|
class LogSettings(BaseSettings):
|
||||||
|
log_level: str = "info"
|
||||||
|
no_color: bool = False
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
case_sensitive = False
|
||||||
|
|
||||||
|
|
||||||
_clean_name = str.maketrans(":\\/", "___", ".")
|
_clean_name = str.maketrans(":\\/", "___", ".")
|
||||||
|
|
||||||
|
|
||||||
@@ -30,4 +42,28 @@ def get_cache_dir(model_name: str, model_type: ModelType) -> Path:
|
|||||||
return Path(settings.cache_folder) / model_type.value / model_name.translate(_clean_name)
|
return Path(settings.cache_folder) / model_type.value / model_name.translate(_clean_name)
|
||||||
|
|
||||||
|
|
||||||
|
LOG_LEVELS: dict[str, int] = {
|
||||||
|
"critical": logging.ERROR,
|
||||||
|
"error": logging.ERROR,
|
||||||
|
"warning": logging.WARNING,
|
||||||
|
"warn": logging.WARNING,
|
||||||
|
"info": logging.INFO,
|
||||||
|
"log": logging.INFO,
|
||||||
|
"debug": logging.DEBUG,
|
||||||
|
"verbose": logging.DEBUG,
|
||||||
|
}
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
log_settings = LogSettings()
|
||||||
|
|
||||||
|
|
||||||
|
class CustomRichHandler(RichHandler):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
console = Console(color_system="standard", no_color=log_settings.no_color)
|
||||||
|
super().__init__(
|
||||||
|
show_path=False, omit_repeated_times=False, console=console, tracebacks_suppress=[gunicorn, starlette]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger("gunicorn.access")
|
||||||
|
log.setLevel(LOG_LEVELS.get(log_settings.log_level.lower(), logging.INFO))
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from typing import Iterator, TypeAlias
|
import json
|
||||||
|
from typing import Any, Iterator, TypeAlias
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@@ -31,3 +32,8 @@ def mock_get_model() -> Iterator[mock.Mock]:
|
|||||||
def deployed_app() -> TestClient:
|
def deployed_app() -> TestClient:
|
||||||
init_state()
|
init_state()
|
||||||
return TestClient(app)
|
return TestClient(app)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def responses() -> dict[str, Any]:
|
||||||
|
return json.load(open("responses.json", "r"))
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import threading
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from zipfile import BadZipFile
|
||||||
|
|
||||||
import orjson
|
import orjson
|
||||||
import uvicorn
|
|
||||||
from fastapi import FastAPI, Form, HTTPException, UploadFile
|
from fastapi import FastAPI, Form, HTTPException, UploadFile
|
||||||
from fastapi.responses import ORJSONResponse
|
from fastapi.responses import ORJSONResponse
|
||||||
|
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf, NoSuchFile # type: ignore
|
||||||
from starlette.formparsers import MultiPartParser
|
from starlette.formparsers import MultiPartParser
|
||||||
|
|
||||||
from app.models.base import InferenceModel
|
from app.models.base import InferenceModel
|
||||||
|
|
||||||
from .config import settings
|
from .config import log, settings
|
||||||
from .models.cache import ModelCache
|
from .models.cache import ModelCache
|
||||||
from .schemas import (
|
from .schemas import (
|
||||||
MessageResponse,
|
MessageResponse,
|
||||||
@@ -20,14 +21,21 @@ from .schemas import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
MultiPartParser.max_file_size = 2**24 # spools to disk if payload is 16 MiB or larger
|
MultiPartParser.max_file_size = 2**24 # spools to disk if payload is 16 MiB or larger
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
def init_state() -> None:
|
def init_state() -> None:
|
||||||
app.state.model_cache = ModelCache(ttl=settings.model_ttl, revalidate=settings.model_ttl > 0)
|
app.state.model_cache = ModelCache(ttl=settings.model_ttl, revalidate=settings.model_ttl > 0)
|
||||||
|
log.info(
|
||||||
|
(
|
||||||
|
"Created in-memory cache with unloading "
|
||||||
|
f"{f'after {settings.model_ttl}s of inactivity' if settings.model_ttl > 0 else 'disabled'}."
|
||||||
|
)
|
||||||
|
)
|
||||||
# asyncio is a huge bottleneck for performance, so we use a thread pool to run blocking code
|
# asyncio is a huge bottleneck for performance, so we use a thread pool to run blocking code
|
||||||
app.state.thread_pool = ThreadPoolExecutor(settings.request_threads)
|
app.state.thread_pool = ThreadPoolExecutor(settings.request_threads) if settings.request_threads > 0 else None
|
||||||
|
app.state.locks = {model_type: threading.Lock() for model_type in ModelType}
|
||||||
|
log.info(f"Initialized request thread pool with {settings.request_threads} threads.")
|
||||||
|
|
||||||
|
|
||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
@@ -59,22 +67,49 @@ async def predict(
|
|||||||
inputs = text
|
inputs = text
|
||||||
else:
|
else:
|
||||||
raise HTTPException(400, "Either image or text must be provided")
|
raise HTTPException(400, "Either image or text must be provided")
|
||||||
|
try:
|
||||||
|
kwargs = orjson.loads(options)
|
||||||
|
except orjson.JSONDecodeError:
|
||||||
|
raise HTTPException(400, f"Invalid options JSON: {options}")
|
||||||
|
|
||||||
model: InferenceModel = await app.state.model_cache.get(model_name, model_type, **orjson.loads(options))
|
model = await load(await app.state.model_cache.get(model_name, model_type, **kwargs))
|
||||||
|
model.configure(**kwargs)
|
||||||
outputs = await run(model, inputs)
|
outputs = await run(model, inputs)
|
||||||
return ORJSONResponse(outputs)
|
return ORJSONResponse(outputs)
|
||||||
|
|
||||||
|
|
||||||
async def run(model: InferenceModel, inputs: Any) -> Any:
|
async def run(model: InferenceModel, inputs: Any) -> Any:
|
||||||
|
if app.state.thread_pool is None:
|
||||||
|
return model.predict(inputs)
|
||||||
|
|
||||||
return await asyncio.get_running_loop().run_in_executor(app.state.thread_pool, model.predict, inputs)
|
return await asyncio.get_running_loop().run_in_executor(app.state.thread_pool, model.predict, inputs)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
async def load(model: InferenceModel) -> InferenceModel:
|
||||||
is_dev = os.getenv("NODE_ENV") == "development"
|
if model.loaded:
|
||||||
uvicorn.run(
|
return model
|
||||||
"app.main:app",
|
|
||||||
host=settings.host,
|
def _load() -> None:
|
||||||
port=settings.port,
|
with app.state.locks[model.model_type]:
|
||||||
reload=is_dev,
|
model.load()
|
||||||
workers=settings.workers,
|
|
||||||
)
|
loop = asyncio.get_running_loop()
|
||||||
|
try:
|
||||||
|
if app.state.thread_pool is None:
|
||||||
|
model.load()
|
||||||
|
else:
|
||||||
|
await loop.run_in_executor(app.state.thread_pool, _load)
|
||||||
|
return model
|
||||||
|
except (OSError, InvalidProtobuf, BadZipFile, NoSuchFile):
|
||||||
|
log.warn(
|
||||||
|
(
|
||||||
|
f"Failed to load {model.model_type.replace('_', ' ')} model '{model.model_name}'."
|
||||||
|
"Clearing cache and retrying."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
model.clear_cache()
|
||||||
|
if app.state.thread_pool is None:
|
||||||
|
model.load()
|
||||||
|
else:
|
||||||
|
await loop.run_in_executor(app.state.thread_pool, _load)
|
||||||
|
return model
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
import pickle
|
import pickle
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from zipfile import BadZipFile
|
|
||||||
|
|
||||||
import onnxruntime as ort
|
import onnxruntime as ort
|
||||||
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf # type: ignore
|
|
||||||
|
|
||||||
from ..config import get_cache_dir, settings
|
from ..config import get_cache_dir, log, settings
|
||||||
from ..schemas import ModelType
|
from ..schemas import ModelType
|
||||||
|
|
||||||
|
|
||||||
@@ -22,48 +19,54 @@ class InferenceModel(ABC):
|
|||||||
self,
|
self,
|
||||||
model_name: str,
|
model_name: str,
|
||||||
cache_dir: Path | str | None = None,
|
cache_dir: Path | str | None = None,
|
||||||
eager: bool = True,
|
|
||||||
inter_op_num_threads: int = settings.model_inter_op_threads,
|
inter_op_num_threads: int = settings.model_inter_op_threads,
|
||||||
intra_op_num_threads: int = settings.model_intra_op_threads,
|
intra_op_num_threads: int = settings.model_intra_op_threads,
|
||||||
**model_kwargs: Any,
|
**model_kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.model_name = model_name
|
self.model_name = model_name
|
||||||
self._loaded = False
|
self.loaded = False
|
||||||
self._cache_dir = Path(cache_dir) if cache_dir is not None else get_cache_dir(model_name, self.model_type)
|
self._cache_dir = Path(cache_dir) if cache_dir is not None else get_cache_dir(model_name, self.model_type)
|
||||||
loader = self.load if eager else self.download
|
|
||||||
|
|
||||||
self.providers = model_kwargs.pop("providers", ["CPUExecutionProvider"])
|
self.providers = model_kwargs.pop("providers", ["CPUExecutionProvider"])
|
||||||
# don't pre-allocate more memory than needed
|
# don't pre-allocate more memory than needed
|
||||||
self.provider_options = model_kwargs.pop(
|
self.provider_options = model_kwargs.pop(
|
||||||
"provider_options", [{"arena_extend_strategy": "kSameAsRequested"}] * len(self.providers)
|
"provider_options", [{"arena_extend_strategy": "kSameAsRequested"}] * len(self.providers)
|
||||||
)
|
)
|
||||||
|
log.debug(
|
||||||
|
(
|
||||||
|
f"Setting '{self.model_name}' execution providers to {self.providers}"
|
||||||
|
"in descending order of preference"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
log.debug(f"Setting execution provider options to {self.provider_options}")
|
||||||
self.sess_options = PicklableSessionOptions()
|
self.sess_options = PicklableSessionOptions()
|
||||||
# avoid thread contention between models
|
# avoid thread contention between models
|
||||||
if inter_op_num_threads > 1:
|
if inter_op_num_threads > 1:
|
||||||
self.sess_options.execution_mode = ort.ExecutionMode.ORT_PARALLEL
|
self.sess_options.execution_mode = ort.ExecutionMode.ORT_PARALLEL
|
||||||
|
|
||||||
|
log.debug(f"Setting execution_mode to {self.sess_options.execution_mode.name}")
|
||||||
|
log.debug(f"Setting inter_op_num_threads to {inter_op_num_threads}")
|
||||||
|
log.debug(f"Setting intra_op_num_threads to {intra_op_num_threads}")
|
||||||
self.sess_options.inter_op_num_threads = inter_op_num_threads
|
self.sess_options.inter_op_num_threads = inter_op_num_threads
|
||||||
self.sess_options.intra_op_num_threads = intra_op_num_threads
|
self.sess_options.intra_op_num_threads = intra_op_num_threads
|
||||||
|
self.sess_options.enable_cpu_mem_arena = False
|
||||||
|
|
||||||
try:
|
def download(self) -> None:
|
||||||
loader(**model_kwargs)
|
|
||||||
except (OSError, InvalidProtobuf, BadZipFile):
|
|
||||||
self.clear_cache()
|
|
||||||
loader(**model_kwargs)
|
|
||||||
|
|
||||||
def download(self, **model_kwargs: Any) -> None:
|
|
||||||
if not self.cached:
|
if not self.cached:
|
||||||
print(f"Downloading {self.model_type.value.replace('_', ' ')} model. This may take a while...")
|
log.info(
|
||||||
self._download(**model_kwargs)
|
(f"Downloading {self.model_type.replace('-', ' ')} model '{self.model_name}'." "This may take a while.")
|
||||||
|
)
|
||||||
|
self._download()
|
||||||
|
|
||||||
def load(self, **model_kwargs: Any) -> None:
|
def load(self) -> None:
|
||||||
self.download(**model_kwargs)
|
if self.loaded:
|
||||||
self._load(**model_kwargs)
|
return
|
||||||
self._loaded = True
|
self.download()
|
||||||
|
log.info(f"Loading {self.model_type.replace('-', ' ')} model '{self.model_name}'")
|
||||||
|
self._load()
|
||||||
|
self.loaded = True
|
||||||
|
|
||||||
def predict(self, inputs: Any, **model_kwargs: Any) -> Any:
|
def predict(self, inputs: Any, **model_kwargs: Any) -> Any:
|
||||||
if not self._loaded:
|
self.load()
|
||||||
print(f"Loading {self.model_type.value.replace('_', ' ')} model...")
|
|
||||||
self.load()
|
|
||||||
if model_kwargs:
|
if model_kwargs:
|
||||||
self.configure(**model_kwargs)
|
self.configure(**model_kwargs)
|
||||||
return self._predict(inputs)
|
return self._predict(inputs)
|
||||||
@@ -76,11 +79,11 @@ class InferenceModel(ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _download(self, **model_kwargs: Any) -> None:
|
def _download(self) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _load(self, **model_kwargs: Any) -> None:
|
def _load(self) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -109,13 +112,23 @@ class InferenceModel(ABC):
|
|||||||
|
|
||||||
def clear_cache(self) -> None:
|
def clear_cache(self) -> None:
|
||||||
if not self.cache_dir.exists():
|
if not self.cache_dir.exists():
|
||||||
|
log.warn(
|
||||||
|
f"Attempted to clear cache for model '{self.model_name}' but cache directory does not exist.",
|
||||||
|
)
|
||||||
return
|
return
|
||||||
if not rmtree.avoids_symlink_attacks:
|
if not rmtree.avoids_symlink_attacks:
|
||||||
raise RuntimeError("Attempted to clear cache, but rmtree is not safe on this platform.")
|
raise RuntimeError("Attempted to clear cache, but rmtree is not safe on this platform.")
|
||||||
|
|
||||||
if self.cache_dir.is_dir():
|
if self.cache_dir.is_dir():
|
||||||
|
log.info(f"Cleared cache directory for model '{self.model_name}'.")
|
||||||
rmtree(self.cache_dir)
|
rmtree(self.cache_dir)
|
||||||
else:
|
else:
|
||||||
|
log.warn(
|
||||||
|
(
|
||||||
|
f"Encountered file instead of directory at cache path "
|
||||||
|
f"for '{self.model_name}'. Removing file and replacing with a directory."
|
||||||
|
),
|
||||||
|
)
|
||||||
self.cache_dir.unlink()
|
self.cache_dir.unlink()
|
||||||
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class ModelCache:
|
|||||||
revalidate: bool = False,
|
revalidate: bool = False,
|
||||||
timeout: int | None = None,
|
timeout: int | None = None,
|
||||||
profiling: bool = False,
|
profiling: bool = False,
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
ttl: Unloads model after this duration. Disabled if None. Defaults to None.
|
ttl: Unloads model after this duration. Disabled if None. Defaults to None.
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from clip_server.model.tokenization import Tokenizer
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
from torchvision.transforms import CenterCrop, Compose, Normalize, Resize, ToTensor
|
from torchvision.transforms import CenterCrop, Compose, Normalize, Resize, ToTensor
|
||||||
|
|
||||||
|
from ..config import log
|
||||||
from ..schemas import ModelType
|
from ..schemas import ModelType
|
||||||
from .base import InferenceModel
|
from .base import InferenceModel
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ class CLIPEncoder(InferenceModel):
|
|||||||
jina_model_name = self._get_jina_model_name(model_name)
|
jina_model_name = self._get_jina_model_name(model_name)
|
||||||
super().__init__(jina_model_name, cache_dir, **model_kwargs)
|
super().__init__(jina_model_name, cache_dir, **model_kwargs)
|
||||||
|
|
||||||
def _download(self, **model_kwargs: Any) -> None:
|
def _download(self) -> None:
|
||||||
models: tuple[tuple[str, str], tuple[str, str]] = _MODELS[self.model_name]
|
models: tuple[tuple[str, str], tuple[str, str]] = _MODELS[self.model_name]
|
||||||
text_onnx_path = self.cache_dir / "textual.onnx"
|
text_onnx_path = self.cache_dir / "textual.onnx"
|
||||||
vision_onnx_path = self.cache_dir / "visual.onnx"
|
vision_onnx_path = self.cache_dir / "visual.onnx"
|
||||||
@@ -52,8 +53,9 @@ class CLIPEncoder(InferenceModel):
|
|||||||
if not vision_onnx_path.is_file():
|
if not vision_onnx_path.is_file():
|
||||||
self._download_model(*models[1])
|
self._download_model(*models[1])
|
||||||
|
|
||||||
def _load(self, **model_kwargs: Any) -> None:
|
def _load(self) -> None:
|
||||||
if self.mode == "text" or self.mode is None:
|
if self.mode == "text" or self.mode is None:
|
||||||
|
log.debug(f"Loading clip text model '{self.model_name}'")
|
||||||
self.text_model = ort.InferenceSession(
|
self.text_model = ort.InferenceSession(
|
||||||
self.cache_dir / "textual.onnx",
|
self.cache_dir / "textual.onnx",
|
||||||
sess_options=self.sess_options,
|
sess_options=self.sess_options,
|
||||||
@@ -64,6 +66,7 @@ class CLIPEncoder(InferenceModel):
|
|||||||
self.tokenizer = Tokenizer(self.model_name)
|
self.tokenizer = Tokenizer(self.model_name)
|
||||||
|
|
||||||
if self.mode == "vision" or self.mode is None:
|
if self.mode == "vision" or self.mode is None:
|
||||||
|
log.debug(f"Loading clip vision model '{self.model_name}'")
|
||||||
self.vision_model = ort.InferenceSession(
|
self.vision_model = ort.InferenceSession(
|
||||||
self.cache_dir / "visual.onnx",
|
self.cache_dir / "visual.onnx",
|
||||||
sess_options=self.sess_options,
|
sess_options=self.sess_options,
|
||||||
@@ -105,9 +108,11 @@ class CLIPEncoder(InferenceModel):
|
|||||||
if model_name in _MODELS:
|
if model_name in _MODELS:
|
||||||
return model_name
|
return model_name
|
||||||
elif model_name in _ST_TO_JINA_MODEL_NAME:
|
elif model_name in _ST_TO_JINA_MODEL_NAME:
|
||||||
print(
|
log.warn(
|
||||||
(f"Warning: Sentence-Transformer model names such as '{model_name}' are no longer supported."),
|
(
|
||||||
(f"Using '{_ST_TO_JINA_MODEL_NAME[model_name]}' instead as it is the best match for '{model_name}'."),
|
f"Sentence-Transformer models like '{model_name}' are not supported."
|
||||||
|
f"Using '{_ST_TO_JINA_MODEL_NAME[model_name]}' instead as it is the best match for '{model_name}'."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
return _ST_TO_JINA_MODEL_NAME[model_name]
|
return _ST_TO_JINA_MODEL_NAME[model_name]
|
||||||
else:
|
else:
|
||||||
@@ -128,6 +133,10 @@ class CLIPEncoder(InferenceModel):
|
|||||||
os.remove(file)
|
os.remove(file)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cached(self) -> bool:
|
||||||
|
return (self.cache_dir / "textual.onnx").is_file() and (self.cache_dir / "visual.onnx").is_file()
|
||||||
|
|
||||||
|
|
||||||
# same as `_transform_blob` without `_blob2image`
|
# same as `_transform_blob` without `_blob2image`
|
||||||
def _transform_pil_image(n_px: int) -> Compose:
|
def _transform_pil_image(n_px: int) -> Compose:
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ class FaceRecognizer(InferenceModel):
|
|||||||
cache_dir: Path | str | None = None,
|
cache_dir: Path | str | None = None,
|
||||||
**model_kwargs: Any,
|
**model_kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.min_score = min_score
|
self.min_score = model_kwargs.pop("minScore", min_score)
|
||||||
super().__init__(model_name, cache_dir, **model_kwargs)
|
super().__init__(model_name, cache_dir, **model_kwargs)
|
||||||
|
|
||||||
def _download(self, **model_kwargs: Any) -> None:
|
def _download(self) -> None:
|
||||||
zip_file = self.cache_dir / f"{self.model_name}.zip"
|
zip_file = self.cache_dir / f"{self.model_name}.zip"
|
||||||
download_file(f"{BASE_REPO_URL}/{self.model_name}.zip", zip_file)
|
download_file(f"{BASE_REPO_URL}/{self.model_name}.zip", zip_file)
|
||||||
with zipfile.ZipFile(zip_file, "r") as zip:
|
with zipfile.ZipFile(zip_file, "r") as zip:
|
||||||
@@ -36,7 +36,7 @@ class FaceRecognizer(InferenceModel):
|
|||||||
zip.extractall(self.cache_dir, members=[det_file, rec_file])
|
zip.extractall(self.cache_dir, members=[det_file, rec_file])
|
||||||
zip_file.unlink()
|
zip_file.unlink()
|
||||||
|
|
||||||
def _load(self, **model_kwargs: Any) -> None:
|
def _load(self) -> None:
|
||||||
try:
|
try:
|
||||||
det_file = next(self.cache_dir.glob("det_*.onnx"))
|
det_file = next(self.cache_dir.glob("det_*.onnx"))
|
||||||
rec_file = next(self.cache_dir.glob("w600k_*.onnx"))
|
rec_file = next(self.cache_dir.glob("w600k_*.onnx"))
|
||||||
@@ -105,4 +105,4 @@ class FaceRecognizer(InferenceModel):
|
|||||||
return self.cache_dir.is_dir() and any(self.cache_dir.glob("*.onnx"))
|
return self.cache_dir.is_dir() and any(self.cache_dir.glob("*.onnx"))
|
||||||
|
|
||||||
def configure(self, **model_kwargs: Any) -> None:
|
def configure(self, **model_kwargs: Any) -> None:
|
||||||
self.det_model.det_thresh = model_kwargs.get("min_score", self.det_model.det_thresh)
|
self.det_model.det_thresh = model_kwargs.pop("minScore", self.det_model.det_thresh)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from optimum.pipelines import pipeline
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
from transformers import AutoImageProcessor
|
from transformers import AutoImageProcessor
|
||||||
|
|
||||||
|
from ..config import log
|
||||||
from ..schemas import ModelType
|
from ..schemas import ModelType
|
||||||
from .base import InferenceModel
|
from .base import InferenceModel
|
||||||
|
|
||||||
@@ -22,10 +23,10 @@ class ImageClassifier(InferenceModel):
|
|||||||
cache_dir: Path | str | None = None,
|
cache_dir: Path | str | None = None,
|
||||||
**model_kwargs: Any,
|
**model_kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.min_score = min_score
|
self.min_score = model_kwargs.pop("minScore", min_score)
|
||||||
super().__init__(model_name, cache_dir, **model_kwargs)
|
super().__init__(model_name, cache_dir, **model_kwargs)
|
||||||
|
|
||||||
def _download(self, **model_kwargs: Any) -> None:
|
def _download(self) -> None:
|
||||||
snapshot_download(
|
snapshot_download(
|
||||||
cache_dir=self.cache_dir,
|
cache_dir=self.cache_dir,
|
||||||
repo_id=self.model_name,
|
repo_id=self.model_name,
|
||||||
@@ -34,20 +35,26 @@ class ImageClassifier(InferenceModel):
|
|||||||
local_dir_use_symlinks=True,
|
local_dir_use_symlinks=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _load(self, **model_kwargs: Any) -> None:
|
def _load(self) -> None:
|
||||||
processor = AutoImageProcessor.from_pretrained(self.cache_dir)
|
processor = AutoImageProcessor.from_pretrained(self.cache_dir, cache_dir=self.cache_dir)
|
||||||
model_kwargs |= {
|
model_path = self.cache_dir / "model.onnx"
|
||||||
|
model_kwargs = {
|
||||||
"cache_dir": self.cache_dir,
|
"cache_dir": self.cache_dir,
|
||||||
"provider": self.providers[0],
|
"provider": self.providers[0],
|
||||||
"provider_options": self.provider_options[0],
|
"provider_options": self.provider_options[0],
|
||||||
"session_options": self.sess_options,
|
"session_options": self.sess_options,
|
||||||
}
|
}
|
||||||
model_path = self.cache_dir / "model.onnx"
|
|
||||||
|
|
||||||
if model_path.exists():
|
if model_path.exists():
|
||||||
model = ORTModelForImageClassification.from_pretrained(self.cache_dir, **model_kwargs)
|
model = ORTModelForImageClassification.from_pretrained(self.cache_dir, **model_kwargs)
|
||||||
self.model = pipeline(self.model_type.value, model, feature_extractor=processor)
|
self.model = pipeline(self.model_type.value, model, feature_extractor=processor)
|
||||||
else:
|
else:
|
||||||
|
log.info(
|
||||||
|
(
|
||||||
|
f"ONNX model not found in cache directory for '{self.model_name}'."
|
||||||
|
"Exporting optimized model for future use."
|
||||||
|
),
|
||||||
|
)
|
||||||
self.sess_options.optimized_model_filepath = model_path.as_posix()
|
self.sess_options.optimized_model_filepath = model_path.as_posix()
|
||||||
self.model = pipeline(
|
self.model = pipeline(
|
||||||
self.model_type.value,
|
self.model_type.value,
|
||||||
@@ -65,4 +72,4 @@ class ImageClassifier(InferenceModel):
|
|||||||
return tags
|
return tags
|
||||||
|
|
||||||
def configure(self, **model_kwargs: Any) -> None:
|
def configure(self, **model_kwargs: Any) -> None:
|
||||||
self.min_score = model_kwargs.get("min_score", self.min_score)
|
self.min_score = model_kwargs.pop("minScore", self.min_score)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
|
import json
|
||||||
import pickle
|
import pickle
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import TypeAlias
|
from typing import Any, TypeAlias
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import onnxruntime as ort
|
|
||||||
import pytest
|
import pytest
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
@@ -31,23 +31,6 @@ class TestImageClassifier:
|
|||||||
{"label": "probably a virus", "score": 0.01},
|
{"label": "probably a virus", "score": 0.01},
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_eager_init(self, mocker: MockerFixture) -> None:
|
|
||||||
mocker.patch.object(ImageClassifier, "download")
|
|
||||||
mock_load = mocker.patch.object(ImageClassifier, "load")
|
|
||||||
classifier = ImageClassifier("test_model_name", cache_dir="test_cache", eager=True, test_arg="test_arg")
|
|
||||||
|
|
||||||
assert classifier.model_name == "test_model_name"
|
|
||||||
mock_load.assert_called_once_with(test_arg="test_arg")
|
|
||||||
|
|
||||||
def test_lazy_init(self, mocker: MockerFixture) -> None:
|
|
||||||
mock_download = mocker.patch.object(ImageClassifier, "download")
|
|
||||||
mock_load = mocker.patch.object(ImageClassifier, "load")
|
|
||||||
face_model = ImageClassifier("test_model_name", cache_dir="test_cache", eager=False, test_arg="test_arg")
|
|
||||||
|
|
||||||
assert face_model.model_name == "test_model_name"
|
|
||||||
mock_download.assert_called_once_with(test_arg="test_arg")
|
|
||||||
mock_load.assert_not_called()
|
|
||||||
|
|
||||||
def test_min_score(self, pil_image: Image.Image, mocker: MockerFixture) -> None:
|
def test_min_score(self, pil_image: Image.Image, mocker: MockerFixture) -> None:
|
||||||
mocker.patch.object(ImageClassifier, "load")
|
mocker.patch.object(ImageClassifier, "load")
|
||||||
classifier = ImageClassifier("test_model_name", min_score=0.0)
|
classifier = ImageClassifier("test_model_name", min_score=0.0)
|
||||||
@@ -74,23 +57,6 @@ class TestImageClassifier:
|
|||||||
class TestCLIP:
|
class TestCLIP:
|
||||||
embedding = np.random.rand(512).astype(np.float32)
|
embedding = np.random.rand(512).astype(np.float32)
|
||||||
|
|
||||||
def test_eager_init(self, mocker: MockerFixture) -> None:
|
|
||||||
mocker.patch.object(CLIPEncoder, "download")
|
|
||||||
mock_load = mocker.patch.object(CLIPEncoder, "load")
|
|
||||||
clip_model = CLIPEncoder("ViT-B-32::openai", cache_dir="test_cache", eager=True, test_arg="test_arg")
|
|
||||||
|
|
||||||
assert clip_model.model_name == "ViT-B-32::openai"
|
|
||||||
mock_load.assert_called_once_with(test_arg="test_arg")
|
|
||||||
|
|
||||||
def test_lazy_init(self, mocker: MockerFixture) -> None:
|
|
||||||
mock_download = mocker.patch.object(CLIPEncoder, "download")
|
|
||||||
mock_load = mocker.patch.object(CLIPEncoder, "load")
|
|
||||||
clip_model = CLIPEncoder("ViT-B-32::openai", cache_dir="test_cache", eager=False, test_arg="test_arg")
|
|
||||||
|
|
||||||
assert clip_model.model_name == "ViT-B-32::openai"
|
|
||||||
mock_download.assert_called_once_with(test_arg="test_arg")
|
|
||||||
mock_load.assert_not_called()
|
|
||||||
|
|
||||||
def test_basic_image(self, pil_image: Image.Image, mocker: MockerFixture) -> None:
|
def test_basic_image(self, pil_image: Image.Image, mocker: MockerFixture) -> None:
|
||||||
mocker.patch.object(CLIPEncoder, "download")
|
mocker.patch.object(CLIPEncoder, "download")
|
||||||
mocked = mocker.patch("app.models.clip.ort.InferenceSession", autospec=True)
|
mocked = mocker.patch("app.models.clip.ort.InferenceSession", autospec=True)
|
||||||
@@ -119,23 +85,6 @@ class TestCLIP:
|
|||||||
|
|
||||||
|
|
||||||
class TestFaceRecognition:
|
class TestFaceRecognition:
|
||||||
def test_eager_init(self, mocker: MockerFixture) -> None:
|
|
||||||
mocker.patch.object(FaceRecognizer, "download")
|
|
||||||
mock_load = mocker.patch.object(FaceRecognizer, "load")
|
|
||||||
face_model = FaceRecognizer("test_model_name", cache_dir="test_cache", eager=True, test_arg="test_arg")
|
|
||||||
|
|
||||||
assert face_model.model_name == "test_model_name"
|
|
||||||
mock_load.assert_called_once_with(test_arg="test_arg")
|
|
||||||
|
|
||||||
def test_lazy_init(self, mocker: MockerFixture) -> None:
|
|
||||||
mock_download = mocker.patch.object(FaceRecognizer, "download")
|
|
||||||
mock_load = mocker.patch.object(FaceRecognizer, "load")
|
|
||||||
face_model = FaceRecognizer("test_model_name", cache_dir="test_cache", eager=False, test_arg="test_arg")
|
|
||||||
|
|
||||||
assert face_model.model_name == "test_model_name"
|
|
||||||
mock_download.assert_called_once_with(test_arg="test_arg")
|
|
||||||
mock_load.assert_not_called()
|
|
||||||
|
|
||||||
def test_set_min_score(self, mocker: MockerFixture) -> None:
|
def test_set_min_score(self, mocker: MockerFixture) -> None:
|
||||||
mocker.patch.object(FaceRecognizer, "load")
|
mocker.patch.object(FaceRecognizer, "load")
|
||||||
face_recognizer = FaceRecognizer("test_model_name", cache_dir="test_cache", min_score=0.5)
|
face_recognizer = FaceRecognizer("test_model_name", cache_dir="test_cache", min_score=0.5)
|
||||||
@@ -220,45 +169,64 @@ class TestCache:
|
|||||||
reason="More time-consuming since it deploys the app and loads models.",
|
reason="More time-consuming since it deploys the app and loads models.",
|
||||||
)
|
)
|
||||||
class TestEndpoints:
|
class TestEndpoints:
|
||||||
def test_tagging_endpoint(self, pil_image: Image.Image, deployed_app: TestClient) -> None:
|
def test_tagging_endpoint(
|
||||||
|
self, pil_image: Image.Image, responses: dict[str, Any], deployed_app: TestClient
|
||||||
|
) -> None:
|
||||||
byte_image = BytesIO()
|
byte_image = BytesIO()
|
||||||
pil_image.save(byte_image, format="jpeg")
|
pil_image.save(byte_image, format="jpeg")
|
||||||
headers = {"Content-Type": "image/jpg"}
|
|
||||||
response = deployed_app.post(
|
response = deployed_app.post(
|
||||||
"http://localhost:3003/image-classifier/tag-image",
|
"http://localhost:3003/predict",
|
||||||
content=byte_image.getvalue(),
|
data={
|
||||||
headers=headers,
|
"modelName": "microsoft/resnet-50",
|
||||||
|
"modelType": "image-classification",
|
||||||
|
"options": json.dumps({"minScore": 0.0}),
|
||||||
|
},
|
||||||
|
files={"image": byte_image.getvalue()},
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
assert response.json() == responses["image-classification"]
|
||||||
|
|
||||||
def test_clip_image_endpoint(self, pil_image: Image.Image, deployed_app: TestClient) -> None:
|
def test_clip_image_endpoint(
|
||||||
|
self, pil_image: Image.Image, responses: dict[str, Any], deployed_app: TestClient
|
||||||
|
) -> None:
|
||||||
byte_image = BytesIO()
|
byte_image = BytesIO()
|
||||||
pil_image.save(byte_image, format="jpeg")
|
pil_image.save(byte_image, format="jpeg")
|
||||||
headers = {"Content-Type": "image/jpg"}
|
|
||||||
response = deployed_app.post(
|
response = deployed_app.post(
|
||||||
"http://localhost:3003/sentence-transformer/encode-image",
|
"http://localhost:3003/predict",
|
||||||
content=byte_image.getvalue(),
|
data={"modelName": "ViT-B-32::openai", "modelType": "clip", "options": json.dumps({"mode": "vision"})},
|
||||||
headers=headers,
|
files={"image": byte_image.getvalue()},
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
assert response.json() == responses["clip"]["image"]
|
||||||
|
|
||||||
def test_clip_text_endpoint(self, deployed_app: TestClient) -> None:
|
def test_clip_text_endpoint(self, responses: dict[str, Any], deployed_app: TestClient) -> None:
|
||||||
response = deployed_app.post(
|
response = deployed_app.post(
|
||||||
"http://localhost:3003/sentence-transformer/encode-text",
|
"http://localhost:3003/predict",
|
||||||
json={"text": "test search query"},
|
data={
|
||||||
|
"modelName": "ViT-B-32::openai",
|
||||||
|
"modelType": "clip",
|
||||||
|
"text": "test search query",
|
||||||
|
"options": json.dumps({"mode": "text"}),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
assert response.json() == responses["clip"]["text"]
|
||||||
|
|
||||||
def test_face_endpoint(self, pil_image: Image.Image, deployed_app: TestClient) -> None:
|
def test_face_endpoint(self, pil_image: Image.Image, responses: dict[str, Any], deployed_app: TestClient) -> None:
|
||||||
byte_image = BytesIO()
|
byte_image = BytesIO()
|
||||||
pil_image.save(byte_image, format="jpeg")
|
pil_image.save(byte_image, format="jpeg")
|
||||||
headers = {"Content-Type": "image/jpg"}
|
|
||||||
response = deployed_app.post(
|
response = deployed_app.post(
|
||||||
"http://localhost:3003/facial-recognition/detect-faces",
|
"http://localhost:3003/predict",
|
||||||
content=byte_image.getvalue(),
|
data={
|
||||||
headers=headers,
|
"modelName": "buffalo_l",
|
||||||
|
"modelType": "facial-recognition",
|
||||||
|
"options": json.dumps({"minScore": 0.034}),
|
||||||
|
},
|
||||||
|
files={"image": byte_image.getvalue()},
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
assert response.json() == responses["facial-recognition"]
|
||||||
|
|
||||||
|
|
||||||
def test_sess_options() -> None:
|
def test_sess_options() -> None:
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
export MACHINE_LEARNING_CACHE_FOLDER=/tmp/model_cache
|
|
||||||
export MACHINE_LEARNING_MIN_FACE_SCORE=0.034 # returns 1 face per request; setting this to 0 blows up the number of faces to the thousands
|
|
||||||
export MACHINE_LEARNING_MIN_TAG_SCORE=0.0
|
|
||||||
export PID_FILE=/tmp/locust_pid
|
|
||||||
export LOG_FILE=/tmp/gunicorn.log
|
|
||||||
export HEADLESS=false
|
|
||||||
export HOST=127.0.0.1:3003
|
|
||||||
export CONCURRENCY=4
|
|
||||||
export NUM_ENDPOINTS=3
|
|
||||||
export PYTHONPATH=app
|
|
||||||
|
|
||||||
gunicorn app.main:app --worker-class uvicorn.workers.UvicornWorker \
|
|
||||||
--bind $HOST --daemon --error-logfile $LOG_FILE --pid $PID_FILE
|
|
||||||
while true ; do
|
|
||||||
echo "Loading models..."
|
|
||||||
sleep 5
|
|
||||||
if cat $LOG_FILE | grep -q -E "startup complete"; then break; fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# "users" are assigned only one task, so multiply concurrency by the number of tasks
|
|
||||||
locust --host http://$HOST --web-host 127.0.0.1 \
|
|
||||||
--run-time 120s --users $(($CONCURRENCY * $NUM_ENDPOINTS)) $(if $HEADLESS; then echo "--headless"; fi)
|
|
||||||
|
|
||||||
if [[ -e $PID_FILE ]]; then kill $(cat $PID_FILE); fi
|
|
||||||
@@ -1,13 +1,32 @@
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
import json
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from locust import HttpUser, events, task
|
from locust import HttpUser, events, task
|
||||||
|
from locust.env import Environment
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
byte_image = BytesIO()
|
||||||
|
|
||||||
|
|
||||||
|
@events.init_command_line_parser.add_listener
|
||||||
|
def _(parser: ArgumentParser) -> None:
|
||||||
|
parser.add_argument("--tag-model", type=str, default="microsoft/resnet-50")
|
||||||
|
parser.add_argument("--clip-model", type=str, default="ViT-B-32::openai")
|
||||||
|
parser.add_argument("--face-model", type=str, default="buffalo_l")
|
||||||
|
parser.add_argument("--tag-min-score", type=int, default=0.0,
|
||||||
|
help="Returns all tags at or above this score. The default returns all tags.")
|
||||||
|
parser.add_argument("--face-min-score", type=int, default=0.034,
|
||||||
|
help=("Returns all faces at or above this score. The default returns 1 face per request; "
|
||||||
|
"setting this to 0 blows up the number of faces to the thousands."))
|
||||||
|
parser.add_argument("--image-size", type=int, default=1000)
|
||||||
|
|
||||||
|
|
||||||
@events.test_start.add_listener
|
@events.test_start.add_listener
|
||||||
def on_test_start(environment, **kwargs):
|
def on_test_start(environment: Environment, **kwargs: Any) -> None:
|
||||||
global byte_image
|
global byte_image
|
||||||
image = Image.new("RGB", (1000, 1000))
|
assert environment.parsed_options is not None
|
||||||
|
image = Image.new("RGB", (environment.parsed_options.image_size, environment.parsed_options.image_size))
|
||||||
byte_image = BytesIO()
|
byte_image = BytesIO()
|
||||||
image.save(byte_image, format="jpeg")
|
image.save(byte_image, format="jpeg")
|
||||||
|
|
||||||
@@ -19,34 +38,55 @@ class InferenceLoadTest(HttpUser):
|
|||||||
headers: dict[str, str] = {"Content-Type": "image/jpg"}
|
headers: dict[str, str] = {"Content-Type": "image/jpg"}
|
||||||
|
|
||||||
# re-use the image across all instances in a process
|
# re-use the image across all instances in a process
|
||||||
def on_start(self):
|
def on_start(self) -> None:
|
||||||
global byte_image
|
global byte_image
|
||||||
self.data = byte_image.getvalue()
|
self.data = byte_image.getvalue()
|
||||||
|
|
||||||
|
|
||||||
class ClassificationLoadTest(InferenceLoadTest):
|
class ClassificationFormDataLoadTest(InferenceLoadTest):
|
||||||
@task
|
@task
|
||||||
def classify(self):
|
def classify(self) -> None:
|
||||||
self.client.post(
|
data = [
|
||||||
"/image-classifier/tag-image", data=self.data, headers=self.headers
|
("modelName", self.environment.parsed_options.clip_model),
|
||||||
)
|
("modelType", "clip"),
|
||||||
|
("options", json.dumps({"minScore": self.environment.parsed_options.tag_min_score})),
|
||||||
|
]
|
||||||
|
files = {"image": self.data}
|
||||||
|
self.client.post("/predict", data=data, files=files)
|
||||||
|
|
||||||
|
|
||||||
class CLIPLoadTest(InferenceLoadTest):
|
class CLIPTextFormDataLoadTest(InferenceLoadTest):
|
||||||
@task
|
@task
|
||||||
def encode_image(self):
|
def encode_text(self) -> None:
|
||||||
self.client.post(
|
data = [
|
||||||
"/sentence-transformer/encode-image",
|
("modelName", self.environment.parsed_options.clip_model),
|
||||||
data=self.data,
|
("modelType", "clip"),
|
||||||
headers=self.headers,
|
("options", json.dumps({"mode": "text"})),
|
||||||
)
|
("text", "test search query")
|
||||||
|
]
|
||||||
|
self.client.post("/predict", data=data)
|
||||||
|
|
||||||
|
|
||||||
class RecognitionLoadTest(InferenceLoadTest):
|
class CLIPVisionFormDataLoadTest(InferenceLoadTest):
|
||||||
@task
|
@task
|
||||||
def recognize(self):
|
def encode_image(self) -> None:
|
||||||
self.client.post(
|
data = [
|
||||||
"/facial-recognition/detect-faces",
|
("modelName", self.environment.parsed_options.clip_model),
|
||||||
data=self.data,
|
("modelType", "clip"),
|
||||||
headers=self.headers,
|
("options", json.dumps({"mode": "vision"})),
|
||||||
)
|
]
|
||||||
|
files = {"image": self.data}
|
||||||
|
self.client.post("/predict", data=data, files=files)
|
||||||
|
|
||||||
|
|
||||||
|
class RecognitionFormDataLoadTest(InferenceLoadTest):
|
||||||
|
@task
|
||||||
|
def recognize(self) -> None:
|
||||||
|
data = [
|
||||||
|
("modelName", self.environment.parsed_options.face_model),
|
||||||
|
("modelType", "facial-recognition"),
|
||||||
|
("options", json.dumps({"minScore": self.environment.parsed_options.face_min_score})),
|
||||||
|
]
|
||||||
|
files = {"image": self.data}
|
||||||
|
|
||||||
|
self.client.post("/predict", data=data, files=files)
|
||||||
|
|||||||
17
machine-learning/log_conf.json
Normal file
17
machine-learning/log_conf.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"disable_existing_loggers": true,
|
||||||
|
"formatters": { "rich": { "show_path": false, "omit_repeated_times": false } },
|
||||||
|
"handlers": {
|
||||||
|
"console": {
|
||||||
|
"class": "app.config.CustomRichHandler",
|
||||||
|
"formatter": "rich",
|
||||||
|
"level": "INFO"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"loggers": {
|
||||||
|
"gunicorn.access": { "propagate": true },
|
||||||
|
"gunicorn.error": { "propagate": true }
|
||||||
|
},
|
||||||
|
"root": { "handlers": ["console"] }
|
||||||
|
}
|
||||||
144
machine-learning/poetry.lock
generated
144
machine-learning/poetry.lock
generated
@@ -164,13 +164,13 @@ tests = ["pytest"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
version = "3.7.1"
|
version = "4.0.0"
|
||||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"},
|
{file = "anyio-4.0.0-py3-none-any.whl", hash = "sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f"},
|
||||||
{file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"},
|
{file = "anyio-4.0.0.tar.gz", hash = "sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -178,9 +178,9 @@ idna = ">=2.8"
|
|||||||
sniffio = ">=1.1"
|
sniffio = ">=1.1"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"]
|
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"]
|
||||||
test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
|
test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
|
||||||
trio = ["trio (<0.22)"]
|
trio = ["trio (>=0.22)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-timeout"
|
name = "async-timeout"
|
||||||
@@ -874,18 +874,18 @@ test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==23.1.0)", "coverage[toml] (>=6
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filelock"
|
name = "filelock"
|
||||||
version = "3.12.2"
|
version = "3.12.3"
|
||||||
description = "A platform independent file lock."
|
description = "A platform independent file lock."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"},
|
{file = "filelock-3.12.3-py3-none-any.whl", hash = "sha256:f067e40ccc40f2b48395a80fcbd4728262fab54e232e090a4063ab804179efeb"},
|
||||||
{file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"},
|
{file = "filelock-3.12.3.tar.gz", hash = "sha256:0ecc1dd2ec4672a10c8550a8182f1bd0c0a5088470ecd5a125e45f49472fac3d"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
|
docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"]
|
||||||
testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"]
|
testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flask"
|
name = "flask"
|
||||||
@@ -1453,17 +1453,17 @@ test = ["objgraph", "psutil"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gunicorn"
|
name = "gunicorn"
|
||||||
version = "20.1.0"
|
version = "21.2.0"
|
||||||
description = "WSGI HTTP Server for UNIX"
|
description = "WSGI HTTP Server for UNIX"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.5"
|
python-versions = ">=3.5"
|
||||||
files = [
|
files = [
|
||||||
{file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"},
|
{file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"},
|
||||||
{file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"},
|
{file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
setuptools = ">=3.0"
|
packaging = "*"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
eventlet = ["eventlet (>=0.24.1)"]
|
eventlet = ["eventlet (>=0.24.1)"]
|
||||||
@@ -2619,69 +2619,61 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pandas"
|
name = "pandas"
|
||||||
version = "2.0.3"
|
version = "2.1.0"
|
||||||
description = "Powerful data structures for data analysis, time series, and statistics"
|
description = "Powerful data structures for data analysis, time series, and statistics"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"},
|
{file = "pandas-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:40dd20439ff94f1b2ed55b393ecee9cb6f3b08104c2c40b0cb7186a2f0046242"},
|
||||||
{file = "pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"},
|
{file = "pandas-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d4f38e4fedeba580285eaac7ede4f686c6701a9e618d8a857b138a126d067f2f"},
|
||||||
{file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183"},
|
{file = "pandas-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e6a0fe052cf27ceb29be9429428b4918f3740e37ff185658f40d8702f0b3e09"},
|
||||||
{file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0"},
|
{file = "pandas-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d81e1813191070440d4c7a413cb673052b3b4a984ffd86b8dd468c45742d3cc"},
|
||||||
{file = "pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210"},
|
{file = "pandas-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eb20252720b1cc1b7d0b2879ffc7e0542dd568f24d7c4b2347cb035206936421"},
|
||||||
{file = "pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e"},
|
{file = "pandas-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:38f74ef7ebc0ffb43b3d633e23d74882bce7e27bfa09607f3c5d3e03ffd9a4a5"},
|
||||||
{file = "pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8"},
|
{file = "pandas-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cda72cc8c4761c8f1d97b169661f23a86b16fdb240bdc341173aee17e4d6cedd"},
|
||||||
{file = "pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26"},
|
{file = "pandas-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d97daeac0db8c993420b10da4f5f5b39b01fc9ca689a17844e07c0a35ac96b4b"},
|
||||||
{file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d"},
|
{file = "pandas-2.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8c58b1113892e0c8078f006a167cc210a92bdae23322bb4614f2f0b7a4b510f"},
|
||||||
{file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df"},
|
{file = "pandas-2.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:629124923bcf798965b054a540f9ccdfd60f71361255c81fa1ecd94a904b9dd3"},
|
||||||
{file = "pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd"},
|
{file = "pandas-2.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:70cf866af3ab346a10debba8ea78077cf3a8cd14bd5e4bed3d41555a3280041c"},
|
||||||
{file = "pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b"},
|
{file = "pandas-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d53c8c1001f6a192ff1de1efe03b31a423d0eee2e9e855e69d004308e046e694"},
|
||||||
{file = "pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061"},
|
{file = "pandas-2.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:86f100b3876b8c6d1a2c66207288ead435dc71041ee4aea789e55ef0e06408cb"},
|
||||||
{file = "pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5"},
|
{file = "pandas-2.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28f330845ad21c11db51e02d8d69acc9035edfd1116926ff7245c7215db57957"},
|
||||||
{file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089"},
|
{file = "pandas-2.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9a6ccf0963db88f9b12df6720e55f337447aea217f426a22d71f4213a3099a6"},
|
||||||
{file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0"},
|
{file = "pandas-2.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d99e678180bc59b0c9443314297bddce4ad35727a1a2656dbe585fd78710b3b9"},
|
||||||
{file = "pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"},
|
{file = "pandas-2.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b31da36d376d50a1a492efb18097b9101bdbd8b3fbb3f49006e02d4495d4c644"},
|
||||||
{file = "pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78"},
|
{file = "pandas-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0164b85937707ec7f70b34a6c3a578dbf0f50787f910f21ca3b26a7fd3363437"},
|
||||||
{file = "pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b"},
|
{file = "pandas-2.1.0.tar.gz", hash = "sha256:62c24c7fc59e42b775ce0679cfa7b14a5f9bfb7643cfbe708c960699e05fb918"},
|
||||||
{file = "pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e"},
|
|
||||||
{file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b"},
|
|
||||||
{file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641"},
|
|
||||||
{file = "pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682"},
|
|
||||||
{file = "pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc"},
|
|
||||||
{file = "pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
numpy = [
|
numpy = {version = ">=1.23.2", markers = "python_version >= \"3.11\""}
|
||||||
{version = ">=1.21.0", markers = "python_version >= \"3.10\""},
|
|
||||||
{version = ">=1.23.2", markers = "python_version >= \"3.11\""},
|
|
||||||
]
|
|
||||||
python-dateutil = ">=2.8.2"
|
python-dateutil = ">=2.8.2"
|
||||||
pytz = ">=2020.1"
|
pytz = ">=2020.1"
|
||||||
tzdata = ">=2022.1"
|
tzdata = ">=2022.1"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
all = ["PyQt5 (>=5.15.1)", "SQLAlchemy (>=1.4.16)", "beautifulsoup4 (>=4.9.3)", "bottleneck (>=1.3.2)", "brotlipy (>=0.7.0)", "fastparquet (>=0.6.3)", "fsspec (>=2021.07.0)", "gcsfs (>=2021.07.0)", "html5lib (>=1.1)", "hypothesis (>=6.34.2)", "jinja2 (>=3.0.0)", "lxml (>=4.6.3)", "matplotlib (>=3.6.1)", "numba (>=0.53.1)", "numexpr (>=2.7.3)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pandas-gbq (>=0.15.0)", "psycopg2 (>=2.8.6)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "python-snappy (>=0.6.0)", "pyxlsb (>=1.0.8)", "qtpy (>=2.2.0)", "s3fs (>=2021.08.0)", "scipy (>=1.7.1)", "tables (>=3.6.1)", "tabulate (>=0.8.9)", "xarray (>=0.21.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)", "zstandard (>=0.15.2)"]
|
all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"]
|
||||||
aws = ["s3fs (>=2021.08.0)"]
|
aws = ["s3fs (>=2022.05.0)"]
|
||||||
clipboard = ["PyQt5 (>=5.15.1)", "qtpy (>=2.2.0)"]
|
clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"]
|
||||||
compression = ["brotlipy (>=0.7.0)", "python-snappy (>=0.6.0)", "zstandard (>=0.15.2)"]
|
compression = ["zstandard (>=0.17.0)"]
|
||||||
computation = ["scipy (>=1.7.1)", "xarray (>=0.21.0)"]
|
computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"]
|
||||||
excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pyxlsb (>=1.0.8)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)"]
|
consortium-standard = ["dataframe-api-compat (>=0.1.7)"]
|
||||||
|
excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"]
|
||||||
feather = ["pyarrow (>=7.0.0)"]
|
feather = ["pyarrow (>=7.0.0)"]
|
||||||
fss = ["fsspec (>=2021.07.0)"]
|
fss = ["fsspec (>=2022.05.0)"]
|
||||||
gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"]
|
gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"]
|
||||||
hdf5 = ["tables (>=3.6.1)"]
|
hdf5 = ["tables (>=3.7.0)"]
|
||||||
html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"]
|
html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"]
|
||||||
mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"]
|
mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"]
|
||||||
output-formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"]
|
output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"]
|
||||||
parquet = ["pyarrow (>=7.0.0)"]
|
parquet = ["pyarrow (>=7.0.0)"]
|
||||||
performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"]
|
performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"]
|
||||||
plot = ["matplotlib (>=3.6.1)"]
|
plot = ["matplotlib (>=3.6.1)"]
|
||||||
postgresql = ["SQLAlchemy (>=1.4.16)", "psycopg2 (>=2.8.6)"]
|
postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"]
|
||||||
spss = ["pyreadstat (>=1.1.2)"]
|
spss = ["pyreadstat (>=1.1.5)"]
|
||||||
sql-other = ["SQLAlchemy (>=1.4.16)"]
|
sql-other = ["SQLAlchemy (>=1.4.36)"]
|
||||||
test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"]
|
test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"]
|
||||||
xml = ["lxml (>=4.6.3)"]
|
xml = ["lxml (>=4.8.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathspec"
|
name = "pathspec"
|
||||||
@@ -3877,13 +3869,13 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tifffile"
|
name = "tifffile"
|
||||||
version = "2023.8.25"
|
version = "2023.8.30"
|
||||||
description = "Read and write TIFF files"
|
description = "Read and write TIFF files"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "tifffile-2023.8.25-py3-none-any.whl", hash = "sha256:40318485b59e9acb62e7139f22bd46e6760f92daea562b79900bfce3ee2613b7"},
|
{file = "tifffile-2023.8.30-py3-none-any.whl", hash = "sha256:62364eef35a6fdcc7bc2ad6f97dd270f577efb01b31260ff800af76a66c1e145"},
|
||||||
{file = "tifffile-2023.8.25.tar.gz", hash = "sha256:0a3ebcdfe71eb61a487dd22eaf21ed8962c511e6eb692153c7ac15f81798dfa4"},
|
{file = "tifffile-2023.8.30.tar.gz", hash = "sha256:6a8c53b012a286b75d09a1498ab32f202f24cc6270a105b5d5911dc4426f162a"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -3894,13 +3886,13 @@ all = ["defusedxml", "fsspec", "imagecodecs (>=2023.8.12)", "lxml", "matplotlib"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "timm"
|
name = "timm"
|
||||||
version = "0.9.5"
|
version = "0.9.6"
|
||||||
description = "PyTorch Image Models"
|
description = "PyTorch Image Models"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "timm-0.9.5-py3-none-any.whl", hash = "sha256:6e70af3a347bddb4167db46c3252a83c59165332ecf6b3df480d49c22866fa46"},
|
{file = "timm-0.9.6-py3-none-any.whl", hash = "sha256:7549a924b86a6151d4083a880c27ae86ce729e1b5c8c6099657217d0a0526a4e"},
|
||||||
{file = "timm-0.9.5.tar.gz", hash = "sha256:669835f0030cfb2412c464b7b563bb240d4d41a141226afbbf1b457e4f18cff1"},
|
{file = "timm-0.9.6.tar.gz", hash = "sha256:6c3c0451b69431de0290eed5662e66b134caf916f1cb9b4aa3b9a13c3d61fd03"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -4126,13 +4118,13 @@ telegram = ["requests"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "transformers"
|
name = "transformers"
|
||||||
version = "4.32.0"
|
version = "4.32.1"
|
||||||
description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow"
|
description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8.0"
|
python-versions = ">=3.8.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "transformers-4.32.0-py3-none-any.whl", hash = "sha256:32d8adf0ed76285508e7fd66657b4448ec1f882599ae6bf6f9c36bd7bf798402"},
|
{file = "transformers-4.32.1-py3-none-any.whl", hash = "sha256:b930d3dbd907a3f300cf49e54d63a56f8a0ab16b01a2c2a61ecff37c6de1da08"},
|
||||||
{file = "transformers-4.32.0.tar.gz", hash = "sha256:ca510f9688d2fe7347abbbfbd13f2f6dcd3c8349870c8d0ed98beed5f579b354"},
|
{file = "transformers-4.32.1.tar.gz", hash = "sha256:1edc8ae1de357d97c3d36b04412aa63d55e6fc0c4b39b419a7d380ed947d2252"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -4693,4 +4685,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "6d200d3ea1ccf9fb89f44043e3e0845e70f19aac374b96227559375f44508dc5"
|
content-hash = "4e97a32e7525cfedbf23892b8c1191b3fe7b4d09b9f043cdb285ed9772862d67"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "machine-learning"
|
name = "machine-learning"
|
||||||
version = "1.76.0"
|
version = "1.78.0"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -33,13 +33,13 @@ open-clip-torch = "^2.20.0"
|
|||||||
python-multipart = "^0.0.6"
|
python-multipart = "^0.0.6"
|
||||||
orjson = "^3.9.5"
|
orjson = "^3.9.5"
|
||||||
safetensors = "0.3.2"
|
safetensors = "0.3.2"
|
||||||
|
gunicorn = "^21.1.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
mypy = "^1.3.0"
|
mypy = "^1.3.0"
|
||||||
black = "^23.3.0"
|
black = "^23.3.0"
|
||||||
pytest = "^7.3.1"
|
pytest = "^7.3.1"
|
||||||
locust = "^2.15.1"
|
locust = "^2.15.1"
|
||||||
gunicorn = "^20.1.0"
|
|
||||||
httpx = "^0.24.1"
|
httpx = "^0.24.1"
|
||||||
pytest-asyncio = "^0.21.0"
|
pytest-asyncio = "^0.21.0"
|
||||||
pytest-cov = "^4.1.0"
|
pytest-cov = "^4.1.0"
|
||||||
@@ -74,6 +74,7 @@ warn_untyped_fields = true
|
|||||||
module = [
|
module = [
|
||||||
"huggingface_hub",
|
"huggingface_hub",
|
||||||
"transformers",
|
"transformers",
|
||||||
|
"gunicorn",
|
||||||
"cv2",
|
"cv2",
|
||||||
"insightface.model_zoo",
|
"insightface.model_zoo",
|
||||||
"insightface.utils.face_align",
|
"insightface.utils.face_align",
|
||||||
|
|||||||
1570
machine-learning/responses.json
Normal file
1570
machine-learning/responses.json
Normal file
File diff suppressed because it is too large
Load Diff
15
machine-learning/start.sh
Executable file
15
machine-learning/start.sh
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
export LD_PRELOAD="/usr/lib/$(arch)-linux-gnu/libmimalloc.so.2"
|
||||||
|
|
||||||
|
: "${MACHINE_LEARNING_HOST:=0.0.0.0}"
|
||||||
|
: "${MACHINE_LEARNING_PORT:=3003}"
|
||||||
|
: "${MACHINE_LEARNING_WORKERS:=1}"
|
||||||
|
: "${MACHINE_LEARNING_WORKER_TIMEOUT:=120}"
|
||||||
|
|
||||||
|
gunicorn app.main:app \
|
||||||
|
-k uvicorn.workers.UvicornWorker \
|
||||||
|
-w $MACHINE_LEARNING_WORKERS \
|
||||||
|
-b $MACHINE_LEARNING_HOST:$MACHINE_LEARNING_PORT \
|
||||||
|
-t $MACHINE_LEARNING_WORKER_TIMEOUT \
|
||||||
|
--log-config-json log_conf.json
|
||||||
11
mobile/.vscode/settings.json
vendored
Normal file
11
mobile/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"dart.flutterSdkPath": ".fvm/flutter_sdk",
|
||||||
|
// Remove .fvm files from search
|
||||||
|
"search.exclude": {
|
||||||
|
"**/.fvm": true
|
||||||
|
},
|
||||||
|
// Remove from file watching
|
||||||
|
"files.watcherExclude": {
|
||||||
|
"**/.fvm": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -96,3 +96,8 @@ dependencies {
|
|||||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
implementation "com.github.bumptech.glide:glide:$glide_version"
|
||||||
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is uncommented in F-Droid build script
|
||||||
|
//f configurations.all {
|
||||||
|
//f exclude group: 'com.google.android.gms'
|
||||||
|
//f }
|
||||||
|
|||||||
@@ -55,7 +55,6 @@
|
|||||||
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ platform :android do
|
|||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 99,
|
"android.injected.version.code" => 101,
|
||||||
"android.injected.version.name" => "1.76.0",
|
"android.injected.version.name" => "1.78.0",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||||
|
|||||||
@@ -10,12 +10,12 @@
|
|||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="67.877631">
|
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="63.585931">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="23.895222">
|
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="24.755096">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"add_to_album_bottom_sheet_added": "Přidáno do {album}",
|
"add_to_album_bottom_sheet_added": "Přidáno do {album}",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Již v {album}",
|
"add_to_album_bottom_sheet_already_exists": "Je již v {album}",
|
||||||
"advanced_settings_prefer_remote_subtitle": "U některých zařízení je načítání miniatur z prostředků v zařízení velmi pomalé. Aktivujte toto nastavení, aby se místo toho načítaly vzdálené obrázky.",
|
"advanced_settings_prefer_remote_subtitle": "U některých zařízení je načítání miniatur z prostředků v zařízení velmi pomalé. Aktivujte toto nastavení, aby se místo toho načítaly vzdálené obrázky.",
|
||||||
"advanced_settings_prefer_remote_title": "Preferovat vzdálené obrázky",
|
"advanced_settings_prefer_remote_title": "Preferovat vzdálené obrázky",
|
||||||
"advanced_settings_tile_subtitle": "Pokročilé uživatelské nastavení",
|
"advanced_settings_tile_subtitle": "Pokročilé uživatelské nastavení",
|
||||||
"advanced_settings_tile_title": "Pokročilé",
|
"advanced_settings_tile_title": "Pokročilé",
|
||||||
"advanced_settings_troubleshooting_subtitle": "Povolit dodatečné funkce pro řešení problémů",
|
"advanced_settings_troubleshooting_subtitle": "Zobrazit dodatečné vlastnosti pro řešení problémů",
|
||||||
"advanced_settings_troubleshooting_title": "Řešení problémů",
|
"advanced_settings_troubleshooting_title": "Řešení problémů",
|
||||||
"album_info_card_backup_album_excluded": "VYLOUČENO",
|
"album_info_card_backup_album_excluded": "VYLOUČENO",
|
||||||
"album_info_card_backup_album_included": "ZAHRNUTO",
|
"album_info_card_backup_album_included": "ZAHRNUTO",
|
||||||
@@ -16,8 +16,8 @@
|
|||||||
"album_thumbnail_shared_by": "Sdílené od {}",
|
"album_thumbnail_shared_by": "Sdílené od {}",
|
||||||
"album_viewer_appbar_share_delete": "Odstranit album",
|
"album_viewer_appbar_share_delete": "Odstranit album",
|
||||||
"album_viewer_appbar_share_err_delete": "Nepodařilo se odstranit album",
|
"album_viewer_appbar_share_err_delete": "Nepodařilo se odstranit album",
|
||||||
"album_viewer_appbar_share_err_leave": "Nepodařilo se ukončit album",
|
"album_viewer_appbar_share_err_leave": "Nepodařilo se opustit album",
|
||||||
"album_viewer_appbar_share_err_remove": "Při odstraňování souborů z alba se vyskytly problémy.",
|
"album_viewer_appbar_share_err_remove": "Při odstraňování položek z alba se vyskytly problémy.",
|
||||||
"album_viewer_appbar_share_err_title": "Nepodařilo se změnit název alba",
|
"album_viewer_appbar_share_err_title": "Nepodařilo se změnit název alba",
|
||||||
"album_viewer_appbar_share_leave": "Opustit album",
|
"album_viewer_appbar_share_leave": "Opustit album",
|
||||||
"album_viewer_appbar_share_remove": "Odstranit z alba",
|
"album_viewer_appbar_share_remove": "Odstranit z alba",
|
||||||
@@ -35,18 +35,18 @@
|
|||||||
"asset_list_settings_title": "Fotografická mřížka",
|
"asset_list_settings_title": "Fotografická mřížka",
|
||||||
"backup_album_selection_page_albums_device": "Alba v zařízení ({})",
|
"backup_album_selection_page_albums_device": "Alba v zařízení ({})",
|
||||||
"backup_album_selection_page_albums_tap": "Klepnutím na položku ji zahrnete, dvojím klepnutím ji vyloučíte",
|
"backup_album_selection_page_albums_tap": "Klepnutím na položku ji zahrnete, dvojím klepnutím ji vyloučíte",
|
||||||
"backup_album_selection_page_assets_scatter": "Soubory mohou být roztroušeny ve více albech. To umožňuje zahrnout nebo vyloučit alba během procesu zálohování.",
|
"backup_album_selection_page_assets_scatter": "Položky mohou být roztroušeny ve více albech. To umožňuje zahrnout nebo vyloučit alba během procesu zálohování.",
|
||||||
"backup_album_selection_page_select_albums": "Vybraná alba",
|
"backup_album_selection_page_select_albums": "Vybraná alba",
|
||||||
"backup_album_selection_page_selection_info": "Informace o výběru",
|
"backup_album_selection_page_selection_info": "Informace o výběru",
|
||||||
"backup_album_selection_page_total_assets": "Celkový počet jedinečných souborů",
|
"backup_album_selection_page_total_assets": "Celkový počet jedinečných položek",
|
||||||
"backup_all": "Vše",
|
"backup_all": "Vše",
|
||||||
"backup_background_service_backup_failed_message": "Zálohování médií selhalo. Zkouším to znovu...",
|
"backup_background_service_backup_failed_message": "Zálohování médií selhalo. Zkouším to znovu...",
|
||||||
"backup_background_service_connection_failed_message": "Nepodařilo se připojit k serveru. Zkouším to znovu...",
|
"backup_background_service_connection_failed_message": "Nepodařilo se připojit k serveru. Zkouším to znovu...",
|
||||||
"backup_background_service_current_upload_notification": "Nahrávání {}",
|
"backup_background_service_current_upload_notification": "Zálohování {}",
|
||||||
"backup_background_service_default_notification": "Kontrola nových médií {}",
|
"backup_background_service_default_notification": "Kontrola nových médií…",
|
||||||
"backup_background_service_error_title": "Chyba zálohování",
|
"backup_background_service_error_title": "Chyba zálohování",
|
||||||
"backup_background_service_in_progress_notification": "Vytvářím kopii vašich médií...",
|
"backup_background_service_in_progress_notification": "Zálohování vašich médií...",
|
||||||
"backup_background_service_upload_failure_notification": "Nepodařilo se nahrát {}",
|
"backup_background_service_upload_failure_notification": "Nepodařilo se zálohovat {}",
|
||||||
"backup_controller_page_albums": "Zálohovaná alba",
|
"backup_controller_page_albums": "Zálohovaná alba",
|
||||||
"backup_controller_page_background_app_refresh_disabled_content": "Povolte obnovení aplikace na pozadí v Nastavení > Obecné > Obnovení aplikace na pozadí, abyste mohli používat zálohování na pozadí.",
|
"backup_controller_page_background_app_refresh_disabled_content": "Povolte obnovení aplikace na pozadí v Nastavení > Obecné > Obnovení aplikace na pozadí, abyste mohli používat zálohování na pozadí.",
|
||||||
"backup_controller_page_background_app_refresh_disabled_title": " Obnovování aplikací na pozadí je vypnuté",
|
"backup_controller_page_background_app_refresh_disabled_title": " Obnovování aplikací na pozadí je vypnuté",
|
||||||
@@ -58,12 +58,12 @@
|
|||||||
"backup_controller_page_background_charging": "Pouze během nabíjení",
|
"backup_controller_page_background_charging": "Pouze během nabíjení",
|
||||||
"backup_controller_page_background_configure_error": "Nepodařilo se nakonfigurovat službu na pozadí",
|
"backup_controller_page_background_configure_error": "Nepodařilo se nakonfigurovat službu na pozadí",
|
||||||
"backup_controller_page_background_delay": "Zpoždění zálohování nových médií: {}",
|
"backup_controller_page_background_delay": "Zpoždění zálohování nových médií: {}",
|
||||||
"backup_controller_page_background_description": "Povolte službu na pozadí pro automatické zálohování všech nových aktiv bez nutnosti otevření aplikace",
|
"backup_controller_page_background_description": "Povolte službu na pozadí pro automatické zálohování všech nových položek bez nutnosti otevření aplikace",
|
||||||
"backup_controller_page_background_is_off": "Automatické zálohování na pozadí je vypnuto",
|
"backup_controller_page_background_is_off": "Automatické zálohování na pozadí je vypnuto",
|
||||||
"backup_controller_page_background_is_on": "Automatické zálohování na pozadí je zapnuto",
|
"backup_controller_page_background_is_on": "Automatické zálohování na pozadí je zapnuto",
|
||||||
"backup_controller_page_background_turn_off": "Vypnout zálohování na pozadí",
|
"backup_controller_page_background_turn_off": "Vypnout zálohování na pozadí",
|
||||||
"backup_controller_page_background_turn_on": "Povolit zálohování na pozadí",
|
"backup_controller_page_background_turn_on": "Povolit zálohování na pozadí",
|
||||||
"backup_controller_page_background_wifi": "Jen na WiFi",
|
"backup_controller_page_background_wifi": "Jen na Wi-Fi",
|
||||||
"backup_controller_page_backup": "Zálohování",
|
"backup_controller_page_backup": "Zálohování",
|
||||||
"backup_controller_page_backup_selected": "Vybrané: ",
|
"backup_controller_page_backup_selected": "Vybrané: ",
|
||||||
"backup_controller_page_backup_sub": "Zálohované fotografie a videa",
|
"backup_controller_page_backup_sub": "Zálohované fotografie a videa",
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
"backup_controller_page_id": "ID: {}",
|
"backup_controller_page_id": "ID: {}",
|
||||||
"backup_controller_page_info": "Informace o zálohování",
|
"backup_controller_page_info": "Informace o zálohování",
|
||||||
"backup_controller_page_none_selected": "Žádné vybrané",
|
"backup_controller_page_none_selected": "Žádné vybrané",
|
||||||
"backup_controller_page_remainder": "Zůstává",
|
"backup_controller_page_remainder": "Zbývá",
|
||||||
"backup_controller_page_remainder_sub": "Zbývající fotografie a videa, která se mají zálohovat z vybraných alb",
|
"backup_controller_page_remainder_sub": "Zbývající fotografie a videa, která se mají zálohovat z vybraných alb",
|
||||||
"backup_controller_page_select": "Vybrat",
|
"backup_controller_page_select": "Vybrat",
|
||||||
"backup_controller_page_server_storage": "Serverové úložiště",
|
"backup_controller_page_server_storage": "Serverové úložiště",
|
||||||
@@ -89,12 +89,12 @@
|
|||||||
"backup_controller_page_total_sub": "Všechny jedinečné fotografie a videa z vybraných alb",
|
"backup_controller_page_total_sub": "Všechny jedinečné fotografie a videa z vybraných alb",
|
||||||
"backup_controller_page_turn_off": "Vypnout zálohování na popředí",
|
"backup_controller_page_turn_off": "Vypnout zálohování na popředí",
|
||||||
"backup_controller_page_turn_on": "Povolit zálohování na popředí",
|
"backup_controller_page_turn_on": "Povolit zálohování na popředí",
|
||||||
"backup_controller_page_uploading_file_info": "Nahrávaný soubor",
|
"backup_controller_page_uploading_file_info": "Informace o zálohovaném souboru",
|
||||||
"backup_err_only_album": "Nelze odstranit jediné vybrané album",
|
"backup_err_only_album": "Nelze odstranit jediné vybrané album",
|
||||||
"backup_info_card_assets": "položek",
|
"backup_info_card_assets": "položek",
|
||||||
"backup_manual_cancelled": "Cancelled",
|
"backup_manual_cancelled": "Zrušeno",
|
||||||
"backup_manual_failed": "Selhalo",
|
"backup_manual_failed": "Selhalo",
|
||||||
"backup_manual_in_progress": "Zálohování již probíhá. Zkuste znovu později",
|
"backup_manual_in_progress": "Zálohování již probíhá. Zkuste to znovu později",
|
||||||
"backup_manual_success": "Úspěch",
|
"backup_manual_success": "Úspěch",
|
||||||
"backup_manual_title": "Stav zálohování",
|
"backup_manual_title": "Stav zálohování",
|
||||||
"cache_settings_album_thumbnails": "Náhledy stránek knihovny (položek {})",
|
"cache_settings_album_thumbnails": "Náhledy stránek knihovny (položek {})",
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
"cache_settings_thumbnail_size": "Velikost vyrovnávací paměti náhledů (položek {})",
|
"cache_settings_thumbnail_size": "Velikost vyrovnávací paměti náhledů (položek {})",
|
||||||
"cache_settings_title": "Nastavení vyrovnávací paměti",
|
"cache_settings_title": "Nastavení vyrovnávací paměti",
|
||||||
"change_password_form_confirm_password": "Potvrďte heslo",
|
"change_password_form_confirm_password": "Potvrďte heslo",
|
||||||
"change_password_form_description": "Dobrý den, {firstName} {lastName},\n\nJe to buď poprvé, co se přihlašujete do systému, nebo byl vytvořen požadavek na změnu hesla. Zadejte níže, prosím, nové heslo.",
|
"change_password_form_description": "Dobrý den, {firstName} {lastName},\n\nje to buď poprvé, co se přihlašujete do systému, nebo byl vytvořen požadavek na změnu hesla. Níže zadejte nové heslo.",
|
||||||
"change_password_form_new_password": "Nové heslo",
|
"change_password_form_new_password": "Nové heslo",
|
||||||
"change_password_form_password_mismatch": "Hesla se neshodují",
|
"change_password_form_password_mismatch": "Hesla se neshodují",
|
||||||
"change_password_form_reenter_new_password": "Znovu zadejte nové heslo",
|
"change_password_form_reenter_new_password": "Znovu zadejte nové heslo",
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
"common_shared": "Sdílené",
|
"common_shared": "Sdílené",
|
||||||
"control_bottom_app_bar_add_to_album": "Přidat do alba",
|
"control_bottom_app_bar_add_to_album": "Přidat do alba",
|
||||||
"control_bottom_app_bar_album_info": "{} položek",
|
"control_bottom_app_bar_album_info": "{} položek",
|
||||||
"control_bottom_app_bar_album_info_shared": "{} položky - sdílené",
|
"control_bottom_app_bar_album_info_shared": "{} položky – sdílené",
|
||||||
"control_bottom_app_bar_archive": "Archív",
|
"control_bottom_app_bar_archive": "Archív",
|
||||||
"control_bottom_app_bar_create_new_album": "Vytvořit nové album",
|
"control_bottom_app_bar_create_new_album": "Vytvořit nové album",
|
||||||
"control_bottom_app_bar_delete": "Vymazat",
|
"control_bottom_app_bar_delete": "Vymazat",
|
||||||
@@ -132,14 +132,14 @@
|
|||||||
"create_album_page_untitled": "Bez názvu",
|
"create_album_page_untitled": "Bez názvu",
|
||||||
"create_shared_album_page_create": "Vytvořit",
|
"create_shared_album_page_create": "Vytvořit",
|
||||||
"create_shared_album_page_share": "Sdílet",
|
"create_shared_album_page_share": "Sdílet",
|
||||||
"create_shared_album_page_share_add_assets": "PŘIDAT",
|
"create_shared_album_page_share_add_assets": "PŘIDAT POLOŽKY",
|
||||||
"create_shared_album_page_share_select_photos": "Vybrat fotografie",
|
"create_shared_album_page_share_select_photos": "Vybrat fotografie",
|
||||||
"curated_location_page_title": "Místa",
|
"curated_location_page_title": "Místa",
|
||||||
"curated_object_page_title": "Věci",
|
"curated_object_page_title": "Věci",
|
||||||
"daily_title_text_date": "EEEE, d. MMMM",
|
"daily_title_text_date": "EEEE, d. MMMM",
|
||||||
"daily_title_text_date_year": "EEEE, d. MMMM y",
|
"daily_title_text_date_year": "EEEE, d. MMMM y",
|
||||||
"date_format": "EEEE, d. MMMM y • H:mm",
|
"date_format": "EEEE, d. MMMM y • H:mm",
|
||||||
"delete_dialog_alert": "Tyto položky budou trvale odstraněny z Immich a z vašeho zařízení.",
|
"delete_dialog_alert": "Tyto položky budou trvale odstraněny z Immich i z vašeho zařízení",
|
||||||
"delete_dialog_cancel": "Zrušit",
|
"delete_dialog_cancel": "Zrušit",
|
||||||
"delete_dialog_ok": "Vymazat",
|
"delete_dialog_ok": "Vymazat",
|
||||||
"delete_dialog_title": "Vymazat trvale",
|
"delete_dialog_title": "Vymazat trvale",
|
||||||
@@ -155,13 +155,13 @@
|
|||||||
"favorites_page_no_favorites": "Nebyla nalezena žádná oblíbená média",
|
"favorites_page_no_favorites": "Nebyla nalezena žádná oblíbená média",
|
||||||
"favorites_page_title": "Oblíbené",
|
"favorites_page_title": "Oblíbené",
|
||||||
"home_page_add_to_album_conflicts": "Přidáno {added} položek do alba {album}. {failed} položek již je v albu.",
|
"home_page_add_to_album_conflicts": "Přidáno {added} položek do alba {album}. {failed} položek již je v albu.",
|
||||||
"home_page_add_to_album_err_local": "Zatím není možné přidat lokální média do alb, přeskakuje se",
|
"home_page_add_to_album_err_local": "Zatím není možné přidat lokální média do alb, přeskakuji",
|
||||||
"home_page_add_to_album_success": "Přidány položky {added} do alba {album}.",
|
"home_page_add_to_album_success": "Přidány položky {added} do alba {album}.",
|
||||||
"home_page_archive_err_local": "Zatím nemohu archivovat lokální média, přeskakuji",
|
"home_page_archive_err_local": "Zatím nemohu archivovat lokální média, přeskakuji",
|
||||||
"home_page_building_timeline": "Vytváření časové osy",
|
"home_page_building_timeline": "Vytváření časové osy",
|
||||||
"home_page_favorite_err_local": "Zatím není možné zařadit lokální média mezi oblíbená, přeskakuje se",
|
"home_page_favorite_err_local": "Zatím není možné zařadit lokální média mezi oblíbená, přeskakuji",
|
||||||
"home_page_first_time_notice": "Pokud aplikaci používáte poprvé, nezapomeňte si vybrat zálohovaná alba, aby se na časové ose mohly nacházet fotografie a videa z vybraných albech.",
|
"home_page_first_time_notice": "Pokud aplikaci používáte poprvé, nezapomeňte si vybrat zálohovaná alba, aby se na časové ose mohly nacházet fotografie a videa z vybraných alb.",
|
||||||
"home_page_upload_err_limit": "Lze nahrát nejvýše 30 položek najednou, přeskakuji",
|
"home_page_upload_err_limit": "Lze zálohovat nejvýše 30 položek najednou, přeskakuji",
|
||||||
"image_viewer_page_state_provider_download_error": "Chyba stahování",
|
"image_viewer_page_state_provider_download_error": "Chyba stahování",
|
||||||
"image_viewer_page_state_provider_download_success": "Stahování bylo úspěšné",
|
"image_viewer_page_state_provider_download_success": "Stahování bylo úspěšné",
|
||||||
"library_page_albums": "Alba",
|
"library_page_albums": "Alba",
|
||||||
@@ -175,7 +175,7 @@
|
|||||||
"login_disabled": "Přihlášení bylo zakázáno",
|
"login_disabled": "Přihlášení bylo zakázáno",
|
||||||
"login_form_api_exception": "Výjimka API. Zkontrolujte URL serveru a zkuste to znovu.",
|
"login_form_api_exception": "Výjimka API. Zkontrolujte URL serveru a zkuste to znovu.",
|
||||||
"login_form_button_text": "Přihlásit se",
|
"login_form_button_text": "Přihlásit se",
|
||||||
"login_form_email_hint": "tvůjmail@email.com",
|
"login_form_email_hint": "tvůje-mail@email.com",
|
||||||
"login_form_endpoint_hint": "http://ip-tvého-serveru:port/api",
|
"login_form_endpoint_hint": "http://ip-tvého-serveru:port/api",
|
||||||
"login_form_endpoint_url": "URL adresa serveru",
|
"login_form_endpoint_url": "URL adresa serveru",
|
||||||
"login_form_err_http": "Prosím, uveďte http:// nebo https://",
|
"login_form_err_http": "Prosím, uveďte http:// nebo https://",
|
||||||
@@ -185,7 +185,7 @@
|
|||||||
"login_form_err_trailing_whitespace": "Koncová mezera",
|
"login_form_err_trailing_whitespace": "Koncová mezera",
|
||||||
"login_form_failed_get_oauth_server_config": "Chyba přihlášení pomocí OAuth, zkontrolujte adresu URL serveru",
|
"login_form_failed_get_oauth_server_config": "Chyba přihlášení pomocí OAuth, zkontrolujte adresu URL serveru",
|
||||||
"login_form_failed_get_oauth_server_disable": "Funkce OAuth není na tomto serveru dostupná",
|
"login_form_failed_get_oauth_server_disable": "Funkce OAuth není na tomto serveru dostupná",
|
||||||
"login_form_failed_login": "Chyba přihlášení, zkontrolujte url adresu serveru, e-mail a heslo.",
|
"login_form_failed_login": "Chyba přihlášení, zkontrolujte URL adresu serveru, e-mail a heslo.",
|
||||||
"login_form_label_email": "E-mail",
|
"login_form_label_email": "E-mail",
|
||||||
"login_form_label_password": "Heslo",
|
"login_form_label_password": "Heslo",
|
||||||
"login_form_next_button": "Další",
|
"login_form_next_button": "Další",
|
||||||
@@ -196,7 +196,7 @@
|
|||||||
"monthly_title_text_date_format": "LLLL y",
|
"monthly_title_text_date_format": "LLLL y",
|
||||||
"motion_photos_page_title": "Pohyblivé fotky",
|
"motion_photos_page_title": "Pohyblivé fotky",
|
||||||
"notification_permission_dialog_cancel": "Zrušit",
|
"notification_permission_dialog_cancel": "Zrušit",
|
||||||
"notification_permission_dialog_content": "Chcete-li povolit oznámení, přejděte do Nastavení a vyberte možnost povolit.",
|
"notification_permission_dialog_content": "Chcete-li povolit oznámení, přejděte do nastavení a vyberte možnost povolit.",
|
||||||
"notification_permission_dialog_settings": "Nastavení",
|
"notification_permission_dialog_settings": "Nastavení",
|
||||||
"notification_permission_list_tile_content": "Udělte oprávnění k aktivaci oznámení.",
|
"notification_permission_list_tile_content": "Udělte oprávnění k aktivaci oznámení.",
|
||||||
"notification_permission_list_tile_enable_button": "Povolit oznámení",
|
"notification_permission_list_tile_enable_button": "Povolit oznámení",
|
||||||
@@ -215,16 +215,16 @@
|
|||||||
"permission_onboarding_go_to_settings": "Přejít do nastavení",
|
"permission_onboarding_go_to_settings": "Přejít do nastavení",
|
||||||
"permission_onboarding_grant_permission": "Povolit přístup",
|
"permission_onboarding_grant_permission": "Povolit přístup",
|
||||||
"permission_onboarding_log_out": "Odhlásit se",
|
"permission_onboarding_log_out": "Odhlásit se",
|
||||||
"permission_onboarding_permission_denied": "Přístup odepřen. Pro používání Immich, je nutné povolit přístup k fotkám a videím v nastavení.",
|
"permission_onboarding_permission_denied": "Přístup odepřen. Pro používání Immich je nutné povolit přístup k fotkám a videím v nastavení.",
|
||||||
"permission_onboarding_permission_granted": "Přístup povolen! Vše je připraveno.",
|
"permission_onboarding_permission_granted": "Přístup povolen! Vše je připraveno.",
|
||||||
"permission_onboarding_permission_limited": "Přístup omezen. Chcete-li používat Immich k zálohování a správě celé vaší kolekci galerií, povolte přístup k fotkám a videím v Nastavení.",
|
"permission_onboarding_permission_limited": "Přístup omezen. Chcete-li používat Immich k zálohování a správě celé vaší kolekce galerií, povolte v nastavení přístup k fotkám a videím.",
|
||||||
"permission_onboarding_request": "Immich potřebuje přístup k zobrazení vašich fotek a videí.",
|
"permission_onboarding_request": "Immich potřebuje přístup k zobrazení vašich fotek a videí.",
|
||||||
"profile_drawer_app_logs": "Logy",
|
"profile_drawer_app_logs": "Logy",
|
||||||
"profile_drawer_client_server_up_to_date": "Klient a server jsou aktuální",
|
"profile_drawer_client_server_up_to_date": "Klient a server jsou aktuální",
|
||||||
"profile_drawer_settings": "Nastavení",
|
"profile_drawer_settings": "Nastavení",
|
||||||
"profile_drawer_sign_out": "Odhlásit se",
|
"profile_drawer_sign_out": "Odhlásit se",
|
||||||
"recently_added_page_title": "Nedávno přidané",
|
"recently_added_page_title": "Nedávno přidané",
|
||||||
"search_bar_hint": "Prohledejte své obrázky",
|
"search_bar_hint": "Prohledejte své fotky",
|
||||||
"search_page_categories": "Kategorie",
|
"search_page_categories": "Kategorie",
|
||||||
"search_page_favorites": "Oblíbené",
|
"search_page_favorites": "Oblíbené",
|
||||||
"search_page_motion_photos": "Pohyblivé fotky",
|
"search_page_motion_photos": "Pohyblivé fotky",
|
||||||
@@ -234,7 +234,7 @@
|
|||||||
"search_page_places": "Místa",
|
"search_page_places": "Místa",
|
||||||
"search_page_recently_added": "Nedávno přidané",
|
"search_page_recently_added": "Nedávno přidané",
|
||||||
"search_page_screenshots": "Snímky obrazovky",
|
"search_page_screenshots": "Snímky obrazovky",
|
||||||
"search_page_selfies": "Selfie",
|
"search_page_selfies": "Autoportréty",
|
||||||
"search_page_things": "Věci",
|
"search_page_things": "Věci",
|
||||||
"search_page_videos": "Videa",
|
"search_page_videos": "Videa",
|
||||||
"search_page_view_all_button": "Zobrazit vše",
|
"search_page_view_all_button": "Zobrazit vše",
|
||||||
@@ -258,11 +258,11 @@
|
|||||||
"setting_notifications_notify_minutes": "{} minut",
|
"setting_notifications_notify_minutes": "{} minut",
|
||||||
"setting_notifications_notify_never": "nikdy",
|
"setting_notifications_notify_never": "nikdy",
|
||||||
"setting_notifications_notify_seconds": "{} sekundy",
|
"setting_notifications_notify_seconds": "{} sekundy",
|
||||||
"setting_notifications_single_progress_subtitle": "Podrobné informace o průběhu nahrávání pro položku",
|
"setting_notifications_single_progress_subtitle": "Podrobné informace o průběhu zálohování položky",
|
||||||
"setting_notifications_single_progress_title": "Zobrazit průběh detailů zálohování na pozadí",
|
"setting_notifications_single_progress_title": "Zobrazit průběh detailů zálohování na pozadí",
|
||||||
"setting_notifications_subtitle": "Přizpůsobení předvoleb oznámení",
|
"setting_notifications_subtitle": "Přizpůsobení předvoleb oznámení",
|
||||||
"setting_notifications_title": "Oznámení",
|
"setting_notifications_title": "Oznámení",
|
||||||
"setting_notifications_total_progress_subtitle": "Celkový průběh nahrávání (nahraných/celkově)",
|
"setting_notifications_total_progress_subtitle": "Celkový průběh zálohování (hotovo/celkově)",
|
||||||
"setting_notifications_total_progress_title": "Zobrazit celkový průběh zálohování na pozadí",
|
"setting_notifications_total_progress_title": "Zobrazit celkový průběh zálohování na pozadí",
|
||||||
"setting_pages_app_bar_settings": "Nastavení",
|
"setting_pages_app_bar_settings": "Nastavení",
|
||||||
"settings_require_restart": "Pro použití tohoto nastavení restartujte Immich",
|
"settings_require_restart": "Pro použití tohoto nastavení restartujte Immich",
|
||||||
@@ -297,7 +297,7 @@
|
|||||||
"upload_dialog_title": "Zálohovat položku",
|
"upload_dialog_title": "Zálohovat položku",
|
||||||
"version_announcement_overlay_ack": "Potvrdit",
|
"version_announcement_overlay_ack": "Potvrdit",
|
||||||
"version_announcement_overlay_release_notes": "poznámky k vydání",
|
"version_announcement_overlay_release_notes": "poznámky k vydání",
|
||||||
"version_announcement_overlay_text_1": "Ahoj, je zde nová verze",
|
"version_announcement_overlay_text_1": "Ahoj, k dispozici je nová verze",
|
||||||
"version_announcement_overlay_text_2": "najděte si čas na návštěvu ",
|
"version_announcement_overlay_text_2": "najděte si čas na návštěvu ",
|
||||||
"version_announcement_overlay_text_3": " a ujistěte se, že vaše konfigurace docker-compose a .env je aktuální, abyste předešli nesprávné konfiguraci, zvláště pokud používáte WatchTower nebo jakýkoli mechanismus, který podporuje automatické aktualizace serverových aplikací.",
|
"version_announcement_overlay_text_3": " a ujistěte se, že vaše konfigurace docker-compose a .env je aktuální, abyste předešli nesprávné konfiguraci, zvláště pokud používáte WatchTower nebo jakýkoli mechanismus, který podporuje automatické aktualizace serverových aplikací.",
|
||||||
"version_announcement_overlay_title": "K dispozici je nová verze serveru \uD83C\uDF89"
|
"version_announcement_overlay_title": "K dispozici je nová verze serveru \uD83C\uDF89"
|
||||||
|
|||||||
@@ -40,29 +40,29 @@
|
|||||||
"backup_album_selection_page_selection_info": "Oplysninger om valgte",
|
"backup_album_selection_page_selection_info": "Oplysninger om valgte",
|
||||||
"backup_album_selection_page_total_assets": "Samlede unikke elementer",
|
"backup_album_selection_page_total_assets": "Samlede unikke elementer",
|
||||||
"backup_all": "Alt",
|
"backup_all": "Alt",
|
||||||
"backup_background_service_backup_failed_message": "Backup af elementer fejlede. Forsøger igen...",
|
"backup_background_service_backup_failed_message": "Sikkerhedskopiering af elementer fejlede. Forsøger igen...",
|
||||||
"backup_background_service_connection_failed_message": "Forbindelsen til serveren blev tabt. Forsøger igen...",
|
"backup_background_service_connection_failed_message": "Forbindelsen til serveren blev tabt. Forsøger igen...",
|
||||||
"backup_background_service_current_upload_notification": "Uploader {}",
|
"backup_background_service_current_upload_notification": "Uploader {}",
|
||||||
"backup_background_service_default_notification": "Søger efter nye elementer...",
|
"backup_background_service_default_notification": "Søger efter nye elementer...",
|
||||||
"backup_background_service_error_title": "Fejl med backup",
|
"backup_background_service_error_title": "Fejl med sikkerhedskopiering",
|
||||||
"backup_background_service_in_progress_notification": "Tager backup af dine elementer...",
|
"backup_background_service_in_progress_notification": "Tager sikkerhedskopi af dine elementer...",
|
||||||
"backup_background_service_upload_failure_notification": "Fejlede med uploade af {}",
|
"backup_background_service_upload_failure_notification": "Fejlede med uploade af {}",
|
||||||
"backup_controller_page_albums": "Sikkerhedskopiér albummer",
|
"backup_controller_page_albums": "Sikkerhedskopiér albummer",
|
||||||
"backup_controller_page_background_app_refresh_disabled_content": "Slå baggrundsopdatering af applikationen til i Indstillinger > Generelt > Baggrundsopdatering af applikationer, for at bruge baggrundsbackup.",
|
"backup_controller_page_background_app_refresh_disabled_content": "Slå baggrundsopdatering af applikationen til i Indstillinger > Generelt > Baggrundsopdatering af applikationer, for at bruge sikkerhedskopi i baggrunden.",
|
||||||
"backup_controller_page_background_app_refresh_disabled_title": "Baggrundsopdatering af app er slået fra",
|
"backup_controller_page_background_app_refresh_disabled_title": "Baggrundsopdatering af app er slået fra",
|
||||||
"backup_controller_page_background_app_refresh_enable_button_text": "Gå til indstillinger",
|
"backup_controller_page_background_app_refresh_enable_button_text": "Gå til indstillinger",
|
||||||
"backup_controller_page_background_battery_info_link": "Vis mig hvordan",
|
"backup_controller_page_background_battery_info_link": "Vis mig hvordan",
|
||||||
"backup_controller_page_background_battery_info_message": "For den bedste oplevelse med baggrundsbackup, bør du slå batterioptimering, der begrænder baggrundsaktivitet, fra.\n\nSiden dette er afhængigt af enheden, bør du undersøge denne information leveret af din enheds producent.",
|
"backup_controller_page_background_battery_info_message": "For den bedste oplevelse med sikkerhedskopiering i baggrunden, bør du slå batterioptimering, der begrænder baggrundsaktivitet, fra.\n\nSiden dette er afhængigt af enheden, bør du undersøge denne information leveret af din enheds producent.",
|
||||||
"backup_controller_page_background_battery_info_ok": "OK",
|
"backup_controller_page_background_battery_info_ok": "OK",
|
||||||
"backup_controller_page_background_battery_info_title": "Batterioptimering",
|
"backup_controller_page_background_battery_info_title": "Batterioptimering",
|
||||||
"backup_controller_page_background_charging": "Kun under opladning",
|
"backup_controller_page_background_charging": "Kun under opladning",
|
||||||
"backup_controller_page_background_configure_error": "Fejlede konfigureringen af baggrundsbackup",
|
"backup_controller_page_background_configure_error": "Fejlede konfigureringen af sikkerhedskopiering i baggrunden",
|
||||||
"backup_controller_page_background_delay": "Udskyd backup af nye elementer: {}",
|
"backup_controller_page_background_delay": "Udskyd sikkerhedskopi af nye elementer: {}",
|
||||||
"backup_controller_page_background_description": "Slå baggrundsbackup til, for automatisk at tage backup af nye elementer, uden at skulle åbne appen",
|
"backup_controller_page_background_description": "Slå sikkerhedskopiering i baggrunden til, for automatisk at tage sikkerhedskopi af nye elementer, uden at skulle åbne appen",
|
||||||
"backup_controller_page_background_is_off": "Automatisk baggrundsbackup er slået fra",
|
"backup_controller_page_background_is_off": "Automatisk sikkerhedskopiering i baggrunden er slået fra",
|
||||||
"backup_controller_page_background_is_on": "Automatisk baggrundsbackup er slået til",
|
"backup_controller_page_background_is_on": "Automatisk sikkerhedskopiering i baggrunden er slået til",
|
||||||
"backup_controller_page_background_turn_off": "Slå baggrundsbackup fra",
|
"backup_controller_page_background_turn_off": "Slå sikkerhedskopiering i baggrunden fra",
|
||||||
"backup_controller_page_background_turn_on": "Slå baggrundsbackup til",
|
"backup_controller_page_background_turn_on": "Slå sikkerhedskopiering i baggrunden til",
|
||||||
"backup_controller_page_background_wifi": "Kun med WiFi",
|
"backup_controller_page_background_wifi": "Kun med WiFi",
|
||||||
"backup_controller_page_backup": "Sikkerhedskopier",
|
"backup_controller_page_backup": "Sikkerhedskopier",
|
||||||
"backup_controller_page_backup_selected": "Valgte: ",
|
"backup_controller_page_backup_selected": "Valgte: ",
|
||||||
@@ -92,11 +92,11 @@
|
|||||||
"backup_controller_page_uploading_file_info": "Uploader filinformation",
|
"backup_controller_page_uploading_file_info": "Uploader filinformation",
|
||||||
"backup_err_only_album": "Kan ikke slette det eneste album",
|
"backup_err_only_album": "Kan ikke slette det eneste album",
|
||||||
"backup_info_card_assets": "elementer",
|
"backup_info_card_assets": "elementer",
|
||||||
"backup_manual_cancelled": "Cancelled",
|
"backup_manual_cancelled": "Annulleret",
|
||||||
"backup_manual_failed": "Failed",
|
"backup_manual_failed": "Mislykkedes",
|
||||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
"backup_manual_in_progress": "Upload er allerede undervejs. Prøv igen efter noget tid",
|
||||||
"backup_manual_success": "Success",
|
"backup_manual_success": "Succes",
|
||||||
"backup_manual_title": "Upload status",
|
"backup_manual_title": "Uploadstatus",
|
||||||
"cache_settings_album_thumbnails": "Biblioteksminiaturebilleder ({} elementer)",
|
"cache_settings_album_thumbnails": "Biblioteksminiaturebilleder ({} elementer)",
|
||||||
"cache_settings_clear_cache_button": "Fjern cache",
|
"cache_settings_clear_cache_button": "Fjern cache",
|
||||||
"cache_settings_clear_cache_button_title": "Fjern appens cache. Dette vil i stor grad påvirke appens ydeevne indtil cachen er genopbygget.",
|
"cache_settings_clear_cache_button_title": "Fjern appens cache. Dette vil i stor grad påvirke appens ydeevne indtil cachen er genopbygget.",
|
||||||
@@ -160,8 +160,8 @@
|
|||||||
"home_page_archive_err_local": "Kan ikke arkivere lokalt element endnu.. Springer over",
|
"home_page_archive_err_local": "Kan ikke arkivere lokalt element endnu.. Springer over",
|
||||||
"home_page_building_timeline": "Bygger tidslinjen",
|
"home_page_building_timeline": "Bygger tidslinjen",
|
||||||
"home_page_favorite_err_local": "Kan endnu ikke gøre lokale elementer til favoritter. Springer over..",
|
"home_page_favorite_err_local": "Kan endnu ikke gøre lokale elementer til favoritter. Springer over..",
|
||||||
"home_page_first_time_notice": "Hvis dette er din første gang i appen, bedes du vælge en backup af albummer så tidlinjen kan blive fyldt med billeder og videoer fra albummerne.",
|
"home_page_first_time_notice": "Hvis det er din første gang i appen, bedes du vælge en sikkerhedskopi af albummer så tidlinjen kan blive fyldt med billeder og videoer fra albummerne.",
|
||||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
"home_page_upload_err_limit": "Det er kun muligt at lave sikkerhedskopi af 30 elementer ad gangen. Springer over",
|
||||||
"image_viewer_page_state_provider_download_error": "Fejl ved download",
|
"image_viewer_page_state_provider_download_error": "Fejl ved download",
|
||||||
"image_viewer_page_state_provider_download_success": "Download succesfuld",
|
"image_viewer_page_state_provider_download_success": "Download succesfuld",
|
||||||
"library_page_albums": "Albummer",
|
"library_page_albums": "Albummer",
|
||||||
@@ -172,7 +172,7 @@
|
|||||||
"library_page_sharing": "Delte",
|
"library_page_sharing": "Delte",
|
||||||
"library_page_sort_created": "Senest oprettet",
|
"library_page_sort_created": "Senest oprettet",
|
||||||
"library_page_sort_title": "Albumtitel",
|
"library_page_sort_title": "Albumtitel",
|
||||||
"login_disabled": "Login has been disabled",
|
"login_disabled": "Login er blevet deaktiveret",
|
||||||
"login_form_api_exception": "API-undtagelse. Tjek serverens URL og prøv igen. ",
|
"login_form_api_exception": "API-undtagelse. Tjek serverens URL og prøv igen. ",
|
||||||
"login_form_button_text": "Log ind",
|
"login_form_button_text": "Log ind",
|
||||||
"login_form_email_hint": "din-e-mail@e-mail.com",
|
"login_form_email_hint": "din-e-mail@e-mail.com",
|
||||||
@@ -217,7 +217,7 @@
|
|||||||
"permission_onboarding_log_out": "Log ud",
|
"permission_onboarding_log_out": "Log ud",
|
||||||
"permission_onboarding_permission_denied": "Tilladelse afvist. For at bruge Immich, skal der gives tilladelse til at se billeder og videoer i indstillinger.",
|
"permission_onboarding_permission_denied": "Tilladelse afvist. For at bruge Immich, skal der gives tilladelse til at se billeder og videoer i indstillinger.",
|
||||||
"permission_onboarding_permission_granted": "Tilladelse givet! Du er nu klar.",
|
"permission_onboarding_permission_granted": "Tilladelse givet! Du er nu klar.",
|
||||||
"permission_onboarding_permission_limited": "Tilladelse begrænset. For at lade Immich lave backup og styre hele dit galleri, skal der gives tilladelse til billeder og videoer i indstillinger.",
|
"permission_onboarding_permission_limited": "Tilladelse begrænset. For at lade Immich lave sikkerhedskopi og styre hele dit galleri, skal der gives tilladelse til billeder og videoer i indstillinger.",
|
||||||
"permission_onboarding_request": "Immich kræver tilliadelse til at se dine billeder og videoer.",
|
"permission_onboarding_request": "Immich kræver tilliadelse til at se dine billeder og videoer.",
|
||||||
"profile_drawer_app_logs": "Log",
|
"profile_drawer_app_logs": "Log",
|
||||||
"profile_drawer_client_server_up_to_date": "Klient og server er ajour",
|
"profile_drawer_client_server_up_to_date": "Klient og server er ajour",
|
||||||
@@ -252,7 +252,7 @@
|
|||||||
"setting_image_viewer_original_title": "Indlæs originalbillede",
|
"setting_image_viewer_original_title": "Indlæs originalbillede",
|
||||||
"setting_image_viewer_preview_subtitle": "Slå indlæsning af et mediumstørrelse billede til. Slå fra for enten direkte at indlæse originalen eller kun at bruge miniaturebilledet.",
|
"setting_image_viewer_preview_subtitle": "Slå indlæsning af et mediumstørrelse billede til. Slå fra for enten direkte at indlæse originalen eller kun at bruge miniaturebilledet.",
|
||||||
"setting_image_viewer_preview_title": "Indlæs forhåndsvisning af billedet",
|
"setting_image_viewer_preview_title": "Indlæs forhåndsvisning af billedet",
|
||||||
"setting_notifications_notify_failures_grace_period": "Giv besked om baggrundsbackupfejl: {}",
|
"setting_notifications_notify_failures_grace_period": "Giv besked om fejl med sikkerhedskopiering i baggrunden: {}",
|
||||||
"setting_notifications_notify_hours": "{} timer",
|
"setting_notifications_notify_hours": "{} timer",
|
||||||
"setting_notifications_notify_immediately": "med det samme",
|
"setting_notifications_notify_immediately": "med det samme",
|
||||||
"setting_notifications_notify_minutes": "{} minutter",
|
"setting_notifications_notify_minutes": "{} minutter",
|
||||||
@@ -291,10 +291,10 @@
|
|||||||
"theme_setting_theme_title": "Tema",
|
"theme_setting_theme_title": "Tema",
|
||||||
"theme_setting_three_stage_loading_subtitle": "Tre-trins indlæsning kan øge ydeevnen, men kan ligeledes føre til højere netværksbelastning",
|
"theme_setting_three_stage_loading_subtitle": "Tre-trins indlæsning kan øge ydeevnen, men kan ligeledes føre til højere netværksbelastning",
|
||||||
"theme_setting_three_stage_loading_title": "Slå tre-trins indlæsning til",
|
"theme_setting_three_stage_loading_title": "Slå tre-trins indlæsning til",
|
||||||
"upload_dialog_cancel": "Cancel",
|
"upload_dialog_cancel": "Annuller",
|
||||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
"upload_dialog_info": "Vil du sikkerhedskopiere de(t) valgte element(er) til serveren?",
|
||||||
"upload_dialog_ok": "Upload",
|
"upload_dialog_ok": "Upload",
|
||||||
"upload_dialog_title": "Upload Asset",
|
"upload_dialog_title": "Upload element",
|
||||||
"version_announcement_overlay_ack": "Accepter",
|
"version_announcement_overlay_ack": "Accepter",
|
||||||
"version_announcement_overlay_release_notes": "udgivelsesnoterne",
|
"version_announcement_overlay_release_notes": "udgivelsesnoterne",
|
||||||
"version_announcement_overlay_text_1": "Hej ven, der er en ny version af",
|
"version_announcement_overlay_text_1": "Hej ven, der er en ny version af",
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
"advanced_settings_tile_title": "Advanced",
|
"advanced_settings_tile_title": "Advanced",
|
||||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
||||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
||||||
|
"advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates",
|
||||||
|
"advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.",
|
||||||
"album_info_card_backup_album_excluded": "EXCLUDED",
|
"album_info_card_backup_album_excluded": "EXCLUDED",
|
||||||
"album_info_card_backup_album_included": "INCLUDED",
|
"album_info_card_backup_album_included": "INCLUDED",
|
||||||
"album_thumbnail_card_item": "1 item",
|
"album_thumbnail_card_item": "1 item",
|
||||||
@@ -174,6 +176,7 @@
|
|||||||
"library_page_sort_title": "Album title",
|
"library_page_sort_title": "Album title",
|
||||||
"login_disabled": "Login has been disabled",
|
"login_disabled": "Login has been disabled",
|
||||||
"login_form_api_exception": "API exception. Please check the server URL and try again.",
|
"login_form_api_exception": "API exception. Please check the server URL and try again.",
|
||||||
|
"login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.",
|
||||||
"login_form_button_text": "Login",
|
"login_form_button_text": "Login",
|
||||||
"login_form_email_hint": "youremail@email.com",
|
"login_form_email_hint": "youremail@email.com",
|
||||||
"login_form_endpoint_hint": "http://your-server-ip:port/api",
|
"login_form_endpoint_hint": "http://your-server-ip:port/api",
|
||||||
@@ -193,6 +196,8 @@
|
|||||||
"login_form_save_login": "Stay logged in",
|
"login_form_save_login": "Stay logged in",
|
||||||
"login_form_server_empty": "Enter a server URL.",
|
"login_form_server_empty": "Enter a server URL.",
|
||||||
"login_form_server_error": "Could not connect to server.",
|
"login_form_server_error": "Could not connect to server.",
|
||||||
|
"login_password_changed_success": "Password updated successfully",
|
||||||
|
"login_password_changed_error": "There was an error updating your password",
|
||||||
"monthly_title_text_date_format": "MMMM y",
|
"monthly_title_text_date_format": "MMMM y",
|
||||||
"motion_photos_page_title": "Motion Photos",
|
"motion_photos_page_title": "Motion Photos",
|
||||||
"notification_permission_dialog_cancel": "Cancel",
|
"notification_permission_dialog_cancel": "Cancel",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"add_to_album_bottom_sheet_added": "Lisätty albumiin {album}",
|
"add_to_album_bottom_sheet_added": "Lisätty albumiin {album}",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Kohde on jo albumissa {album}",
|
"add_to_album_bottom_sheet_already_exists": "Kohde on jo albumissa {album}",
|
||||||
"advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.",
|
"advanced_settings_prefer_remote_subtitle": "Jotkut laitteet ovat erittäin hitaita lataamaan esikatselukuvia laitteen kohteista. Aktivoi tämä asetus käyttääksesi etäkuvia.",
|
||||||
"advanced_settings_prefer_remote_title": "Prefer remote images",
|
"advanced_settings_prefer_remote_title": "Suosi etäkuvia",
|
||||||
"advanced_settings_tile_subtitle": "Edistyneen käyttäjän asetukset",
|
"advanced_settings_tile_subtitle": "Edistyneen käyttäjän asetukset",
|
||||||
"advanced_settings_tile_title": "Edistyneet",
|
"advanced_settings_tile_title": "Edistyneet",
|
||||||
"advanced_settings_troubleshooting_subtitle": "Kytke vianetsinnän lisäominaisuudet päälle",
|
"advanced_settings_troubleshooting_subtitle": "Kytke vianetsinnän lisäominaisuudet päälle",
|
||||||
@@ -22,12 +22,12 @@
|
|||||||
"album_viewer_appbar_share_leave": "Poistu albumista",
|
"album_viewer_appbar_share_leave": "Poistu albumista",
|
||||||
"album_viewer_appbar_share_remove": "Poista albumista",
|
"album_viewer_appbar_share_remove": "Poista albumista",
|
||||||
"album_viewer_page_share_add_users": "Lisää käyttäjiä",
|
"album_viewer_page_share_add_users": "Lisää käyttäjiä",
|
||||||
"all_people_page_title": "People",
|
"all_people_page_title": "Ihmiset",
|
||||||
"all_videos_page_title": "Videot",
|
"all_videos_page_title": "Videot",
|
||||||
"archive_page_no_archived_assets": "No archived assets found",
|
"archive_page_no_archived_assets": "Arkistoituja kohteita ei löytynyt",
|
||||||
"archive_page_title": "Arkisto ({})",
|
"archive_page_title": "Arkisto ({})",
|
||||||
"asset_list_layout_settings_dynamic_layout_title": "Dynaaminen asetelma",
|
"asset_list_layout_settings_dynamic_layout_title": "Dynaaminen asetelma",
|
||||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
"asset_list_layout_settings_group_automatically": "Automaattisesti",
|
||||||
"asset_list_layout_settings_group_by": "Ryhmittele",
|
"asset_list_layout_settings_group_by": "Ryhmittele",
|
||||||
"asset_list_layout_settings_group_by_month": "Kuukauden mukaan",
|
"asset_list_layout_settings_group_by_month": "Kuukauden mukaan",
|
||||||
"asset_list_layout_settings_group_by_month_day": "Kuukauden ja päivän mukaan",
|
"asset_list_layout_settings_group_by_month_day": "Kuukauden ja päivän mukaan",
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
"backup_controller_page_backup_sub": "Varmuuskopioidut kuvat ja videot",
|
"backup_controller_page_backup_sub": "Varmuuskopioidut kuvat ja videot",
|
||||||
"backup_controller_page_cancel": "Peruuta",
|
"backup_controller_page_cancel": "Peruuta",
|
||||||
"backup_controller_page_created": "Luotu: {}",
|
"backup_controller_page_created": "Luotu: {}",
|
||||||
"backup_controller_page_desc_backup": "Kytke varmuuskopiointi päälle ladataksesi uudet kohteet palvelimelle automaattisesti.",
|
"backup_controller_page_desc_backup": "Kytke varmuuskopiointi päälle lähettääksesi uudet kohteet palvelimelle automaattisesti.",
|
||||||
"backup_controller_page_excluded": "Jätetty pois:",
|
"backup_controller_page_excluded": "Jätetty pois:",
|
||||||
"backup_controller_page_failed": "Epäonnistui ({})",
|
"backup_controller_page_failed": "Epäonnistui ({})",
|
||||||
"backup_controller_page_filename": "Tiedoston nimi: {} [{}]",
|
"backup_controller_page_filename": "Tiedoston nimi: {} [{}]",
|
||||||
@@ -92,11 +92,11 @@
|
|||||||
"backup_controller_page_uploading_file_info": "Tiedostojen lähetystiedot",
|
"backup_controller_page_uploading_file_info": "Tiedostojen lähetystiedot",
|
||||||
"backup_err_only_album": "Vähintään yhden albumin tulee olla valittuna",
|
"backup_err_only_album": "Vähintään yhden albumin tulee olla valittuna",
|
||||||
"backup_info_card_assets": "kohdetta",
|
"backup_info_card_assets": "kohdetta",
|
||||||
"backup_manual_cancelled": "Cancelled",
|
"backup_manual_cancelled": "Peruutettu",
|
||||||
"backup_manual_failed": "Failed",
|
"backup_manual_failed": "Epäonnistui",
|
||||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
"backup_manual_in_progress": "Lähetys palvelimelle on jo käynnissä. Kokeile uudelleen hetken kuluttua.",
|
||||||
"backup_manual_success": "Success",
|
"backup_manual_success": "Onnistui",
|
||||||
"backup_manual_title": "Upload status",
|
"backup_manual_title": "Lähetyksen tila",
|
||||||
"cache_settings_album_thumbnails": "Kirjastosivun esikatselukuvat ({} kohdetta)",
|
"cache_settings_album_thumbnails": "Kirjastosivun esikatselukuvat ({} kohdetta)",
|
||||||
"cache_settings_clear_cache_button": "Tyhjennä välimuisti",
|
"cache_settings_clear_cache_button": "Tyhjennä välimuisti",
|
||||||
"cache_settings_clear_cache_button_title": "Tyhjennä sovelluksen välimuisti. Tämä vaikuttaa merkittävästi sovelluksen suorituskykyyn, kunnes välimuisti on rakennettu uudelleen.",
|
"cache_settings_clear_cache_button_title": "Tyhjennä sovelluksen välimuisti. Tämä vaikuttaa merkittävästi sovelluksen suorituskykyyn, kunnes välimuisti on rakennettu uudelleen.",
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
"control_bottom_app_bar_delete": "Poista",
|
"control_bottom_app_bar_delete": "Poista",
|
||||||
"control_bottom_app_bar_favorite": "Suosikki",
|
"control_bottom_app_bar_favorite": "Suosikki",
|
||||||
"control_bottom_app_bar_share": "Jaa",
|
"control_bottom_app_bar_share": "Jaa",
|
||||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
"control_bottom_app_bar_unarchive": "Palauta arkistosta",
|
||||||
"create_album_page_untitled": "Nimetön",
|
"create_album_page_untitled": "Nimetön",
|
||||||
"create_shared_album_page_create": "Luo",
|
"create_shared_album_page_create": "Luo",
|
||||||
"create_shared_album_page_share": "Jaa",
|
"create_shared_album_page_share": "Jaa",
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
"experimental_settings_new_asset_list_title": "Ota käyttöön kokeellinen kuvaruudukko",
|
"experimental_settings_new_asset_list_title": "Ota käyttöön kokeellinen kuvaruudukko",
|
||||||
"experimental_settings_subtitle": "Käyttö omalla vastuulla!",
|
"experimental_settings_subtitle": "Käyttö omalla vastuulla!",
|
||||||
"experimental_settings_title": "Kokeellinen",
|
"experimental_settings_title": "Kokeellinen",
|
||||||
"favorites_page_no_favorites": "No favorite assets found",
|
"favorites_page_no_favorites": "Suosikkikohteita ei löytynyt",
|
||||||
"favorites_page_title": "Suosikit",
|
"favorites_page_title": "Suosikit",
|
||||||
"home_page_add_to_album_conflicts": "Lisätty {added} kohdetta albumiin {album}. {failed} kohdetta on jo albumissa.",
|
"home_page_add_to_album_conflicts": "Lisätty {added} kohdetta albumiin {album}. {failed} kohdetta on jo albumissa.",
|
||||||
"home_page_add_to_album_err_local": "Paikallisten kohteiden lisääminen albumeihin ei ole mahdollista, ohitetaan",
|
"home_page_add_to_album_err_local": "Paikallisten kohteiden lisääminen albumeihin ei ole mahdollista, ohitetaan",
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
"home_page_building_timeline": "Rakennetaan aikajanaa",
|
"home_page_building_timeline": "Rakennetaan aikajanaa",
|
||||||
"home_page_favorite_err_local": "Paikallisten kohteiden lisääminen suosikkeihin ei ole mahdollista, ohitetaan",
|
"home_page_favorite_err_local": "Paikallisten kohteiden lisääminen suosikkeihin ei ole mahdollista, ohitetaan",
|
||||||
"home_page_first_time_notice": "Jos käytät sovellusta ensimmäistä kertaa, muista valita varmuuskopioitavat albumi(t), jotta aikajanalla voi olla kuvia ja videoita.",
|
"home_page_first_time_notice": "Jos käytät sovellusta ensimmäistä kertaa, muista valita varmuuskopioitavat albumi(t), jotta aikajanalla voi olla kuvia ja videoita.",
|
||||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
"home_page_upload_err_limit": "Voit lähettää palvelimelle enintään 30 kohdetta kerrallaan, ohitetaan",
|
||||||
"image_viewer_page_state_provider_download_error": "Lataus epäonnistui",
|
"image_viewer_page_state_provider_download_error": "Lataus epäonnistui",
|
||||||
"image_viewer_page_state_provider_download_success": "Lataus onnistui",
|
"image_viewer_page_state_provider_download_success": "Lataus onnistui",
|
||||||
"library_page_albums": "Albumit",
|
"library_page_albums": "Albumit",
|
||||||
@@ -172,7 +172,7 @@
|
|||||||
"library_page_sharing": "Jakaminen",
|
"library_page_sharing": "Jakaminen",
|
||||||
"library_page_sort_created": "Viimeisin luotu",
|
"library_page_sort_created": "Viimeisin luotu",
|
||||||
"library_page_sort_title": "Albumin otsikko",
|
"library_page_sort_title": "Albumin otsikko",
|
||||||
"login_disabled": "Login has been disabled",
|
"login_disabled": "Kirjautuminen on poistettu käytöstä",
|
||||||
"login_form_api_exception": "API-virhe. Tarkista palvelimen URL-osoite ja yritä uudelleen.",
|
"login_form_api_exception": "API-virhe. Tarkista palvelimen URL-osoite ja yritä uudelleen.",
|
||||||
"login_form_button_text": "Kirjaudu",
|
"login_form_button_text": "Kirjaudu",
|
||||||
"login_form_email_hint": "sahkopostisi@esimerkki.fi",
|
"login_form_email_hint": "sahkopostisi@esimerkki.fi",
|
||||||
@@ -201,15 +201,15 @@
|
|||||||
"notification_permission_list_tile_content": "Myönnä käyttöoikeus ottaaksesi ilmoitukset käyttöön.",
|
"notification_permission_list_tile_content": "Myönnä käyttöoikeus ottaaksesi ilmoitukset käyttöön.",
|
||||||
"notification_permission_list_tile_enable_button": "Ota ilmoitukset käyttöön",
|
"notification_permission_list_tile_enable_button": "Ota ilmoitukset käyttöön",
|
||||||
"notification_permission_list_tile_title": "Ilmoitusten käyttöoikeus",
|
"notification_permission_list_tile_title": "Ilmoitusten käyttöoikeus",
|
||||||
"partner_page_add_partner": "Add partner",
|
"partner_page_add_partner": "Lisää kumppani",
|
||||||
"partner_page_empty_message": "Your photos are not yet shared with any partner.",
|
"partner_page_empty_message": "Kuviasi ei ole vielä jaettu kenenkään kumppanin kanssa.",
|
||||||
"partner_page_no_more_users": "No more users to add",
|
"partner_page_no_more_users": "Ei enempää käyttäjiä lisättäväksi",
|
||||||
"partner_page_partner_add_failed": "Failed to add partner",
|
"partner_page_partner_add_failed": "Kumppanin lisääminen epäonnistui",
|
||||||
"partner_page_select_partner": "Select partner",
|
"partner_page_select_partner": "Valitse kumppani",
|
||||||
"partner_page_shared_to_title": "Shared to",
|
"partner_page_shared_to_title": "Jaettu henkilöille",
|
||||||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
"partner_page_stop_sharing_content": "{} ei voi enää käyttää kuviasi.",
|
||||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
"partner_page_stop_sharing_title": "Lopetetaanko kuvien jakaminen?",
|
||||||
"partner_page_title": "Partner",
|
"partner_page_title": "Kumppani",
|
||||||
"permission_onboarding_continue_anyway": "Jatka silti",
|
"permission_onboarding_continue_anyway": "Jatka silti",
|
||||||
"permission_onboarding_get_started": "Aloittaminen",
|
"permission_onboarding_get_started": "Aloittaminen",
|
||||||
"permission_onboarding_go_to_settings": "Siirry asetuksiin",
|
"permission_onboarding_go_to_settings": "Siirry asetuksiin",
|
||||||
@@ -230,7 +230,7 @@
|
|||||||
"search_page_motion_photos": "Liikekuvat",
|
"search_page_motion_photos": "Liikekuvat",
|
||||||
"search_page_no_objects": "Objektitietoja ei ole saatavilla",
|
"search_page_no_objects": "Objektitietoja ei ole saatavilla",
|
||||||
"search_page_no_places": "Paikkatietoja ei ole saatavilla",
|
"search_page_no_places": "Paikkatietoja ei ole saatavilla",
|
||||||
"search_page_people": "People",
|
"search_page_people": "Ihmiset",
|
||||||
"search_page_places": "Paikat",
|
"search_page_places": "Paikat",
|
||||||
"search_page_recently_added": "Viimeksi lisätyt",
|
"search_page_recently_added": "Viimeksi lisätyt",
|
||||||
"search_page_screenshots": "Näyttökuvat",
|
"search_page_screenshots": "Näyttökuvat",
|
||||||
@@ -291,10 +291,10 @@
|
|||||||
"theme_setting_theme_title": "Teema",
|
"theme_setting_theme_title": "Teema",
|
||||||
"theme_setting_three_stage_loading_subtitle": "Kolmivaiheinen lataaminen saattaa parantaa latauksen suorituskykyä, mutta lisää kaistankäyttöä huomattavasti.",
|
"theme_setting_three_stage_loading_subtitle": "Kolmivaiheinen lataaminen saattaa parantaa latauksen suorituskykyä, mutta lisää kaistankäyttöä huomattavasti.",
|
||||||
"theme_setting_three_stage_loading_title": "Ota kolmivaiheinen lataus käyttöön",
|
"theme_setting_three_stage_loading_title": "Ota kolmivaiheinen lataus käyttöön",
|
||||||
"upload_dialog_cancel": "Cancel",
|
"upload_dialog_cancel": "Peruuta",
|
||||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
"upload_dialog_info": "Haluatko varmuuskopioida valitut kohteet palvelimelle?",
|
||||||
"upload_dialog_ok": "Upload",
|
"upload_dialog_ok": "Lähetä",
|
||||||
"upload_dialog_title": "Upload Asset",
|
"upload_dialog_title": "Lähetä kohde",
|
||||||
"version_announcement_overlay_ack": "Tiedostan",
|
"version_announcement_overlay_ack": "Tiedostan",
|
||||||
"version_announcement_overlay_release_notes": "julkaisutiedoissa",
|
"version_announcement_overlay_release_notes": "julkaisutiedoissa",
|
||||||
"version_announcement_overlay_text_1": "Hei, kaveri! Uusi palvelinversio on saatavilla sovelluksesta",
|
"version_announcement_overlay_text_1": "Hei, kaveri! Uusi palvelinversio on saatavilla sovelluksesta",
|
||||||
|
|||||||
@@ -92,11 +92,11 @@
|
|||||||
"backup_controller_page_uploading_file_info": "Transfert des informations du fichier",
|
"backup_controller_page_uploading_file_info": "Transfert des informations du fichier",
|
||||||
"backup_err_only_album": "Impossible de retirer le seul album",
|
"backup_err_only_album": "Impossible de retirer le seul album",
|
||||||
"backup_info_card_assets": "éléments",
|
"backup_info_card_assets": "éléments",
|
||||||
"backup_manual_cancelled": "Cancelled",
|
"backup_manual_cancelled": "Annulé",
|
||||||
"backup_manual_failed": "Failed",
|
"backup_manual_failed": "Echec",
|
||||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
"backup_manual_in_progress": "Téléchargement déjà en cours. Essayez après un instant",
|
||||||
"backup_manual_success": "Success",
|
"backup_manual_success": "Succès ",
|
||||||
"backup_manual_title": "Upload status",
|
"backup_manual_title": "Statut du téléchargement ",
|
||||||
"cache_settings_album_thumbnails": "Miniatures de la page bibliothèque ({} éléments)",
|
"cache_settings_album_thumbnails": "Miniatures de la page bibliothèque ({} éléments)",
|
||||||
"cache_settings_clear_cache_button": "Effacer le cache",
|
"cache_settings_clear_cache_button": "Effacer le cache",
|
||||||
"cache_settings_clear_cache_button_title": "Efface le cache de l'application. Cela aura un impact significatif sur les performances de l'application jusqu'à ce que le cache soit reconstruit.",
|
"cache_settings_clear_cache_button_title": "Efface le cache de l'application. Cela aura un impact significatif sur les performances de l'application jusqu'à ce que le cache soit reconstruit.",
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
"home_page_building_timeline": "Construction de la chronologie",
|
"home_page_building_timeline": "Construction de la chronologie",
|
||||||
"home_page_favorite_err_local": "Impossible d'ajouter des éléments locaux aux favoris pour le moment, étape ignorée",
|
"home_page_favorite_err_local": "Impossible d'ajouter des éléments locaux aux favoris pour le moment, étape ignorée",
|
||||||
"home_page_first_time_notice": "Si c'est la première fois que vous utilisez l'application, veillez à choisir un ou plusieurs albums de sauvegarde afin que la chronologie puisse alimenter les photos et les vidéos de cet ou ces albums.",
|
"home_page_first_time_notice": "Si c'est la première fois que vous utilisez l'application, veillez à choisir un ou plusieurs albums de sauvegarde afin que la chronologie puisse alimenter les photos et les vidéos de cet ou ces albums.",
|
||||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
"home_page_upload_err_limit": "Limite de téléchargement de 30 éléments en même temps, demande ignorée",
|
||||||
"image_viewer_page_state_provider_download_error": "Erreur de téléchargement",
|
"image_viewer_page_state_provider_download_error": "Erreur de téléchargement",
|
||||||
"image_viewer_page_state_provider_download_success": "Téléchargement réussi",
|
"image_viewer_page_state_provider_download_success": "Téléchargement réussi",
|
||||||
"library_page_albums": "Albums",
|
"library_page_albums": "Albums",
|
||||||
@@ -172,7 +172,7 @@
|
|||||||
"library_page_sharing": "Partage",
|
"library_page_sharing": "Partage",
|
||||||
"library_page_sort_created": "Créations les plus récentes",
|
"library_page_sort_created": "Créations les plus récentes",
|
||||||
"library_page_sort_title": "Titre de l'album",
|
"library_page_sort_title": "Titre de l'album",
|
||||||
"login_disabled": "Login has been disabled",
|
"login_disabled": "La connexion a été désactivée ",
|
||||||
"login_form_api_exception": "Erreur de l'API. Veuillez vérifier l'URL du serveur et et réessayer.",
|
"login_form_api_exception": "Erreur de l'API. Veuillez vérifier l'URL du serveur et et réessayer.",
|
||||||
"login_form_button_text": "Connexion",
|
"login_form_button_text": "Connexion",
|
||||||
"login_form_email_hint": "votreemail@email.com",
|
"login_form_email_hint": "votreemail@email.com",
|
||||||
@@ -291,10 +291,10 @@
|
|||||||
"theme_setting_theme_title": "Thème",
|
"theme_setting_theme_title": "Thème",
|
||||||
"theme_setting_three_stage_loading_subtitle": "Le chargement en trois étapes peut améliorer les performances de chargement, mais entraîne une augmentation significative de la charge du réseau.",
|
"theme_setting_three_stage_loading_subtitle": "Le chargement en trois étapes peut améliorer les performances de chargement, mais entraîne une augmentation significative de la charge du réseau.",
|
||||||
"theme_setting_three_stage_loading_title": "Activer le chargement en trois étapes",
|
"theme_setting_three_stage_loading_title": "Activer le chargement en trois étapes",
|
||||||
"upload_dialog_cancel": "Cancel",
|
"upload_dialog_cancel": "Annuler",
|
||||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
"upload_dialog_info": "Voulez-vous sauvegarder la sélection vers le serveur ?",
|
||||||
"upload_dialog_ok": "Upload",
|
"upload_dialog_ok": "Télécharger ",
|
||||||
"upload_dialog_title": "Upload Asset",
|
"upload_dialog_title": "Télécharger cet élément ",
|
||||||
"version_announcement_overlay_ack": "Confirmer",
|
"version_announcement_overlay_ack": "Confirmer",
|
||||||
"version_announcement_overlay_release_notes": "notes de mise à jour",
|
"version_announcement_overlay_release_notes": "notes de mise à jour",
|
||||||
"version_announcement_overlay_text_1": "Bonjour, une nouvelle version de",
|
"version_announcement_overlay_text_1": "Bonjour, une nouvelle version de",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
"add_to_album_bottom_sheet_added": "Hozzáadva a(z) {album} nevű albumhoz",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
"add_to_album_bottom_sheet_already_exists": "Már eleme a(z) {album} nevű albumnak",
|
||||||
"advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.",
|
"advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.",
|
||||||
"advanced_settings_prefer_remote_title": "Prefer remote images",
|
"advanced_settings_prefer_remote_title": "Prefer remote images",
|
||||||
"advanced_settings_tile_subtitle": "Haladó felhasználói beállítások",
|
"advanced_settings_tile_subtitle": "Haladó felhasználói beállítások",
|
||||||
@@ -9,107 +9,107 @@
|
|||||||
"advanced_settings_troubleshooting_title": "Hibaelhárítás",
|
"advanced_settings_troubleshooting_title": "Hibaelhárítás",
|
||||||
"album_info_card_backup_album_excluded": "EXCLUDED",
|
"album_info_card_backup_album_excluded": "EXCLUDED",
|
||||||
"album_info_card_backup_album_included": "INCLUDED",
|
"album_info_card_backup_album_included": "INCLUDED",
|
||||||
"album_thumbnail_card_item": "1 item",
|
"album_thumbnail_card_item": "1 elem",
|
||||||
"album_thumbnail_card_items": "{} items",
|
"album_thumbnail_card_items": "{} elem",
|
||||||
"album_thumbnail_card_shared": " · Shared",
|
"album_thumbnail_card_shared": "· Megosztott",
|
||||||
"album_thumbnail_owned": "Tulajdonos",
|
"album_thumbnail_owned": "Tulajdonos",
|
||||||
"album_thumbnail_shared_by": "Megosztotta: {}",
|
"album_thumbnail_shared_by": "Megosztotta: {}",
|
||||||
"album_viewer_appbar_share_delete": "Delete album",
|
"album_viewer_appbar_share_delete": "Album törlése",
|
||||||
"album_viewer_appbar_share_err_delete": "Failed to delete album",
|
"album_viewer_appbar_share_err_delete": "Hiba az album törlése közben",
|
||||||
"album_viewer_appbar_share_err_leave": "Failed to leave album",
|
"album_viewer_appbar_share_err_leave": "Hiba az albumból való kilépés közben",
|
||||||
"album_viewer_appbar_share_err_remove": "There are problems in removing assets from album",
|
"album_viewer_appbar_share_err_remove": "Hiba az elemek törlése közben",
|
||||||
"album_viewer_appbar_share_err_title": "Failed to change album title",
|
"album_viewer_appbar_share_err_title": "Hiba az album átnevezése közben",
|
||||||
"album_viewer_appbar_share_leave": "Leave album",
|
"album_viewer_appbar_share_leave": "Kilépés az albumból",
|
||||||
"album_viewer_appbar_share_remove": "Remove from album",
|
"album_viewer_appbar_share_remove": "Törlés az albumból",
|
||||||
"album_viewer_page_share_add_users": "Add users",
|
"album_viewer_page_share_add_users": "Felhasználók hozzáadása",
|
||||||
"all_people_page_title": "People",
|
"all_people_page_title": "Emberek",
|
||||||
"all_videos_page_title": "Videók",
|
"all_videos_page_title": "Videók",
|
||||||
"archive_page_no_archived_assets": "Nem található archivált média",
|
"archive_page_no_archived_assets": "Nem található archivált média",
|
||||||
"archive_page_title": "Archívum ({})",
|
"archive_page_title": "Archívum ({})",
|
||||||
"asset_list_layout_settings_dynamic_layout_title": "Dynamic layout",
|
"asset_list_layout_settings_dynamic_layout_title": "Dynamic layout",
|
||||||
"asset_list_layout_settings_group_automatically": "Automatikus",
|
"asset_list_layout_settings_group_automatically": "Automatikus",
|
||||||
"asset_list_layout_settings_group_by": "Group assets by",
|
"asset_list_layout_settings_group_by": "Group assets by",
|
||||||
"asset_list_layout_settings_group_by_month": "Month",
|
"asset_list_layout_settings_group_by_month": "Hónap",
|
||||||
"asset_list_layout_settings_group_by_month_day": "Month + day",
|
"asset_list_layout_settings_group_by_month_day": "Hónap + nap",
|
||||||
"asset_list_settings_subtitle": "Photo grid layout settings",
|
"asset_list_settings_subtitle": "Photo grid layout settings",
|
||||||
"asset_list_settings_title": "Photo Grid",
|
"asset_list_settings_title": "Photo Grid",
|
||||||
"backup_album_selection_page_albums_device": "Albums on device ({})",
|
"backup_album_selection_page_albums_device": "Az eszközön lévő albumok ({})",
|
||||||
"backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude",
|
"backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude",
|
||||||
"backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.",
|
"backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.",
|
||||||
"backup_album_selection_page_select_albums": "Select albums",
|
"backup_album_selection_page_select_albums": "Albumok kiválasztása",
|
||||||
"backup_album_selection_page_selection_info": "Selection Info",
|
"backup_album_selection_page_selection_info": "Selection Info",
|
||||||
"backup_album_selection_page_total_assets": "Total unique assets",
|
"backup_album_selection_page_total_assets": "Összes egyedi elem",
|
||||||
"backup_all": "All",
|
"backup_all": "Összes",
|
||||||
"backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
|
"backup_background_service_backup_failed_message": "HIba a mentés közben. Újrapróbálkozás...",
|
||||||
"backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…",
|
"backup_background_service_connection_failed_message": "HIba a szerverhez való csatlakozás közben. Újrapróbálkozás...",
|
||||||
"backup_background_service_current_upload_notification": "Uploading {}",
|
"backup_background_service_current_upload_notification": "Feltöltés {}",
|
||||||
"backup_background_service_default_notification": "Checking for new assets…",
|
"backup_background_service_default_notification": "Keresés új elemek után...",
|
||||||
"backup_background_service_error_title": "Backup error",
|
"backup_background_service_error_title": "Hiba mentés közben",
|
||||||
"backup_background_service_in_progress_notification": "Backing up your assets…",
|
"backup_background_service_in_progress_notification": "Elemek mentés alatt..",
|
||||||
"backup_background_service_upload_failure_notification": "Failed to upload {}",
|
"backup_background_service_upload_failure_notification": "Hiba feltöltés közben {}",
|
||||||
"backup_controller_page_albums": "Backup Albums",
|
"backup_controller_page_albums": "Albumok mentése",
|
||||||
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
|
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
|
||||||
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
|
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
|
||||||
"backup_controller_page_background_app_refresh_enable_button_text": "Beállítások megnyitása",
|
"backup_controller_page_background_app_refresh_enable_button_text": "Beállítások megnyitása",
|
||||||
"backup_controller_page_background_battery_info_link": "Show me how",
|
"backup_controller_page_background_battery_info_link": "Mutasd meg hogyan",
|
||||||
"backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.",
|
"backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.",
|
||||||
"backup_controller_page_background_battery_info_ok": "OK",
|
"backup_controller_page_background_battery_info_ok": "OK",
|
||||||
"backup_controller_page_background_battery_info_title": "Battery optimizations",
|
"backup_controller_page_background_battery_info_title": "Akkumulátoroptimalizálás",
|
||||||
"backup_controller_page_background_charging": "Only while charging",
|
"backup_controller_page_background_charging": "Csak töltés közben",
|
||||||
"backup_controller_page_background_configure_error": "Failed to configure the background service",
|
"backup_controller_page_background_configure_error": "Failed to configure the background service",
|
||||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||||
"backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app",
|
"backup_controller_page_background_description": "Kapcsold be a háttérfolyamatot, hogy automatikusan mentsen elemeket az applikáció megnyitása nélkül",
|
||||||
"backup_controller_page_background_is_off": "Automatic background backup is off",
|
"backup_controller_page_background_is_off": "Automatikus mentés a háttérben ki van kapcsolva",
|
||||||
"backup_controller_page_background_is_on": "Automatic background backup is on",
|
"backup_controller_page_background_is_on": "Automatikus mentés a háttérben bekapcsolva",
|
||||||
"backup_controller_page_background_turn_off": "Turn off background service",
|
"backup_controller_page_background_turn_off": "Háttérfolyamat kikapcsolása",
|
||||||
"backup_controller_page_background_turn_on": "Turn on background service",
|
"backup_controller_page_background_turn_on": "Háttérfolyamat bekapcsolása",
|
||||||
"backup_controller_page_background_wifi": "Only on WiFi",
|
"backup_controller_page_background_wifi": "Csak WiFi-n",
|
||||||
"backup_controller_page_backup": "Backup",
|
"backup_controller_page_backup": "Mentés",
|
||||||
"backup_controller_page_backup_selected": "Selected: ",
|
"backup_controller_page_backup_selected": "Kiválasztva:",
|
||||||
"backup_controller_page_backup_sub": "Backed up photos and videos",
|
"backup_controller_page_backup_sub": "Mentett fotók és videók",
|
||||||
"backup_controller_page_cancel": "Cancel",
|
"backup_controller_page_cancel": "Megszakít",
|
||||||
"backup_controller_page_created": "Created on: {}",
|
"backup_controller_page_created": "Létrehozva: {}",
|
||||||
"backup_controller_page_desc_backup": "Turn on foreground backup to automatically upload new assets to the server when opening the app.",
|
"backup_controller_page_desc_backup": "Turn on foreground backup to automatically upload new assets to the server when opening the app.",
|
||||||
"backup_controller_page_excluded": "Excluded: ",
|
"backup_controller_page_excluded": "Kivéve:",
|
||||||
"backup_controller_page_failed": "Failed ({})",
|
"backup_controller_page_failed": "Sikertelen ({})",
|
||||||
"backup_controller_page_filename": "File name: {} [{}]",
|
"backup_controller_page_filename": "Fájlnév: {}[{}]",
|
||||||
"backup_controller_page_id": "ID: {}",
|
"backup_controller_page_id": "Azonosító: {}",
|
||||||
"backup_controller_page_info": "Backup Information",
|
"backup_controller_page_info": "Mentésinformációk",
|
||||||
"backup_controller_page_none_selected": "None selected",
|
"backup_controller_page_none_selected": "Egy sincs kiválasztva",
|
||||||
"backup_controller_page_remainder": "Remainder",
|
"backup_controller_page_remainder": "Maradék",
|
||||||
"backup_controller_page_remainder_sub": "Remaining photos and videos to back up from selection",
|
"backup_controller_page_remainder_sub": "Hátralévő fotók és videók a kijelöltek közül",
|
||||||
"backup_controller_page_select": "Select",
|
"backup_controller_page_select": "Kiválaszt",
|
||||||
"backup_controller_page_server_storage": "Server Storage",
|
"backup_controller_page_server_storage": "Szerver tárhely",
|
||||||
"backup_controller_page_start_backup": "Start Backup",
|
"backup_controller_page_start_backup": "Mentés elindítása",
|
||||||
"backup_controller_page_status_off": "Automatic foreground backup is off",
|
"backup_controller_page_status_off": "Autoatikus mentés az előtérben kikapcsolva",
|
||||||
"backup_controller_page_status_on": "Automatic foreground backup is on",
|
"backup_controller_page_status_on": "Autoatikus mentés az előtérben bekapcsolva",
|
||||||
"backup_controller_page_storage_format": "{} of {} used",
|
"backup_controller_page_storage_format": "{} / {} felhasználva",
|
||||||
"backup_controller_page_to_backup": "Albums to be backup",
|
"backup_controller_page_to_backup": "Albumok amiket mentesz",
|
||||||
"backup_controller_page_total": "Total",
|
"backup_controller_page_total": "Összes",
|
||||||
"backup_controller_page_total_sub": "All unique photos and videos from selected albums",
|
"backup_controller_page_total_sub": "Minden egyedi fotó és videó a kijelölt albumokból",
|
||||||
"backup_controller_page_turn_off": "Turn off foreground backup",
|
"backup_controller_page_turn_off": "Turn off foreground backup",
|
||||||
"backup_controller_page_turn_on": "Turn on foreground backup",
|
"backup_controller_page_turn_on": "Turn on foreground backup",
|
||||||
"backup_controller_page_uploading_file_info": "Uploading file info",
|
"backup_controller_page_uploading_file_info": "Uploading file info",
|
||||||
"backup_err_only_album": "Cannot remove the only album",
|
"backup_err_only_album": "Az utolsó albumot nem tudod törölni",
|
||||||
"backup_info_card_assets": "assets",
|
"backup_info_card_assets": "elemek",
|
||||||
"backup_manual_cancelled": "Cancelled",
|
"backup_manual_cancelled": "Megszakítva",
|
||||||
"backup_manual_failed": "Failed",
|
"backup_manual_failed": "Failed",
|
||||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
||||||
"backup_manual_success": "Success",
|
"backup_manual_success": "Sikeres",
|
||||||
"backup_manual_title": "Upload status",
|
"backup_manual_title": "Upload status",
|
||||||
"cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
|
"cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
|
||||||
"cache_settings_clear_cache_button": "Clear cache",
|
"cache_settings_clear_cache_button": "Gyorsítótár törlése",
|
||||||
"cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
|
"cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
|
||||||
"cache_settings_image_cache_size": "Image cache size ({} assets)",
|
"cache_settings_image_cache_size": "Image cache size ({} assets)",
|
||||||
"cache_settings_statistics_album": "Library thumbnails",
|
"cache_settings_statistics_album": "Library thumbnails",
|
||||||
"cache_settings_statistics_assets": "{} assets ({})",
|
"cache_settings_statistics_assets": "{} assets ({})",
|
||||||
"cache_settings_statistics_full": "Full images",
|
"cache_settings_statistics_full": "Teljes képek",
|
||||||
"cache_settings_statistics_shared": "Shared album thumbnails",
|
"cache_settings_statistics_shared": "Shared album thumbnails",
|
||||||
"cache_settings_statistics_thumbnail": "Thumbnails",
|
"cache_settings_statistics_thumbnail": "Előnézeti képek",
|
||||||
"cache_settings_statistics_title": "Cache usage",
|
"cache_settings_statistics_title": "Gyorsítótár által használt terület",
|
||||||
"cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application",
|
"cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application",
|
||||||
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
|
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
|
||||||
"cache_settings_title": "Caching Settings",
|
"cache_settings_title": "Gyorsítótár beállítások",
|
||||||
"change_password_form_confirm_password": "Jelszó Megerősítése",
|
"change_password_form_confirm_password": "Jelszó Megerősítése",
|
||||||
"change_password_form_description": "Kedves {lastName} {firstName}!\n\nMost jelentkezel be először a rendszerbe vagy más okból szükséfes a jelszavad meváltoztatása. Kérjük, add meg új jelszavad.",
|
"change_password_form_description": "Kedves {lastName} {firstName}!\n\nMost jelentkezel be először a rendszerbe vagy más okból szükséfes a jelszavad meváltoztatása. Kérjük, add meg új jelszavad.",
|
||||||
"change_password_form_new_password": "Új Jelszó",
|
"change_password_form_new_password": "Új Jelszó",
|
||||||
@@ -118,42 +118,42 @@
|
|||||||
"common_add_to_album": "Albumhoz ad",
|
"common_add_to_album": "Albumhoz ad",
|
||||||
"common_change_password": "Jelszócsere",
|
"common_change_password": "Jelszócsere",
|
||||||
"common_create_new_album": "Új album létrehozása",
|
"common_create_new_album": "Új album létrehozása",
|
||||||
"common_server_error": "Kérjük, ellenőrid a hálózati kapcsolatot, gondoskodj róla, hogy a szerver elérhető legyen, valamint az app és a szerver kompatibilis verziójú legyen.",
|
"common_server_error": "Kérjük, ellenőrizd a hálózati kapcsolatot, gondoskodj róla, hogy a szerver elérhető legyen, valamint az app és a szerver kompatibilis verziójú legyen.",
|
||||||
"common_shared": "Megosztva",
|
"common_shared": "Megosztva",
|
||||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
"control_bottom_app_bar_add_to_album": "Hozzáadás az albumhoz",
|
||||||
"control_bottom_app_bar_album_info": "{} items",
|
"control_bottom_app_bar_album_info": "{} elem",
|
||||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
"control_bottom_app_bar_album_info_shared": "{} elemek· Megosztva",
|
||||||
"control_bottom_app_bar_archive": "Archivál",
|
"control_bottom_app_bar_archive": "Archivál",
|
||||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
"control_bottom_app_bar_create_new_album": "Album létrehozása",
|
||||||
"control_bottom_app_bar_delete": "Delete",
|
"control_bottom_app_bar_delete": "Törlés",
|
||||||
"control_bottom_app_bar_favorite": "Kedvenc",
|
"control_bottom_app_bar_favorite": "Kedvenc",
|
||||||
"control_bottom_app_bar_share": "Share",
|
"control_bottom_app_bar_share": "Megosztás",
|
||||||
"control_bottom_app_bar_unarchive": "Archiválás megszüntetése",
|
"control_bottom_app_bar_unarchive": "Archiválás megszüntetése",
|
||||||
"create_album_page_untitled": "Untitled",
|
"create_album_page_untitled": "Névtelen",
|
||||||
"create_shared_album_page_create": "Create",
|
"create_shared_album_page_create": "Létrehoz",
|
||||||
"create_shared_album_page_share": "Share",
|
"create_shared_album_page_share": "Megosztás",
|
||||||
"create_shared_album_page_share_add_assets": "ADD ASSETS",
|
"create_shared_album_page_share_add_assets": "ELEMEK HOZZÁADÁSA",
|
||||||
"create_shared_album_page_share_select_photos": "Select Photos",
|
"create_shared_album_page_share_select_photos": "Fotók kiválasztása",
|
||||||
"curated_location_page_title": "Helyek",
|
"curated_location_page_title": "Helyek",
|
||||||
"curated_object_page_title": "Dolgok",
|
"curated_object_page_title": "Dolgok",
|
||||||
"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",
|
||||||
"date_format": "E, LLL d, y • h:mm a",
|
"date_format": "E, LLL d, y • h:mm a",
|
||||||
"delete_dialog_alert": "These items will be permanently deleted from Immich and from your device",
|
"delete_dialog_alert": "Ezek az elemek véglegesen törölve lesznek Immich-ről és az eszközödről is",
|
||||||
"delete_dialog_cancel": "Cancel",
|
"delete_dialog_cancel": "Mégse",
|
||||||
"delete_dialog_ok": "Delete",
|
"delete_dialog_ok": "Törlés",
|
||||||
"delete_dialog_title": "Delete Permanently",
|
"delete_dialog_title": "Törlés véglegesen",
|
||||||
"description_input_hint_text": "Leírás hozzáadása...",
|
"description_input_hint_text": "Leírás hozzáadása...",
|
||||||
"description_input_submit_error": "Nem sikerült frissíteni a leírást. További információért kérjük, nézd meg az eseménynaplót",
|
"description_input_submit_error": "Nem sikerült frissíteni a leírást. További információért kérjük, nézd meg az eseménynaplót",
|
||||||
"exif_bottom_sheet_description": "Add Description...",
|
"exif_bottom_sheet_description": "Leírás hozzáadása...",
|
||||||
"exif_bottom_sheet_details": "DETAILS",
|
"exif_bottom_sheet_details": "RÉSZLETEK",
|
||||||
"exif_bottom_sheet_location": "LOCATION",
|
"exif_bottom_sheet_location": "HELYSZÍN",
|
||||||
"experimental_settings_new_asset_list_subtitle": "Work in progress",
|
"experimental_settings_new_asset_list_subtitle": "Fejlesztés alatt",
|
||||||
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
||||||
"experimental_settings_subtitle": "Use at your own risk!",
|
"experimental_settings_subtitle": "Csak saját felelősségre használd",
|
||||||
"experimental_settings_title": "Experimental",
|
"experimental_settings_title": "Kísérleti",
|
||||||
"favorites_page_no_favorites": "Nem található kedvencnek jelölt média",
|
"favorites_page_no_favorites": "Nem található kedvencnek jelölt média",
|
||||||
"favorites_page_title": "Favorites",
|
"favorites_page_title": "Kedvencek",
|
||||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||||
"home_page_add_to_album_err_local": "Helyi médiát még nem lehet albumba tenni. Kihagyjuk.",
|
"home_page_add_to_album_err_local": "Helyi médiát még nem lehet albumba tenni. Kihagyjuk.",
|
||||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||||
@@ -161,42 +161,42 @@
|
|||||||
"home_page_building_timeline": "Building the timeline",
|
"home_page_building_timeline": "Building the timeline",
|
||||||
"home_page_favorite_err_local": "Helyi médiát még nem lehet a kedvencek közé tenni. Kihagyjuk.",
|
"home_page_favorite_err_local": "Helyi médiát még nem lehet a kedvencek közé tenni. Kihagyjuk.",
|
||||||
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
||||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
"home_page_upload_err_limit": "Csak 30 elemet tudsz egyszerre feltölteni, átugrás",
|
||||||
"image_viewer_page_state_provider_download_error": "Letöltési Hiba",
|
"image_viewer_page_state_provider_download_error": "Letöltési Hiba",
|
||||||
"image_viewer_page_state_provider_download_success": "Letöltés Sikeres",
|
"image_viewer_page_state_provider_download_success": "Letöltés Sikeres",
|
||||||
"library_page_albums": "Albums",
|
"library_page_albums": "Albumok",
|
||||||
"library_page_archive": "Archívum",
|
"library_page_archive": "Archívum",
|
||||||
"library_page_device_albums": "Albumok az Eszközön",
|
"library_page_device_albums": "Albumok az Eszközön",
|
||||||
"library_page_favorites": "Favorites",
|
"library_page_favorites": "Kedvencek",
|
||||||
"library_page_new_album": "New album",
|
"library_page_new_album": "Új album",
|
||||||
"library_page_sharing": "Sharing",
|
"library_page_sharing": "Megosztás\n",
|
||||||
"library_page_sort_created": "Most recently created",
|
"library_page_sort_created": "Legutoljára létrehozott",
|
||||||
"library_page_sort_title": "Album title",
|
"library_page_sort_title": "Album címe",
|
||||||
"login_disabled": "Login has been disabled",
|
"login_disabled": "A bejelentkezés letiltva",
|
||||||
"login_form_api_exception": "API hiba. Kérljük, ellenőrid a szerver címét, majd próbáld újra.",
|
"login_form_api_exception": "API hiba. Kérljük, ellenőrid a szerver címét, majd próbáld újra.",
|
||||||
"login_form_button_text": "Login",
|
"login_form_button_text": "Belépés",
|
||||||
"login_form_email_hint": "youremail@email.com",
|
"login_form_email_hint": "teemailed@email.com",
|
||||||
"login_form_endpoint_hint": "http://your-server-ip:port/api",
|
"login_form_endpoint_hint": "http://szerver-címe:port/api",
|
||||||
"login_form_endpoint_url": "Server Endpoint URL",
|
"login_form_endpoint_url": "Kiszolgáló végpont címe",
|
||||||
"login_form_err_http": "Please specify http:// or https://",
|
"login_form_err_http": "Kérem, adjon meg egy http:// vagy https:// címet",
|
||||||
"login_form_err_invalid_email": "Invalid Email",
|
"login_form_err_invalid_email": "Érvénytelen email cím",
|
||||||
"login_form_err_invalid_url": "Invalid URL",
|
"login_form_err_invalid_url": "Érvénytelen cím",
|
||||||
"login_form_err_leading_whitespace": "Leading whitespace",
|
"login_form_err_leading_whitespace": "Az első karakter szóköz",
|
||||||
"login_form_err_trailing_whitespace": "Trailing whitespace",
|
"login_form_err_trailing_whitespace": "Az utolsó karakter szóköz",
|
||||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||||
"login_form_failed_login": "Error logging you in, check server URL, email and password",
|
"login_form_failed_login": "Hiba bejelentkezés közben, ellenőrizd a címet, email-t és a jelszót",
|
||||||
"login_form_label_email": "Email",
|
"login_form_label_email": "Email",
|
||||||
"login_form_label_password": "Password",
|
"login_form_label_password": "Jelszó",
|
||||||
"login_form_next_button": "Következő",
|
"login_form_next_button": "Következő",
|
||||||
"login_form_password_hint": "password",
|
"login_form_password_hint": "jelszó",
|
||||||
"login_form_save_login": "Stay logged in",
|
"login_form_save_login": "Maradjon bejelentkezve",
|
||||||
"login_form_server_empty": "Add meg a szerver címét.",
|
"login_form_server_empty": "Add meg a szerver címét.",
|
||||||
"login_form_server_error": "Nem sikerült kapcsolódni a szerverhez.",
|
"login_form_server_error": "Nem sikerült kapcsolódni a szerverhez.",
|
||||||
"monthly_title_text_date_format": "MMMM y",
|
"monthly_title_text_date_format": "MMMM y",
|
||||||
"motion_photos_page_title": "Mozgó Fotók",
|
"motion_photos_page_title": "Mozgó Fotók",
|
||||||
"notification_permission_dialog_cancel": "Mégsem",
|
"notification_permission_dialog_cancel": "Mégsem",
|
||||||
"notification_permission_dialog_content": "Az értesítések bakapcsolásához a Beállítások menüben válaszd ki az Engedélyezés-t.",
|
"notification_permission_dialog_content": "Az értesítések bekapcsolásához a Beállítások menüben válaszd ki az Engedélyezés-t.",
|
||||||
"notification_permission_dialog_settings": "Beállítások",
|
"notification_permission_dialog_settings": "Beállítások",
|
||||||
"notification_permission_list_tile_content": "Értesítések engedélyezése",
|
"notification_permission_list_tile_content": "Értesítések engedélyezése",
|
||||||
"notification_permission_list_tile_enable_button": "Értesítések Bekapcsolása",
|
"notification_permission_list_tile_enable_button": "Értesítések Bekapcsolása",
|
||||||
@@ -215,36 +215,36 @@
|
|||||||
"permission_onboarding_go_to_settings": "Beállítások megnyitása",
|
"permission_onboarding_go_to_settings": "Beállítások megnyitása",
|
||||||
"permission_onboarding_grant_permission": "Engedélyezés",
|
"permission_onboarding_grant_permission": "Engedélyezés",
|
||||||
"permission_onboarding_log_out": "Kijelentkezés",
|
"permission_onboarding_log_out": "Kijelentkezés",
|
||||||
"permission_onboarding_permission_denied": "Hozzáférés megtagadva. Az Immich használatához enedélyezni kell a fotó és videó hozzáférést a Beállításokban.",
|
"permission_onboarding_permission_denied": "Hozzáférés megtagadva. Az Immich használatához engedélyezni kell a fotó és videó hozzáférést a Beállításokban.",
|
||||||
"permission_onboarding_permission_granted": "Hozzáférés engedélyezve! Minden készen áll.",
|
"permission_onboarding_permission_granted": "Hozzáférés engedélyezve! Minden készen áll.",
|
||||||
"permission_onboarding_permission_limited": "Korlátozott hozzáférés. Ha szeretnéd, hogy az Immich a teljes galéria gyűjteményedet mentse és kezelje, akkor a Beállításokban engedélyezd a fotó és videó jogosultságokat.",
|
"permission_onboarding_permission_limited": "Korlátozott hozzáférés. Ha szeretnéd, hogy az Immich a teljes galéria gyűjteményedet mentse és kezelje, akkor a Beállításokban engedélyezd a fotó és videó jogosultságokat.",
|
||||||
"permission_onboarding_request": "Engedélyezni kell, hogy az Immich hozzáférjen a képekhez és videókhoz",
|
"permission_onboarding_request": "Engedélyezni kell, hogy az Immich hozzáférjen a képekhez és videókhoz",
|
||||||
"profile_drawer_app_logs": "Logs",
|
"profile_drawer_app_logs": "Naplók",
|
||||||
"profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
|
"profile_drawer_client_server_up_to_date": "Kliens és a szerver is naprakész",
|
||||||
"profile_drawer_settings": "Settings",
|
"profile_drawer_settings": "Beállítások",
|
||||||
"profile_drawer_sign_out": "Sign Out",
|
"profile_drawer_sign_out": "Kijelentkezés",
|
||||||
"recently_added_page_title": "Nemrég Hozzáadott",
|
"recently_added_page_title": "Nemrég Hozzáadott",
|
||||||
"search_bar_hint": "Search your photos",
|
"search_bar_hint": "Keress a fotóid között",
|
||||||
"search_page_categories": "Kategóriák",
|
"search_page_categories": "Kategóriák",
|
||||||
"search_page_favorites": "Kedvencek",
|
"search_page_favorites": "Kedvencek",
|
||||||
"search_page_motion_photos": "Mozgó Fotók",
|
"search_page_motion_photos": "Mozgó Fotók",
|
||||||
"search_page_no_objects": "No Objects Info Available",
|
"search_page_no_objects": "No Objects Info Available",
|
||||||
"search_page_no_places": "No Places Info Available",
|
"search_page_no_places": "Helyinformáció nem érhető el",
|
||||||
"search_page_people": "People",
|
"search_page_people": "Emberek",
|
||||||
"search_page_places": "Places",
|
"search_page_places": "Helyszínek",
|
||||||
"search_page_recently_added": "Nemrég hozzáadott",
|
"search_page_recently_added": "Nemrég hozzáadott",
|
||||||
"search_page_screenshots": "Képernyőképek",
|
"search_page_screenshots": "Képernyőképek",
|
||||||
"search_page_selfies": "Szelfik",
|
"search_page_selfies": "Szelfik",
|
||||||
"search_page_things": "Things",
|
"search_page_things": "Dolgok",
|
||||||
"search_page_videos": "Videók",
|
"search_page_videos": "Videók",
|
||||||
"search_page_view_all_button": "Összes mutatása",
|
"search_page_view_all_button": "Összes mutatása",
|
||||||
"search_page_your_activity": "Tevékenységeid",
|
"search_page_your_activity": "Tevékenységeid",
|
||||||
"search_result_page_new_search_hint": "New Search",
|
"search_result_page_new_search_hint": "Új keresés",
|
||||||
"search_suggestion_list_smart_search_hint_1": "Az intelligens keresés alapértelmezetten be van kapcsolva, metaadatokat így kereshetsz",
|
"search_suggestion_list_smart_search_hint_1": "Az intelligens keresés alapértelmezetten be van kapcsolva, metaadatokat így kereshetsz",
|
||||||
"search_suggestion_list_smart_search_hint_2": "m:keresési-kifejezés",
|
"search_suggestion_list_smart_search_hint_2": "m:keresési-kifejezés",
|
||||||
"select_additional_user_for_sharing_page_suggestions": "Suggestions",
|
"select_additional_user_for_sharing_page_suggestions": "Javaslatok",
|
||||||
"select_user_for_sharing_page_err_album": "Failed to create album",
|
"select_user_for_sharing_page_err_album": "Hiba az album létrehozása közben",
|
||||||
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
"select_user_for_sharing_page_share_suggestions": "Javaslatok",
|
||||||
"server_info_box_app_version": "Alkalmazás Verzió",
|
"server_info_box_app_version": "Alkalmazás Verzió",
|
||||||
"server_info_box_server_version": "Szerver Verzió",
|
"server_info_box_server_version": "Szerver Verzió",
|
||||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||||
@@ -253,52 +253,52 @@
|
|||||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||||
"setting_image_viewer_preview_title": "Load preview image",
|
"setting_image_viewer_preview_title": "Load preview image",
|
||||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||||
"setting_notifications_notify_hours": "{} hours",
|
"setting_notifications_notify_hours": "{} óra",
|
||||||
"setting_notifications_notify_immediately": "immediately",
|
"setting_notifications_notify_immediately": "azonnal",
|
||||||
"setting_notifications_notify_minutes": "{} minutes",
|
"setting_notifications_notify_minutes": "{} perc",
|
||||||
"setting_notifications_notify_never": "never",
|
"setting_notifications_notify_never": "soha",
|
||||||
"setting_notifications_notify_seconds": "{} seconds",
|
"setting_notifications_notify_seconds": "{} másodperc",
|
||||||
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
|
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
|
||||||
"setting_notifications_single_progress_title": "Show background backup detail progress",
|
"setting_notifications_single_progress_title": "Show background backup detail progress",
|
||||||
"setting_notifications_subtitle": "Adjust your notification preferences",
|
"setting_notifications_subtitle": "Adjust your notification preferences",
|
||||||
"setting_notifications_title": "Notifications",
|
"setting_notifications_title": "Értesítések",
|
||||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
||||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||||
"setting_pages_app_bar_settings": "Settings",
|
"setting_pages_app_bar_settings": "Beállítások",
|
||||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
"settings_require_restart": "Kérlek indítsd újra az Immich-et hogy alkalmazd ezt a beállítást",
|
||||||
"share_add": "Add",
|
"share_add": "Hozzáadás",
|
||||||
"share_add_photos": "Add photos",
|
"share_add_photos": "Fotók hozzáadása",
|
||||||
"share_add_title": "Add a title",
|
"share_add_title": "Cím hozzáadása",
|
||||||
"share_create_album": "Create album",
|
"share_create_album": "Album létrehozása",
|
||||||
"share_dialog_preparing": "Preparing...",
|
"share_dialog_preparing": "Előkészítés...",
|
||||||
"share_invite": "Invite to album",
|
"share_invite": "Meghívás az albumba",
|
||||||
"sharing_page_album": "Shared albums",
|
"sharing_page_album": "Megosztott albumok",
|
||||||
"sharing_page_description": "Create shared albums to share photos and videos with people in your network.",
|
"sharing_page_description": "Hozzon létre megosztott albumokat, hogy megoszthasson fényképeket és videókat a hálózatában lévő emberekkel.",
|
||||||
"sharing_page_empty_list": "EMPTY LIST",
|
"sharing_page_empty_list": "ÜRES LISTA",
|
||||||
"sharing_silver_appbar_create_shared_album": "Create shared album",
|
"sharing_silver_appbar_create_shared_album": "Megosztott album létrehozása",
|
||||||
"sharing_silver_appbar_share_partner": "Share with partner",
|
"sharing_silver_appbar_share_partner": "Megosztás másokkal",
|
||||||
"tab_controller_nav_library": "Library",
|
"tab_controller_nav_library": "Könyvtár",
|
||||||
"tab_controller_nav_photos": "Photos",
|
"tab_controller_nav_photos": "Képek",
|
||||||
"tab_controller_nav_search": "Search",
|
"tab_controller_nav_search": "Keresés",
|
||||||
"tab_controller_nav_sharing": "Sharing",
|
"tab_controller_nav_sharing": "Megosztás",
|
||||||
"theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles",
|
"theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles",
|
||||||
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
||||||
"theme_setting_dark_mode_switch": "Dark mode",
|
"theme_setting_dark_mode_switch": "Sötét mód",
|
||||||
"theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
|
"theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
|
||||||
"theme_setting_image_viewer_quality_title": "Image viewer quality",
|
"theme_setting_image_viewer_quality_title": "Image viewer quality",
|
||||||
"theme_setting_system_theme_switch": "Automatic (Follow system setting)",
|
"theme_setting_system_theme_switch": "Automatikus (követi a rendszer témáját)",
|
||||||
"theme_setting_theme_subtitle": "Choose the app's theme setting",
|
"theme_setting_theme_subtitle": "Choose the app's theme setting",
|
||||||
"theme_setting_theme_title": "Theme",
|
"theme_setting_theme_title": "Téma",
|
||||||
"theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load",
|
"theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load",
|
||||||
"theme_setting_three_stage_loading_title": "Enable three-stage loading",
|
"theme_setting_three_stage_loading_title": "Enable three-stage loading",
|
||||||
"upload_dialog_cancel": "Cancel",
|
"upload_dialog_cancel": "Mégse",
|
||||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
"upload_dialog_info": "Akarod menteni a kiválasztott eleme(ke)t a szerverre?",
|
||||||
"upload_dialog_ok": "Upload",
|
"upload_dialog_ok": "Feltöltés",
|
||||||
"upload_dialog_title": "Upload Asset",
|
"upload_dialog_title": "Elem feltöltése",
|
||||||
"version_announcement_overlay_ack": "Acknowledge",
|
"version_announcement_overlay_ack": "Megértettem",
|
||||||
"version_announcement_overlay_release_notes": "release notes",
|
"version_announcement_overlay_release_notes": "a változtatások listáját elolvasd",
|
||||||
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",
|
"version_announcement_overlay_text_1": "Szia, egy új verzió érhető el",
|
||||||
"version_announcement_overlay_text_2": "please take your time to visit the ",
|
"version_announcement_overlay_text_2": "kérlek szánj időt arra, hogy ",
|
||||||
"version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
|
"version_announcement_overlay_text_3": "és gyöződj meg róla, hogy a docker-compose és .env beállításai naprakészek és pontosak, különösen akkor, ha használsz watchtower-t vagy bármi olyan megoldást ami automatikusan frissíti a szervert.",
|
||||||
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89"
|
"version_announcement_overlay_title": "Új szerververzió érhető el \uD83C\uDF89"
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"add_to_album_bottom_sheet_added": "Aggiunto in {album}",
|
"add_to_album_bottom_sheet_added": "Aggiunto in {album}",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Già presente in {album}",
|
"add_to_album_bottom_sheet_already_exists": "Già presente in {album}",
|
||||||
"advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.",
|
"advanced_settings_prefer_remote_subtitle": "Alcuni dispositivi sono molto lenti a caricare le anteprime delle immagini dal dispositivo. Attivare questa impostazione per caricare invece le immagini remote.",
|
||||||
"advanced_settings_prefer_remote_title": "Prefer remote images",
|
"advanced_settings_prefer_remote_title": "Preferisci immagini remote.",
|
||||||
"advanced_settings_tile_subtitle": "Impostazioni aggiuntive utenti",
|
"advanced_settings_tile_subtitle": "Impostazioni aggiuntive utenti",
|
||||||
"advanced_settings_tile_title": "Avanzato",
|
"advanced_settings_tile_title": "Avanzato",
|
||||||
"advanced_settings_troubleshooting_subtitle": "Attiva funzioni addizionali per la risoluzione dei problemi",
|
"advanced_settings_troubleshooting_subtitle": "Attiva funzioni addizionali per la risoluzione dei problemi",
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"album_viewer_appbar_share_leave": "Lascia album",
|
"album_viewer_appbar_share_leave": "Lascia album",
|
||||||
"album_viewer_appbar_share_remove": "Rimuovere dall'album ",
|
"album_viewer_appbar_share_remove": "Rimuovere dall'album ",
|
||||||
"album_viewer_page_share_add_users": "Aggiungi utenti",
|
"album_viewer_page_share_add_users": "Aggiungi utenti",
|
||||||
"all_people_page_title": "People",
|
"all_people_page_title": "Persone",
|
||||||
"all_videos_page_title": "Video",
|
"all_videos_page_title": "Video",
|
||||||
"archive_page_no_archived_assets": "Nessuna oggetto archiviato",
|
"archive_page_no_archived_assets": "Nessuna oggetto archiviato",
|
||||||
"archive_page_title": "Archivia ({})",
|
"archive_page_title": "Archivia ({})",
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"asset_list_layout_settings_group_by_month_day": "Mese + giorno",
|
"asset_list_layout_settings_group_by_month_day": "Mese + giorno",
|
||||||
"asset_list_settings_subtitle": "Impostazion del layout della griglia delle foto",
|
"asset_list_settings_subtitle": "Impostazion del layout della griglia delle foto",
|
||||||
"asset_list_settings_title": "Griglia foto",
|
"asset_list_settings_title": "Griglia foto",
|
||||||
"backup_album_selection_page_albums_device": "Albums sul device ({})",
|
"backup_album_selection_page_albums_device": "Album sul dispositivo ({})",
|
||||||
"backup_album_selection_page_albums_tap": "Tap per includere, doppio tap per escludere.",
|
"backup_album_selection_page_albums_tap": "Tap per includere, doppio tap per escludere.",
|
||||||
"backup_album_selection_page_assets_scatter": "Stesse immagini e video possono trovarsi tra più album, così gli album possono essere inclusi o esclusi dal backup.",
|
"backup_album_selection_page_assets_scatter": "Stesse immagini e video possono trovarsi tra più album, così gli album possono essere inclusi o esclusi dal backup.",
|
||||||
"backup_album_selection_page_select_albums": "Seleziona gli album",
|
"backup_album_selection_page_select_albums": "Seleziona gli album",
|
||||||
@@ -44,8 +44,8 @@
|
|||||||
"backup_background_service_connection_failed_message": "Impossibile connettersi al server. Riprovo…",
|
"backup_background_service_connection_failed_message": "Impossibile connettersi al server. Riprovo…",
|
||||||
"backup_background_service_current_upload_notification": "Caricamento {}",
|
"backup_background_service_current_upload_notification": "Caricamento {}",
|
||||||
"backup_background_service_default_notification": "Ricerca di nuovi contenuti…",
|
"backup_background_service_default_notification": "Ricerca di nuovi contenuti…",
|
||||||
"backup_background_service_error_title": "Errore di Backup",
|
"backup_background_service_error_title": "Errore di backup",
|
||||||
"backup_background_service_in_progress_notification": "Backing dei tuoi contenuti…",
|
"backup_background_service_in_progress_notification": "Backup dei tuoi contenuti…",
|
||||||
"backup_background_service_upload_failure_notification": "Impossibile caricare {}",
|
"backup_background_service_upload_failure_notification": "Impossibile caricare {}",
|
||||||
"backup_controller_page_albums": "Backup Album",
|
"backup_controller_page_albums": "Backup Album",
|
||||||
"backup_controller_page_background_app_refresh_disabled_content": "Attiva background app refresh dalle Impostazioni > Generale > Background App Refresh per utilizzare backup in background.",
|
"backup_controller_page_background_app_refresh_disabled_content": "Attiva background app refresh dalle Impostazioni > Generale > Background App Refresh per utilizzare backup in background.",
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
"backup_controller_page_backup_sub": "Foto e video caricati",
|
"backup_controller_page_backup_sub": "Foto e video caricati",
|
||||||
"backup_controller_page_cancel": "Cancella ",
|
"backup_controller_page_cancel": "Cancella ",
|
||||||
"backup_controller_page_created": "Creato il: {}",
|
"backup_controller_page_created": "Creato il: {}",
|
||||||
"backup_controller_page_desc_backup": "Attiva il backup per eseguire il caricamento automatico sul server",
|
"backup_controller_page_desc_backup": "Attiva il backup per eseguire il caricamento automatico sul server all'apertura dell'applicazione.",
|
||||||
"backup_controller_page_excluded": "Esclusi:",
|
"backup_controller_page_excluded": "Esclusi:",
|
||||||
"backup_controller_page_failed": "Falliti: ({})",
|
"backup_controller_page_failed": "Falliti: ({})",
|
||||||
"backup_controller_page_filename": "Nome del file: {} [{}]",
|
"backup_controller_page_filename": "Nome del file: {} [{}]",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"backup_controller_page_info": "Informazioni sul backup",
|
"backup_controller_page_info": "Informazioni sul backup",
|
||||||
"backup_controller_page_none_selected": "Nessuna selezione",
|
"backup_controller_page_none_selected": "Nessuna selezione",
|
||||||
"backup_controller_page_remainder": "Promemoria ",
|
"backup_controller_page_remainder": "Promemoria ",
|
||||||
"backup_controller_page_remainder_sub": "Photo e album selezionati che rimangono da caricare",
|
"backup_controller_page_remainder_sub": "Foto e album selezionati che rimangono da caricare",
|
||||||
"backup_controller_page_select": "Seleziona ",
|
"backup_controller_page_select": "Seleziona ",
|
||||||
"backup_controller_page_server_storage": "Spazio sul server",
|
"backup_controller_page_server_storage": "Spazio sul server",
|
||||||
"backup_controller_page_start_backup": "Inizia backup ",
|
"backup_controller_page_start_backup": "Inizia backup ",
|
||||||
@@ -92,11 +92,11 @@
|
|||||||
"backup_controller_page_uploading_file_info": "Caricando informazioni sul file",
|
"backup_controller_page_uploading_file_info": "Caricando informazioni sul file",
|
||||||
"backup_err_only_album": "Non è possibile rimuovere l'unico album",
|
"backup_err_only_album": "Non è possibile rimuovere l'unico album",
|
||||||
"backup_info_card_assets": "oggetti ",
|
"backup_info_card_assets": "oggetti ",
|
||||||
"backup_manual_cancelled": "Cancelled",
|
"backup_manual_cancelled": "Annullato",
|
||||||
"backup_manual_failed": "Failed",
|
"backup_manual_failed": "Fallito",
|
||||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
"backup_manual_in_progress": "Caricamento già in corso. Riprova più tardi.",
|
||||||
"backup_manual_success": "Success",
|
"backup_manual_success": "Successo",
|
||||||
"backup_manual_title": "Upload status",
|
"backup_manual_title": "Stato del caricamento",
|
||||||
"cache_settings_album_thumbnails": "Anteprime pagine librerie ({} assets)",
|
"cache_settings_album_thumbnails": "Anteprime pagine librerie ({} assets)",
|
||||||
"cache_settings_clear_cache_button": "Cancella cache",
|
"cache_settings_clear_cache_button": "Cancella cache",
|
||||||
"cache_settings_clear_cache_button_title": "Cancella la cache dell'app. Questo impatterà significativamente le prestazioni dell''app fino a quando la cache non sarà rigenerata.",
|
"cache_settings_clear_cache_button_title": "Cancella la cache dell'app. Questo impatterà significativamente le prestazioni dell''app fino a quando la cache non sarà rigenerata.",
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
"home_page_building_timeline": "Costruendo il Timeline",
|
"home_page_building_timeline": "Costruendo il Timeline",
|
||||||
"home_page_favorite_err_local": "Non puoi aggiungere tra i preferiti le foto ancora non caricate",
|
"home_page_favorite_err_local": "Non puoi aggiungere tra i preferiti le foto ancora non caricate",
|
||||||
"home_page_first_time_notice": "Se è la prima volta che usi l'app, assicurati di scegliere gli album per avere il Timeline con immagini e video",
|
"home_page_first_time_notice": "Se è la prima volta che usi l'app, assicurati di scegliere gli album per avere il Timeline con immagini e video",
|
||||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
"home_page_upload_err_limit": "Puoi caricare al massimo 30 file per volta, ignora quelli in eccesso",
|
||||||
"image_viewer_page_state_provider_download_error": "Errore nel Download",
|
"image_viewer_page_state_provider_download_error": "Errore nel Download",
|
||||||
"image_viewer_page_state_provider_download_success": "Download con successo",
|
"image_viewer_page_state_provider_download_success": "Download con successo",
|
||||||
"library_page_albums": "Album",
|
"library_page_albums": "Album",
|
||||||
@@ -172,7 +172,7 @@
|
|||||||
"library_page_sharing": "Condividendo",
|
"library_page_sharing": "Condividendo",
|
||||||
"library_page_sort_created": "Creato il più recente",
|
"library_page_sort_created": "Creato il più recente",
|
||||||
"library_page_sort_title": "Titolo album",
|
"library_page_sort_title": "Titolo album",
|
||||||
"login_disabled": "Login has been disabled",
|
"login_disabled": "L'accesso è stato disattivato",
|
||||||
"login_form_api_exception": "API error, per favore ricontrolli URL del server e riprovi",
|
"login_form_api_exception": "API error, per favore ricontrolli URL del server e riprovi",
|
||||||
"login_form_button_text": "Login",
|
"login_form_button_text": "Login",
|
||||||
"login_form_email_hint": "tuaemail@email.com",
|
"login_form_email_hint": "tuaemail@email.com",
|
||||||
@@ -201,14 +201,14 @@
|
|||||||
"notification_permission_list_tile_content": "Concedi i permessi per attivare le notifiche",
|
"notification_permission_list_tile_content": "Concedi i permessi per attivare le notifiche",
|
||||||
"notification_permission_list_tile_enable_button": "Attiva notifiche",
|
"notification_permission_list_tile_enable_button": "Attiva notifiche",
|
||||||
"notification_permission_list_tile_title": "Permessi delle Notifiche",
|
"notification_permission_list_tile_title": "Permessi delle Notifiche",
|
||||||
"partner_page_add_partner": "Add partner",
|
"partner_page_add_partner": "Aggiungi partner.",
|
||||||
"partner_page_empty_message": "Your photos are not yet shared with any partner.",
|
"partner_page_empty_message": "Le tue foto non sono ancora condivise con alcun partner.",
|
||||||
"partner_page_no_more_users": "No more users to add",
|
"partner_page_no_more_users": "Nessun altro utente da aggiungere.",
|
||||||
"partner_page_partner_add_failed": "Failed to add partner",
|
"partner_page_partner_add_failed": "Aggiunta del partner non riuscita.",
|
||||||
"partner_page_select_partner": "Select partner",
|
"partner_page_select_partner": "Seleziona partner.",
|
||||||
"partner_page_shared_to_title": "Shared to",
|
"partner_page_shared_to_title": "Condividi con",
|
||||||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
"partner_page_stop_sharing_content": "{} non sarà più in grado di accedere alle tue foto.",
|
||||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
"partner_page_stop_sharing_title": "Stoppare la condivisione delle tue foto?",
|
||||||
"partner_page_title": "Partner",
|
"partner_page_title": "Partner",
|
||||||
"permission_onboarding_continue_anyway": "Continua lo stesso",
|
"permission_onboarding_continue_anyway": "Continua lo stesso",
|
||||||
"permission_onboarding_get_started": "Inizia",
|
"permission_onboarding_get_started": "Inizia",
|
||||||
@@ -230,7 +230,7 @@
|
|||||||
"search_page_motion_photos": "Motion Foto",
|
"search_page_motion_photos": "Motion Foto",
|
||||||
"search_page_no_objects": "Nessuna informazione relativa all'oggetto disponibile",
|
"search_page_no_objects": "Nessuna informazione relativa all'oggetto disponibile",
|
||||||
"search_page_no_places": "Nessun informazione sul luogo disponibile",
|
"search_page_no_places": "Nessun informazione sul luogo disponibile",
|
||||||
"search_page_people": "People",
|
"search_page_people": "Persone",
|
||||||
"search_page_places": "Luoghi",
|
"search_page_places": "Luoghi",
|
||||||
"search_page_recently_added": "Aggiunte di recente",
|
"search_page_recently_added": "Aggiunte di recente",
|
||||||
"search_page_screenshots": "Screenshot",
|
"search_page_screenshots": "Screenshot",
|
||||||
@@ -291,10 +291,10 @@
|
|||||||
"theme_setting_theme_title": "Tema",
|
"theme_setting_theme_title": "Tema",
|
||||||
"theme_setting_three_stage_loading_subtitle": "Il caricamento a tre stage aumenterà le performance di caricamento ma anche il consumo di banda",
|
"theme_setting_three_stage_loading_subtitle": "Il caricamento a tre stage aumenterà le performance di caricamento ma anche il consumo di banda",
|
||||||
"theme_setting_three_stage_loading_title": "Abilita il caricamento a tre stage",
|
"theme_setting_three_stage_loading_title": "Abilita il caricamento a tre stage",
|
||||||
"upload_dialog_cancel": "Cancel",
|
"upload_dialog_cancel": "Cancella",
|
||||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
"upload_dialog_info": "Vuoi fare il backup sul server di ciò che hai selezionato?",
|
||||||
"upload_dialog_ok": "Upload",
|
"upload_dialog_ok": "Carica",
|
||||||
"upload_dialog_title": "Upload Asset",
|
"upload_dialog_title": "Carica file",
|
||||||
"version_announcement_overlay_ack": "Presa visione",
|
"version_announcement_overlay_ack": "Presa visione",
|
||||||
"version_announcement_overlay_release_notes": "note di rilascio ",
|
"version_announcement_overlay_release_notes": "note di rilascio ",
|
||||||
"version_announcement_overlay_text_1": "Ciao, c'è una nuova versione di",
|
"version_announcement_overlay_text_1": "Ciao, c'è una nuova versione di",
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
"backup_controller_page_uploading_file_info": "Laster opp filinformasjon",
|
"backup_controller_page_uploading_file_info": "Laster opp filinformasjon",
|
||||||
"backup_err_only_album": "Kan ikke fjerne det eneste albumet",
|
"backup_err_only_album": "Kan ikke fjerne det eneste albumet",
|
||||||
"backup_info_card_assets": "objekter",
|
"backup_info_card_assets": "objekter",
|
||||||
"backup_manual_cancelled": "Cancelled",
|
"backup_manual_cancelled": "Avbrutt",
|
||||||
"backup_manual_failed": "Feilet",
|
"backup_manual_failed": "Feilet",
|
||||||
"backup_manual_in_progress": "Opplasting er allerede i gang. Prøv igjen om litt",
|
"backup_manual_in_progress": "Opplasting er allerede i gang. Prøv igjen om litt",
|
||||||
"backup_manual_success": "Vellykket",
|
"backup_manual_success": "Vellykket",
|
||||||
|
|||||||
@@ -92,11 +92,11 @@
|
|||||||
"backup_controller_page_uploading_file_info": "Bestandsgegevens uploaden",
|
"backup_controller_page_uploading_file_info": "Bestandsgegevens uploaden",
|
||||||
"backup_err_only_album": "Kan het enige album niet verwijderen",
|
"backup_err_only_album": "Kan het enige album niet verwijderen",
|
||||||
"backup_info_card_assets": "items",
|
"backup_info_card_assets": "items",
|
||||||
"backup_manual_cancelled": "Cancelled",
|
"backup_manual_cancelled": "Geannuleerd",
|
||||||
"backup_manual_failed": "Failed",
|
"backup_manual_failed": "Gefaald",
|
||||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
"backup_manual_in_progress": "Het uploaden is al bezig. Probeer het na een tijdje",
|
||||||
"backup_manual_success": "Success",
|
"backup_manual_success": "Succes",
|
||||||
"backup_manual_title": "Upload status",
|
"backup_manual_title": "Uploadstatus",
|
||||||
"cache_settings_album_thumbnails": "Thumbnails bibliotheekpagina ({} items)",
|
"cache_settings_album_thumbnails": "Thumbnails bibliotheekpagina ({} items)",
|
||||||
"cache_settings_clear_cache_button": "Cache wissen",
|
"cache_settings_clear_cache_button": "Cache wissen",
|
||||||
"cache_settings_clear_cache_button_title": "Wist de cache van de app. Dit zal de presentaties van de app aanzienlijk beïnvloeden totdat de cache opnieuw is opgebouwd.",
|
"cache_settings_clear_cache_button_title": "Wist de cache van de app. Dit zal de presentaties van de app aanzienlijk beïnvloeden totdat de cache opnieuw is opgebouwd.",
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
"home_page_building_timeline": "Tijdlijn opbouwen",
|
"home_page_building_timeline": "Tijdlijn opbouwen",
|
||||||
"home_page_favorite_err_local": "Lokale items kunnen nog niet als favoriet worden aangemerkt, overslaan",
|
"home_page_favorite_err_local": "Lokale items kunnen nog niet als favoriet worden aangemerkt, overslaan",
|
||||||
"home_page_first_time_notice": "Als dit de eerste keer is dat je de app gebruikt, zorg er dan voor dat je een back-up album kiest, zodat de tijdlijn gevuld kan worden met foto's en video's uit het album.",
|
"home_page_first_time_notice": "Als dit de eerste keer is dat je de app gebruikt, zorg er dan voor dat je een back-up album kiest, zodat de tijdlijn gevuld kan worden met foto's en video's uit het album.",
|
||||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
"home_page_upload_err_limit": "Kan maximaal 30 assets tegelijk uploaden, overslaan",
|
||||||
"image_viewer_page_state_provider_download_error": "Download mislukt",
|
"image_viewer_page_state_provider_download_error": "Download mislukt",
|
||||||
"image_viewer_page_state_provider_download_success": "Download succesvol",
|
"image_viewer_page_state_provider_download_success": "Download succesvol",
|
||||||
"library_page_albums": "Albums",
|
"library_page_albums": "Albums",
|
||||||
@@ -172,7 +172,7 @@
|
|||||||
"library_page_sharing": "Gedeeld",
|
"library_page_sharing": "Gedeeld",
|
||||||
"library_page_sort_created": "Meest recent gemaakt",
|
"library_page_sort_created": "Meest recent gemaakt",
|
||||||
"library_page_sort_title": "Albumtitel",
|
"library_page_sort_title": "Albumtitel",
|
||||||
"login_disabled": "Login has been disabled",
|
"login_disabled": "Aanmelding uitgeschakeld",
|
||||||
"login_form_api_exception": "API fout. Controleer de server URL en probeer opnieuw.",
|
"login_form_api_exception": "API fout. Controleer de server URL en probeer opnieuw.",
|
||||||
"login_form_button_text": "Inloggen",
|
"login_form_button_text": "Inloggen",
|
||||||
"login_form_email_hint": "jouwemail@email.com",
|
"login_form_email_hint": "jouwemail@email.com",
|
||||||
@@ -291,10 +291,10 @@
|
|||||||
"theme_setting_theme_title": "Thema",
|
"theme_setting_theme_title": "Thema",
|
||||||
"theme_setting_three_stage_loading_subtitle": "Laden in drie fasen kan de laadprestaties verbeteren, maar veroorzaakt een aanzienlijk hogere netwerkbelasting",
|
"theme_setting_three_stage_loading_subtitle": "Laden in drie fasen kan de laadprestaties verbeteren, maar veroorzaakt een aanzienlijk hogere netwerkbelasting",
|
||||||
"theme_setting_three_stage_loading_title": "Laden in drie fasen inschakelen",
|
"theme_setting_three_stage_loading_title": "Laden in drie fasen inschakelen",
|
||||||
"upload_dialog_cancel": "Cancel",
|
"upload_dialog_cancel": "Annuleren",
|
||||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
"upload_dialog_info": "Wilt u een backup maken van de geselecteerde Asset(s) op de server?",
|
||||||
"upload_dialog_ok": "Upload",
|
"upload_dialog_ok": "Upload",
|
||||||
"upload_dialog_title": "Upload Asset",
|
"upload_dialog_title": "Asset uploaden",
|
||||||
"version_announcement_overlay_ack": "Bevestig",
|
"version_announcement_overlay_ack": "Bevestig",
|
||||||
"version_announcement_overlay_release_notes": "releaseopmerkingen",
|
"version_announcement_overlay_release_notes": "releaseopmerkingen",
|
||||||
"version_announcement_overlay_text_1": "Hoi, er is een nieuwe versie beschikbaar van",
|
"version_announcement_overlay_text_1": "Hoi, er is een nieuwe versie beschikbaar van",
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
"add_to_album_bottom_sheet_added": "Dodano do {album}",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
"add_to_album_bottom_sheet_already_exists": "Już w {album}",
|
||||||
"advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.",
|
"advanced_settings_prefer_remote_subtitle": "Niektóre urządzenia bardzo wolno ładują miniatury z zasobów na urządzeniu. Aktywuj to ustawienie, aby ładować zdalne obrazy.",
|
||||||
"advanced_settings_prefer_remote_title": "Prefer remote images",
|
"advanced_settings_prefer_remote_title": "Preferuj obrazy zdalne",
|
||||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
"advanced_settings_tile_subtitle": "Zaawansowane ustawienia użytkownika",
|
||||||
"advanced_settings_tile_title": "Advanced",
|
"advanced_settings_tile_title": "Zaawansowane",
|
||||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
"advanced_settings_troubleshooting_subtitle": "Włącz dodatkowe funkcje rozwiązywania problemów",
|
||||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
"advanced_settings_troubleshooting_title": "Rozwiązywanie problemów",
|
||||||
"album_info_card_backup_album_excluded": "WYKLUCZONE",
|
"album_info_card_backup_album_excluded": "WYKLUCZONE",
|
||||||
"album_info_card_backup_album_included": "WŁĄCZONE",
|
"album_info_card_backup_album_included": "WŁĄCZONE",
|
||||||
"album_thumbnail_card_item": "1 pozycja",
|
"album_thumbnail_card_item": "1 pozycja",
|
||||||
"album_thumbnail_card_items": "{} pozycje",
|
"album_thumbnail_card_items": "{} pozycje",
|
||||||
"album_thumbnail_card_shared": "Udostępniony",
|
"album_thumbnail_card_shared": "Udostępniony",
|
||||||
"album_thumbnail_owned": "Owned",
|
"album_thumbnail_owned": "Posiadany",
|
||||||
"album_thumbnail_shared_by": "Shared by {}",
|
"album_thumbnail_shared_by": "Udostępnione przez {}",
|
||||||
"album_viewer_appbar_share_delete": "Usuń album",
|
"album_viewer_appbar_share_delete": "Usuń album",
|
||||||
"album_viewer_appbar_share_err_delete": "Nie udało się usunąć albumu",
|
"album_viewer_appbar_share_err_delete": "Nie udało się usunąć albumu",
|
||||||
"album_viewer_appbar_share_err_leave": "Nie udało się wyjść z albumu",
|
"album_viewer_appbar_share_err_leave": "Nie udało się wyjść z albumu",
|
||||||
@@ -22,15 +22,15 @@
|
|||||||
"album_viewer_appbar_share_leave": "Opuść album",
|
"album_viewer_appbar_share_leave": "Opuść album",
|
||||||
"album_viewer_appbar_share_remove": "Usuń z albumu",
|
"album_viewer_appbar_share_remove": "Usuń z albumu",
|
||||||
"album_viewer_page_share_add_users": "Dodaj użytkowników",
|
"album_viewer_page_share_add_users": "Dodaj użytkowników",
|
||||||
"all_people_page_title": "People",
|
"all_people_page_title": "Ludzie",
|
||||||
"all_videos_page_title": "Videos",
|
"all_videos_page_title": "Filmy",
|
||||||
"archive_page_no_archived_assets": "No archived assets found",
|
"archive_page_no_archived_assets": "Nie znaleziono zarchiwizowanych zasobów",
|
||||||
"archive_page_title": "Archive ({})",
|
"archive_page_title": "Archiwum ({})",
|
||||||
"asset_list_layout_settings_dynamic_layout_title": "Dynamic layout",
|
"asset_list_layout_settings_dynamic_layout_title": "Układ dynamiczny",
|
||||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
"asset_list_layout_settings_group_automatically": "Automatyczny",
|
||||||
"asset_list_layout_settings_group_by": "Group assets by",
|
"asset_list_layout_settings_group_by": "Grupuj zasoby według",
|
||||||
"asset_list_layout_settings_group_by_month": "Month",
|
"asset_list_layout_settings_group_by_month": "Miesiąc",
|
||||||
"asset_list_layout_settings_group_by_month_day": "Month + day",
|
"asset_list_layout_settings_group_by_month_day": "Miesiąc + dzień",
|
||||||
"asset_list_settings_subtitle": "Ustawienia układu siatki zdjęć",
|
"asset_list_settings_subtitle": "Ustawienia układu siatki zdjęć",
|
||||||
"asset_list_settings_title": "Siatka Zdjęć",
|
"asset_list_settings_title": "Siatka Zdjęć",
|
||||||
"backup_album_selection_page_albums_device": "Albumy na urządzeniu ({})",
|
"backup_album_selection_page_albums_device": "Albumy na urządzeniu ({})",
|
||||||
@@ -48,16 +48,16 @@
|
|||||||
"backup_background_service_in_progress_notification": "Tworzę kopię twoich zasobów...",
|
"backup_background_service_in_progress_notification": "Tworzę kopię twoich zasobów...",
|
||||||
"backup_background_service_upload_failure_notification": "Nie udało się przesłać {}",
|
"backup_background_service_upload_failure_notification": "Nie udało się przesłać {}",
|
||||||
"backup_controller_page_albums": "Backup Albumów",
|
"backup_controller_page_albums": "Backup Albumów",
|
||||||
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
|
"backup_controller_page_background_app_refresh_disabled_content": "Włącz odświeżanie aplikacji w tle w Ustawienia > Ogólne > Odświeżanie aplikacji w tle, aby móc korzystać z kopii zapasowej w tle.",
|
||||||
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
|
"backup_controller_page_background_app_refresh_disabled_title": "Odświeżanie aplikacji w tle wyłączone",
|
||||||
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
|
"backup_controller_page_background_app_refresh_enable_button_text": "Przejdź do ustawień",
|
||||||
"backup_controller_page_background_battery_info_link": "Pokaż mi jak",
|
"backup_controller_page_background_battery_info_link": "Pokaż mi jak",
|
||||||
"backup_controller_page_background_battery_info_message": "Aby uzyskać najlepsze rezultaty podczas tworzenia kopii zapasowej w tle, należy wyłączyć wszelkie optymalizacje baterii ograniczające aktywność w tle dla Immich w urządzeniu.\n\nPonieważ jest to zależne od urządzenia, proszę sprawdzić wymagane informacje dla producenta urządzenia.",
|
"backup_controller_page_background_battery_info_message": "Aby uzyskać najlepsze rezultaty podczas tworzenia kopii zapasowej w tle, należy wyłączyć wszelkie optymalizacje baterii ograniczające aktywność w tle dla Immich w urządzeniu.\n\nPonieważ jest to zależne od urządzenia, proszę sprawdzić wymagane informacje dla producenta urządzenia.",
|
||||||
"backup_controller_page_background_battery_info_ok": "OK",
|
"backup_controller_page_background_battery_info_ok": "OK",
|
||||||
"backup_controller_page_background_battery_info_title": "Optymalizacja Baterii",
|
"backup_controller_page_background_battery_info_title": "Optymalizacja Baterii",
|
||||||
"backup_controller_page_background_charging": "Tylko podczas ładowania",
|
"backup_controller_page_background_charging": "Tylko podczas ładowania",
|
||||||
"backup_controller_page_background_configure_error": "Nie udało się skonfigurować usługi w tle",
|
"backup_controller_page_background_configure_error": "Nie udało się skonfigurować usługi w tle",
|
||||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
"backup_controller_page_background_delay": "Opóźnij tworzenie kopii zapasowych nowych zasobów: {}",
|
||||||
"backup_controller_page_background_description": "Włącz usługę w tle, aby automatycznie tworzyć kopie zapasowe wszelkich nowych zasobów bez konieczności otwierania aplikacji",
|
"backup_controller_page_background_description": "Włącz usługę w tle, aby automatycznie tworzyć kopie zapasowe wszelkich nowych zasobów bez konieczności otwierania aplikacji",
|
||||||
"backup_controller_page_background_is_off": "Automatyczna kopia zapasowa w tle jest wyłączona",
|
"backup_controller_page_background_is_off": "Automatyczna kopia zapasowa w tle jest wyłączona",
|
||||||
"backup_controller_page_background_is_on": "Automatyczna kopia zapasowa w tle jest włączona",
|
"backup_controller_page_background_is_on": "Automatyczna kopia zapasowa w tle jest włączona",
|
||||||
@@ -92,11 +92,11 @@
|
|||||||
"backup_controller_page_uploading_file_info": "Przesyłanie informacji o pliku",
|
"backup_controller_page_uploading_file_info": "Przesyłanie informacji o pliku",
|
||||||
"backup_err_only_album": "Nie można usunąć tylko i wyłącznie albumu",
|
"backup_err_only_album": "Nie można usunąć tylko i wyłącznie albumu",
|
||||||
"backup_info_card_assets": "zasoby",
|
"backup_info_card_assets": "zasoby",
|
||||||
"backup_manual_cancelled": "Cancelled",
|
"backup_manual_cancelled": "Anulowano",
|
||||||
"backup_manual_failed": "Failed",
|
"backup_manual_failed": "Niepowodzenie",
|
||||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
"backup_manual_in_progress": "Przesyłanie już trwa. Spróbuj po pewnym czasie",
|
||||||
"backup_manual_success": "Success",
|
"backup_manual_success": "Sukces",
|
||||||
"backup_manual_title": "Upload status",
|
"backup_manual_title": "Stan przesyłania",
|
||||||
"cache_settings_album_thumbnails": "Miniatury stron bibliotek ({} zasobów)",
|
"cache_settings_album_thumbnails": "Miniatury stron bibliotek ({} zasobów)",
|
||||||
"cache_settings_clear_cache_button": "Wyczyść Cache",
|
"cache_settings_clear_cache_button": "Wyczyść Cache",
|
||||||
"cache_settings_clear_cache_button_title": "Czyści pamięć podręczną aplikacji. Wpłynie to znacząco na wydajność aplikacji, dopóki pamięć podręczna nie zostanie odbudowana.",
|
"cache_settings_clear_cache_button_title": "Czyści pamięć podręczną aplikacji. Wpłynie to znacząco na wydajność aplikacji, dopóki pamięć podręczna nie zostanie odbudowana.",
|
||||||
@@ -110,32 +110,32 @@
|
|||||||
"cache_settings_subtitle": "Kontrolowanie zachowania buforowania aplikacji mobilnej Immich",
|
"cache_settings_subtitle": "Kontrolowanie zachowania buforowania aplikacji mobilnej Immich",
|
||||||
"cache_settings_thumbnail_size": "Rozmiar pamięci podręcznej miniatur ({} zasobów)",
|
"cache_settings_thumbnail_size": "Rozmiar pamięci podręcznej miniatur ({} zasobów)",
|
||||||
"cache_settings_title": "Ustawienia Buforowania",
|
"cache_settings_title": "Ustawienia Buforowania",
|
||||||
"change_password_form_confirm_password": "Confirm Password",
|
"change_password_form_confirm_password": "Potwierdź Hasło",
|
||||||
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
|
"change_password_form_description": "Cześć {firstName} {lastName},\n\nPierwszy raz logujesz się do systemu, albo złożono prośbę o zmianę hasła. Wpisz poniżej nowe hasło.",
|
||||||
"change_password_form_new_password": "New Password",
|
"change_password_form_new_password": "Nowe Hasło",
|
||||||
"change_password_form_password_mismatch": "Passwords do not match",
|
"change_password_form_password_mismatch": "Hasła nie są zgodne",
|
||||||
"change_password_form_reenter_new_password": "Re-enter New Password",
|
"change_password_form_reenter_new_password": "Wprowadź ponownie Nowe Hasło",
|
||||||
"common_add_to_album": "Add to album",
|
"common_add_to_album": "Dodaj do albumu",
|
||||||
"common_change_password": "Change Password",
|
"common_change_password": "Zmień Hasło",
|
||||||
"common_create_new_album": "Create new album",
|
"common_create_new_album": "Utwórz nowy album",
|
||||||
"common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.",
|
"common_server_error": "Sprawdź połączenie sieciowe, upewnij się, że serwer jest osiągalny i wersje aplikacji/serwera są kompatybilne.",
|
||||||
"common_shared": "Shared",
|
"common_shared": "Udostępnione",
|
||||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
"control_bottom_app_bar_add_to_album": "Dodaj do albumu",
|
||||||
"control_bottom_app_bar_album_info": "{} items",
|
"control_bottom_app_bar_album_info": "{} pozycji",
|
||||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
"control_bottom_app_bar_album_info_shared": "{} pozycji · Udostępnionych",
|
||||||
"control_bottom_app_bar_archive": "Archive",
|
"control_bottom_app_bar_archive": "Archiwum",
|
||||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
"control_bottom_app_bar_create_new_album": "Utwórz nowy album",
|
||||||
"control_bottom_app_bar_delete": "Usuń",
|
"control_bottom_app_bar_delete": "Usuń",
|
||||||
"control_bottom_app_bar_favorite": "Favorite",
|
"control_bottom_app_bar_favorite": "Ulubione",
|
||||||
"control_bottom_app_bar_share": "Udostępnij",
|
"control_bottom_app_bar_share": "Udostępnij",
|
||||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
"control_bottom_app_bar_unarchive": "Cofnij archiwizację",
|
||||||
"create_album_page_untitled": "Bez tytułu",
|
"create_album_page_untitled": "Bez tytułu",
|
||||||
"create_shared_album_page_create": "Utwórz",
|
"create_shared_album_page_create": "Utwórz",
|
||||||
"create_shared_album_page_share": "Udostępnij",
|
"create_shared_album_page_share": "Udostępnij",
|
||||||
"create_shared_album_page_share_add_assets": "DODAJ ZASOBY",
|
"create_shared_album_page_share_add_assets": "DODAJ ZASOBY",
|
||||||
"create_shared_album_page_share_select_photos": "Zaznacz Zdjęcia",
|
"create_shared_album_page_share_select_photos": "Zaznacz Zdjęcia",
|
||||||
"curated_location_page_title": "Places",
|
"curated_location_page_title": "Miejsca",
|
||||||
"curated_object_page_title": "Things",
|
"curated_object_page_title": "Rzeczy",
|
||||||
"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",
|
||||||
"date_format": "E, LLL d, y • h:mm a",
|
"date_format": "E, LLL d, y • h:mm a",
|
||||||
@@ -143,129 +143,129 @@
|
|||||||
"delete_dialog_cancel": "Anuluj",
|
"delete_dialog_cancel": "Anuluj",
|
||||||
"delete_dialog_ok": "Usuń",
|
"delete_dialog_ok": "Usuń",
|
||||||
"delete_dialog_title": "Usuń trwale",
|
"delete_dialog_title": "Usuń trwale",
|
||||||
"description_input_hint_text": "Add description...",
|
"description_input_hint_text": "Dodaj opis...",
|
||||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
"description_input_submit_error": "Błąd aktualizacji opisu, sprawdź dziennik, aby uzyskać więcej szczegółów",
|
||||||
"exif_bottom_sheet_description": "Dodaj Opis...",
|
"exif_bottom_sheet_description": "Dodaj Opis...",
|
||||||
"exif_bottom_sheet_details": "SZCZEGÓŁY",
|
"exif_bottom_sheet_details": "SZCZEGÓŁY",
|
||||||
"exif_bottom_sheet_location": "LOKALIZACJA",
|
"exif_bottom_sheet_location": "LOKALIZACJA",
|
||||||
"experimental_settings_new_asset_list_subtitle": "Work in progress",
|
"experimental_settings_new_asset_list_subtitle": "Praca w toku",
|
||||||
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
"experimental_settings_new_asset_list_title": "Włącz eksperymentalną układ zdjęć",
|
||||||
"experimental_settings_subtitle": "Use at your own risk!",
|
"experimental_settings_subtitle": "Używaj na własne ryzyko!",
|
||||||
"experimental_settings_title": "Experimental",
|
"experimental_settings_title": "Eksperymentalny",
|
||||||
"favorites_page_no_favorites": "No favorite assets found",
|
"favorites_page_no_favorites": "Nie znaleziono ulubionych zasobów",
|
||||||
"favorites_page_title": "Favorites",
|
"favorites_page_title": "Ulubione",
|
||||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
"home_page_add_to_album_conflicts": "Dodano {added} zasoby do albumu {album}. {failed} zasobów jest już w albumie.",
|
||||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
"home_page_add_to_album_err_local": "Nie można dodawać zasobów lokalnych do albumów, pomijam",
|
||||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
"home_page_add_to_album_success": "Dodano {added} zasoby do albumu {album}.",
|
||||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
"home_page_archive_err_local": "Nie można jeszcze zarchiwizować zasobów lokalnych, pomijanie",
|
||||||
"home_page_building_timeline": "Building the timeline",
|
"home_page_building_timeline": "Budowanie osi czasu",
|
||||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
"home_page_favorite_err_local": "Nie można dodać do ulubionych lokalnych zasobów, pomijam",
|
||||||
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
"home_page_first_time_notice": "Jeśli korzystasz z aplikacji po raz pierwszy, pamiętaj o wybraniu albumów zapasowych, aby oś czasu mogła zapełnić zdjęcia i filmy w albumach.",
|
||||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
"home_page_upload_err_limit": "Można przesłać maksymalnie 30 zasobów jednocześnie, pomijanie",
|
||||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
"image_viewer_page_state_provider_download_error": "Błąd pobierania",
|
||||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
"image_viewer_page_state_provider_download_success": "Pobieranie zakończone",
|
||||||
"library_page_albums": "Albumy",
|
"library_page_albums": "Albumy",
|
||||||
"library_page_archive": "Archive",
|
"library_page_archive": "Archiwum",
|
||||||
"library_page_device_albums": "Albums on Device",
|
"library_page_device_albums": "Albumy na Urządzeniu",
|
||||||
"library_page_favorites": "Favorites",
|
"library_page_favorites": "Ulubione",
|
||||||
"library_page_new_album": "Nowy album",
|
"library_page_new_album": "Nowy album",
|
||||||
"library_page_sharing": "Sharing",
|
"library_page_sharing": "Udostępnianie",
|
||||||
"library_page_sort_created": "Most recently created",
|
"library_page_sort_created": "Ostatnio utworzone",
|
||||||
"library_page_sort_title": "Album title",
|
"library_page_sort_title": "Tytuł albumu",
|
||||||
"login_disabled": "Login has been disabled",
|
"login_disabled": "Logowanie zostało wyłączone",
|
||||||
"login_form_api_exception": "API exception. Please check the server URL and try again.",
|
"login_form_api_exception": "Wyjątek API. Sprawdź adres URL serwera i spróbuj ponownie.",
|
||||||
"login_form_button_text": "Login",
|
"login_form_button_text": "Login",
|
||||||
"login_form_email_hint": "twojmail@email.com",
|
"login_form_email_hint": "twojmail@email.com",
|
||||||
"login_form_endpoint_hint": "http://ip-twojego-serwera:port/api",
|
"login_form_endpoint_hint": "http://ip-twojego-serwera:port/api",
|
||||||
"login_form_endpoint_url": "URL Serwera",
|
"login_form_endpoint_url": "URL Serwera",
|
||||||
"login_form_err_http": "Proszę określić http:// lub https://",
|
"login_form_err_http": "Proszę określić http:// lub https://",
|
||||||
"login_form_err_invalid_email": "Niepoprawny Email",
|
"login_form_err_invalid_email": "Niepoprawny Email",
|
||||||
"login_form_err_invalid_url": "Invalid URL",
|
"login_form_err_invalid_url": "Nieprawidłowy URL",
|
||||||
"login_form_err_leading_whitespace": "Białe znaki",
|
"login_form_err_leading_whitespace": "Białe znaki",
|
||||||
"login_form_err_trailing_whitespace": "Białe znaki po przecinku",
|
"login_form_err_trailing_whitespace": "Białe znaki po przecinku",
|
||||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
"login_form_failed_get_oauth_server_config": "Błąd logowania przy użyciu OAuth. Sprawdź adres URL serwera",
|
||||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
"login_form_failed_get_oauth_server_disable": "Funkcja OAuth nie jest dostępna na tym serwerze",
|
||||||
"login_form_failed_login": "Błąd logowania, sprawdź adres url serwera, email i hasło.",
|
"login_form_failed_login": "Błąd logowania, sprawdź adres url serwera, email i hasło.",
|
||||||
"login_form_label_email": "Email",
|
"login_form_label_email": "Email",
|
||||||
"login_form_label_password": "Hasło",
|
"login_form_label_password": "Hasło",
|
||||||
"login_form_next_button": "Next",
|
"login_form_next_button": "Dalej",
|
||||||
"login_form_password_hint": "hasło",
|
"login_form_password_hint": "hasło",
|
||||||
"login_form_save_login": "Pozostań zalogowany",
|
"login_form_save_login": "Pozostań zalogowany",
|
||||||
"login_form_server_empty": "Enter a server URL.",
|
"login_form_server_empty": "Wprowadź adres URL serwera.",
|
||||||
"login_form_server_error": "Could not connect to server.",
|
"login_form_server_error": "Nie można połączyć się z serwerem.",
|
||||||
"monthly_title_text_date_format": "MMMM y",
|
"monthly_title_text_date_format": "MMMM y",
|
||||||
"motion_photos_page_title": "Motion Photos",
|
"motion_photos_page_title": "Zdjęcia ruchome",
|
||||||
"notification_permission_dialog_cancel": "Cancel",
|
"notification_permission_dialog_cancel": "Anuluj",
|
||||||
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
|
"notification_permission_dialog_content": "Aby włączyć powiadomienia, przejdź do Ustawień i wybierz opcję Zezwalaj.",
|
||||||
"notification_permission_dialog_settings": "Settings",
|
"notification_permission_dialog_settings": "Ustawienia",
|
||||||
"notification_permission_list_tile_content": "Grant permission to enable notifications.",
|
"notification_permission_list_tile_content": "Przyznaj uprawnienia, aby włączyć powiadomienia.",
|
||||||
"notification_permission_list_tile_enable_button": "Enable Notifications",
|
"notification_permission_list_tile_enable_button": "Włącz Powiadomienia",
|
||||||
"notification_permission_list_tile_title": "Notification Permission",
|
"notification_permission_list_tile_title": "Pozwolenie na powiadomienia",
|
||||||
"partner_page_add_partner": "Add partner",
|
"partner_page_add_partner": "Dodaj partnera",
|
||||||
"partner_page_empty_message": "Your photos are not yet shared with any partner.",
|
"partner_page_empty_message": "Twoje zdjęcia nie są udostępnione żadnemu partnerowi",
|
||||||
"partner_page_no_more_users": "No more users to add",
|
"partner_page_no_more_users": "Brak użytkowników do dodania",
|
||||||
"partner_page_partner_add_failed": "Failed to add partner",
|
"partner_page_partner_add_failed": "Nie udało się dodać partnera",
|
||||||
"partner_page_select_partner": "Select partner",
|
"partner_page_select_partner": "Wybierz partnera",
|
||||||
"partner_page_shared_to_title": "Shared to",
|
"partner_page_shared_to_title": "Udostępniono",
|
||||||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
"partner_page_stop_sharing_content": "{} nie będziesz już mieć dostępu do swoich zdjęć.",
|
||||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
"partner_page_stop_sharing_title": "Przestać udostępniać swoje zdjęcia?",
|
||||||
"partner_page_title": "Partner",
|
"partner_page_title": "Partner",
|
||||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
"permission_onboarding_continue_anyway": "Kontynuuj mimo to",
|
||||||
"permission_onboarding_get_started": "Get started",
|
"permission_onboarding_get_started": "Rozpocznij",
|
||||||
"permission_onboarding_go_to_settings": "Go to settings",
|
"permission_onboarding_go_to_settings": "Przejdź do ustawień",
|
||||||
"permission_onboarding_grant_permission": "Grant permission",
|
"permission_onboarding_grant_permission": "Wydaj pozwolenie",
|
||||||
"permission_onboarding_log_out": "Log out",
|
"permission_onboarding_log_out": "Wyloguj",
|
||||||
"permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.",
|
"permission_onboarding_permission_denied": "Odmowa pozwolenia. Aby korzystać z Immich, przyznaj uprawnienia do zdjęć i filmów w Ustawieniach.",
|
||||||
"permission_onboarding_permission_granted": "Permission granted! You are all set.",
|
"permission_onboarding_permission_granted": "Pozwolenie udzielone! Wszystko gotowe.",
|
||||||
"permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.",
|
"permission_onboarding_permission_limited": "Pozwolenie ograniczone. Aby umożliwić Immichowi tworzenie kopii zapasowych całej kolekcji galerii i zarządzanie nią, przyznaj uprawnienia do zdjęć i filmów w Ustawieniach.",
|
||||||
"permission_onboarding_request": "Immich requires permission to view your photos and videos.",
|
"permission_onboarding_request": "Immich potrzebuje pozwolenia na przeglądanie Twoich zdjęć i filmów.",
|
||||||
"profile_drawer_app_logs": "Logs",
|
"profile_drawer_app_logs": "Logi",
|
||||||
"profile_drawer_client_server_up_to_date": "Klient i serwer są aktualne",
|
"profile_drawer_client_server_up_to_date": "Klient i serwer są aktualne",
|
||||||
"profile_drawer_settings": "Ustawienia",
|
"profile_drawer_settings": "Ustawienia",
|
||||||
"profile_drawer_sign_out": "Wyloguj się",
|
"profile_drawer_sign_out": "Wyloguj się",
|
||||||
"recently_added_page_title": "Recently Added",
|
"recently_added_page_title": "Ostatnio Dodane",
|
||||||
"search_bar_hint": "Szukaj swoich zdjęć",
|
"search_bar_hint": "Szukaj swoich zdjęć",
|
||||||
"search_page_categories": "Categories",
|
"search_page_categories": "Kategorie",
|
||||||
"search_page_favorites": "Favorites",
|
"search_page_favorites": "Ulubione",
|
||||||
"search_page_motion_photos": "Motion Photos",
|
"search_page_motion_photos": "Zdjęcia ruchome",
|
||||||
"search_page_no_objects": "Brak informacji o obiektach",
|
"search_page_no_objects": "Brak informacji o obiektach",
|
||||||
"search_page_no_places": "Brak informacji o miejscu",
|
"search_page_no_places": "Brak informacji o miejscu",
|
||||||
"search_page_people": "People",
|
"search_page_people": "Ludzie",
|
||||||
"search_page_places": "Miejsca",
|
"search_page_places": "Miejsca",
|
||||||
"search_page_recently_added": "Recently added",
|
"search_page_recently_added": "Ostatnio dodane",
|
||||||
"search_page_screenshots": "Screenshots",
|
"search_page_screenshots": "Zrzuty ekranu",
|
||||||
"search_page_selfies": "Selfies",
|
"search_page_selfies": "Selfi",
|
||||||
"search_page_things": "Rzeczy",
|
"search_page_things": "Rzeczy",
|
||||||
"search_page_videos": "Videos",
|
"search_page_videos": "Filmy",
|
||||||
"search_page_view_all_button": "View all",
|
"search_page_view_all_button": "Pokaż wszystkie",
|
||||||
"search_page_your_activity": "Your activity",
|
"search_page_your_activity": "Twoja aktywność",
|
||||||
"search_result_page_new_search_hint": "Nowe wyszukiwanie",
|
"search_result_page_new_search_hint": "Nowe wyszukiwanie",
|
||||||
"search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ",
|
"search_suggestion_list_smart_search_hint_1": "Inteligentne wyszukiwanie jest domyślnie włączone, aby wyszukiwać metadane, użyj składni ",
|
||||||
"search_suggestion_list_smart_search_hint_2": "m:your-search-term",
|
"search_suggestion_list_smart_search_hint_2": "m:your-search-term",
|
||||||
"select_additional_user_for_sharing_page_suggestions": "Propozycje",
|
"select_additional_user_for_sharing_page_suggestions": "Propozycje",
|
||||||
"select_user_for_sharing_page_err_album": "Nie udało się utworzyć albumu",
|
"select_user_for_sharing_page_err_album": "Nie udało się utworzyć albumu",
|
||||||
"select_user_for_sharing_page_share_suggestions": "Propozycje",
|
"select_user_for_sharing_page_share_suggestions": "Propozycje",
|
||||||
"server_info_box_app_version": "App Version",
|
"server_info_box_app_version": "Wersja Aplikacji",
|
||||||
"server_info_box_server_version": "Server Version",
|
"server_info_box_server_version": "Wersja Serwera",
|
||||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
"setting_image_viewer_help": "Przeglądarka szczegółów najpierw ładuje małą miniaturę, następnie ładuje podgląd średniej wielkości (jeśli jest włączony), a na koniec ładuje oryginał (jeśli jest włączony).",
|
||||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
"setting_image_viewer_original_subtitle": "Włącz ładowanie oryginalnego obrazu w pełnej rozdzielczości (dużego!). Wyłącz, aby zmniejszyć zużycie danych (zarówno w sieci, jak i w pamięci podręcznej urządzenia).",
|
||||||
"setting_image_viewer_original_title": "Load original image",
|
"setting_image_viewer_original_title": "Załaduj oryginalny obraz",
|
||||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
"setting_image_viewer_preview_subtitle": "Włącz ładowanie obrazu o średniej rozdzielczości. Wyłącz opcję bezpośredniego ładowania oryginału lub używania tylko miniatury.",
|
||||||
"setting_image_viewer_preview_title": "Load preview image",
|
"setting_image_viewer_preview_title": "Załaduj obraz podglądu",
|
||||||
"setting_notifications_notify_failures_grace_period": "Powiadomienie o awariach kopii zapasowych w tle: {}",
|
"setting_notifications_notify_failures_grace_period": "Powiadomienie o awariach kopii zapasowych w tle: {}",
|
||||||
"setting_notifications_notify_hours": "{} godzin",
|
"setting_notifications_notify_hours": "{} godzin",
|
||||||
"setting_notifications_notify_immediately": "natychmiast",
|
"setting_notifications_notify_immediately": "natychmiast",
|
||||||
"setting_notifications_notify_minutes": "{} minut",
|
"setting_notifications_notify_minutes": "{} minut",
|
||||||
"setting_notifications_notify_never": "nigdy",
|
"setting_notifications_notify_never": "nigdy",
|
||||||
"setting_notifications_notify_seconds": "{} seconds",
|
"setting_notifications_notify_seconds": "{} sekund",
|
||||||
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
|
"setting_notifications_single_progress_subtitle": "Szczegółowe informacje o postępie przesyłania dla każdego zasobu",
|
||||||
"setting_notifications_single_progress_title": "Show background backup detail progress",
|
"setting_notifications_single_progress_title": "Pokaż postęp szczegółów kopii zapasowej w tle",
|
||||||
"setting_notifications_subtitle": "Dostosuj preferencje powiadomień",
|
"setting_notifications_subtitle": "Dostosuj preferencje powiadomień",
|
||||||
"setting_notifications_title": "Powiadomienia",
|
"setting_notifications_title": "Powiadomienia",
|
||||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
"setting_notifications_total_progress_subtitle": "Ogólny postęp przesyłania (gotowe/całkowite zasoby)",
|
||||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
"setting_notifications_total_progress_title": "Pokaż całkowity postęp tworzenia kopii zapasowej w tle",
|
||||||
"setting_pages_app_bar_settings": "Ustawienia",
|
"setting_pages_app_bar_settings": "Ustawienia",
|
||||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
"settings_require_restart": "Aby zastosować to ustawienie, uruchom ponownie Immich",
|
||||||
"share_add": "Dodaj",
|
"share_add": "Dodaj",
|
||||||
"share_add_photos": "Dodaj zdjęcia",
|
"share_add_photos": "Dodaj zdjęcia",
|
||||||
"share_add_title": "Dodaj tytuł",
|
"share_add_title": "Dodaj tytuł",
|
||||||
@@ -282,7 +282,7 @@
|
|||||||
"tab_controller_nav_search": "Szukaj",
|
"tab_controller_nav_search": "Szukaj",
|
||||||
"tab_controller_nav_sharing": "Udostępnianie",
|
"tab_controller_nav_sharing": "Udostępnianie",
|
||||||
"theme_setting_asset_list_storage_indicator_title": "Pokaż wskaźnik przechowywania na kafelkach zasobów",
|
"theme_setting_asset_list_storage_indicator_title": "Pokaż wskaźnik przechowywania na kafelkach zasobów",
|
||||||
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
"theme_setting_asset_list_tiles_per_row_title": "Liczba zasobów w wierszu ({})",
|
||||||
"theme_setting_dark_mode_switch": "Ciemny Motyw",
|
"theme_setting_dark_mode_switch": "Ciemny Motyw",
|
||||||
"theme_setting_image_viewer_quality_subtitle": "Dostosuj jakość podglądu szczegółowości",
|
"theme_setting_image_viewer_quality_subtitle": "Dostosuj jakość podglądu szczegółowości",
|
||||||
"theme_setting_image_viewer_quality_title": "Jakość przeglądania obrazów",
|
"theme_setting_image_viewer_quality_title": "Jakość przeglądania obrazów",
|
||||||
@@ -291,10 +291,10 @@
|
|||||||
"theme_setting_theme_title": "Motyw",
|
"theme_setting_theme_title": "Motyw",
|
||||||
"theme_setting_three_stage_loading_subtitle": "Trójstopniowe ładowanie może zwiększyć wydajność ładowania, ale powoduje znacznie większe obciążenie sieci",
|
"theme_setting_three_stage_loading_subtitle": "Trójstopniowe ładowanie może zwiększyć wydajność ładowania, ale powoduje znacznie większe obciążenie sieci",
|
||||||
"theme_setting_three_stage_loading_title": "Włączenie trójstopniowego ładowania",
|
"theme_setting_three_stage_loading_title": "Włączenie trójstopniowego ładowania",
|
||||||
"upload_dialog_cancel": "Cancel",
|
"upload_dialog_cancel": "Anuluj",
|
||||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
"upload_dialog_info": "Czy chcesz wykonać kopię zapasową wybranych zasobów na serwerze?",
|
||||||
"upload_dialog_ok": "Upload",
|
"upload_dialog_ok": "Prześlij",
|
||||||
"upload_dialog_title": "Upload Asset",
|
"upload_dialog_title": "Prześlij Zasób",
|
||||||
"version_announcement_overlay_ack": "Potwierdzam",
|
"version_announcement_overlay_ack": "Potwierdzam",
|
||||||
"version_announcement_overlay_release_notes": "informacje o wydaniu",
|
"version_announcement_overlay_release_notes": "informacje o wydaniu",
|
||||||
"version_announcement_overlay_text_1": "Cześć przyjacielu, jest nowe wydanie",
|
"version_announcement_overlay_text_1": "Cześć przyjacielu, jest nowe wydanie",
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"add_to_album_bottom_sheet_added": "Добавлено в {album}",
|
"add_to_album_bottom_sheet_added": "Добавлено в {album}",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Уже в {album}",
|
"add_to_album_bottom_sheet_already_exists": "Уже в {album}",
|
||||||
"advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.",
|
"advanced_settings_prefer_remote_subtitle": "Некоторые устройства очень медленно загружают предпросмотр объектов, находящихся на устройстве. Активируйте эту настройку, чтобы вместо них загружались изображени с сервера.",
|
||||||
"advanced_settings_prefer_remote_title": "Prefer remote images",
|
"advanced_settings_prefer_remote_title": "Предпочитать фото на сервере",
|
||||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
"advanced_settings_tile_subtitle": "Расширенные настройки пользователя",
|
||||||
"advanced_settings_tile_title": "Advanced",
|
"advanced_settings_tile_title": "Расширенные",
|
||||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
"advanced_settings_troubleshooting_subtitle": "Включить расширенные возможности для решения проблем",
|
||||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
"advanced_settings_troubleshooting_title": "Решение проблем",
|
||||||
"album_info_card_backup_album_excluded": "ИСКЛЮЧЕН",
|
"album_info_card_backup_album_excluded": "ИСКЛЮЧЕН",
|
||||||
"album_info_card_backup_album_included": "ВКЛЮЧЕН",
|
"album_info_card_backup_album_included": "ВКЛЮЧЕН",
|
||||||
"album_thumbnail_card_item": "1 объект",
|
"album_thumbnail_card_item": "1 объект",
|
||||||
"album_thumbnail_card_items": "{} объектов",
|
"album_thumbnail_card_items": "{} объектов",
|
||||||
"album_thumbnail_card_shared": "· Общий",
|
"album_thumbnail_card_shared": "· Общий",
|
||||||
"album_thumbnail_owned": "Owned",
|
"album_thumbnail_owned": "Автор",
|
||||||
"album_thumbnail_shared_by": "Shared by {}",
|
"album_thumbnail_shared_by": "Поделился {}",
|
||||||
"album_viewer_appbar_share_delete": "Удалить альбом",
|
"album_viewer_appbar_share_delete": "Удалить альбом",
|
||||||
"album_viewer_appbar_share_err_delete": "Невозможно удалить альбом",
|
"album_viewer_appbar_share_err_delete": "Невозможно удалить альбом",
|
||||||
"album_viewer_appbar_share_err_leave": "Невозможно покинуть альбом",
|
"album_viewer_appbar_share_err_leave": "Невозможно покинуть альбом",
|
||||||
@@ -22,15 +22,15 @@
|
|||||||
"album_viewer_appbar_share_leave": "Покинуть альбом",
|
"album_viewer_appbar_share_leave": "Покинуть альбом",
|
||||||
"album_viewer_appbar_share_remove": "Удалить из альбома",
|
"album_viewer_appbar_share_remove": "Удалить из альбома",
|
||||||
"album_viewer_page_share_add_users": "Добавить пользователей",
|
"album_viewer_page_share_add_users": "Добавить пользователей",
|
||||||
"all_people_page_title": "People",
|
"all_people_page_title": "Люди",
|
||||||
"all_videos_page_title": "Videos",
|
"all_videos_page_title": "Видео",
|
||||||
"archive_page_no_archived_assets": "No archived assets found",
|
"archive_page_no_archived_assets": "В архиве сейчас пусто",
|
||||||
"archive_page_title": "Archive ({})",
|
"archive_page_title": "Архив ({})",
|
||||||
"asset_list_layout_settings_dynamic_layout_title": "Динамическое расположение",
|
"asset_list_layout_settings_dynamic_layout_title": "Динамическое расположение",
|
||||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
"asset_list_layout_settings_group_automatically": "Автоматически",
|
||||||
"asset_list_layout_settings_group_by": "Группировать объекты по",
|
"asset_list_layout_settings_group_by": "Группировать объекты по:",
|
||||||
"asset_list_layout_settings_group_by_month": "месяцу",
|
"asset_list_layout_settings_group_by_month": "Месяцу",
|
||||||
"asset_list_layout_settings_group_by_month_day": "месяцу и дню",
|
"asset_list_layout_settings_group_by_month_day": "Месяцу и дню",
|
||||||
"asset_list_settings_subtitle": "Настройки макета сетки фотографий",
|
"asset_list_settings_subtitle": "Настройки макета сетки фотографий",
|
||||||
"asset_list_settings_title": "Сетка фотографий",
|
"asset_list_settings_title": "Сетка фотографий",
|
||||||
"backup_album_selection_page_albums_device": "Альбомов на устройстве ({})",
|
"backup_album_selection_page_albums_device": "Альбомов на устройстве ({})",
|
||||||
@@ -48,9 +48,9 @@
|
|||||||
"backup_background_service_in_progress_notification": "Резервное копирование ваших объектов…",
|
"backup_background_service_in_progress_notification": "Резервное копирование ваших объектов…",
|
||||||
"backup_background_service_upload_failure_notification": "Ошибка загрузки {}",
|
"backup_background_service_upload_failure_notification": "Ошибка загрузки {}",
|
||||||
"backup_controller_page_albums": "Резервное копирование альбомов",
|
"backup_controller_page_albums": "Резервное копирование альбомов",
|
||||||
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
|
"backup_controller_page_background_app_refresh_disabled_content": "Включите фоновое обновление приложений в меню Настройки > Общие > Фоновое обновление приложений, чтобы использовать фоновое резервное копирование.",
|
||||||
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
|
"backup_controller_page_background_app_refresh_disabled_title": "Фоновое обновление отключено",
|
||||||
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
|
"backup_controller_page_background_app_refresh_enable_button_text": "Перейти в настройки",
|
||||||
"backup_controller_page_background_battery_info_link": "Показать как",
|
"backup_controller_page_background_battery_info_link": "Показать как",
|
||||||
"backup_controller_page_background_battery_info_message": "Для наилучшего фонового резервного копирования отключите любые настройки оптимизации батареи, ограничивающие фоновую активность для Immich.\n\nПоскольку это зависит от устройства, найдите необходимую информацию для производителя вашего устройства.",
|
"backup_controller_page_background_battery_info_message": "Для наилучшего фонового резервного копирования отключите любые настройки оптимизации батареи, ограничивающие фоновую активность для Immich.\n\nПоскольку это зависит от устройства, найдите необходимую информацию для производителя вашего устройства.",
|
||||||
"backup_controller_page_background_battery_info_ok": "ОК",
|
"backup_controller_page_background_battery_info_ok": "ОК",
|
||||||
@@ -68,8 +68,8 @@
|
|||||||
"backup_controller_page_backup_selected": "Выбрано: ",
|
"backup_controller_page_backup_selected": "Выбрано: ",
|
||||||
"backup_controller_page_backup_sub": "Загруженные фото и видео",
|
"backup_controller_page_backup_sub": "Загруженные фото и видео",
|
||||||
"backup_controller_page_cancel": "Отмена",
|
"backup_controller_page_cancel": "Отмена",
|
||||||
"backup_controller_page_created": "Создано на: {}",
|
"backup_controller_page_created": "Создано: {}",
|
||||||
"backup_controller_page_desc_backup": "Включите резервное копирование в активном режиме, чтобы автоматически загружать новые активы на сервер при открытии приложения.",
|
"backup_controller_page_desc_backup": "Включите резервное копирование в активном режиме, чтобы автоматически загружать новые объекты на сервер при открытии приложения.",
|
||||||
"backup_controller_page_excluded": "Исключены:",
|
"backup_controller_page_excluded": "Исключены:",
|
||||||
"backup_controller_page_failed": "Неудачных ({})",
|
"backup_controller_page_failed": "Неудачных ({})",
|
||||||
"backup_controller_page_filename": "Имя файла: {} [{}]",
|
"backup_controller_page_filename": "Имя файла: {} [{}]",
|
||||||
@@ -89,14 +89,14 @@
|
|||||||
"backup_controller_page_total_sub": "Все уникальные фото и видео из выбранных альбомов",
|
"backup_controller_page_total_sub": "Все уникальные фото и видео из выбранных альбомов",
|
||||||
"backup_controller_page_turn_off": "Выключить резервное копирование в активном режиме",
|
"backup_controller_page_turn_off": "Выключить резервное копирование в активном режиме",
|
||||||
"backup_controller_page_turn_on": "Включить резервное копирование в активном режиме",
|
"backup_controller_page_turn_on": "Включить резервное копирование в активном режиме",
|
||||||
"backup_controller_page_uploading_file_info": "Загрузка информации о файле",
|
"backup_controller_page_uploading_file_info": "Информация о загружаемом файле",
|
||||||
"backup_err_only_album": "Невозможно удалить единственный альбом",
|
"backup_err_only_album": "Невозможно удалить единственный альбом",
|
||||||
"backup_info_card_assets": "объекты",
|
"backup_info_card_assets": "объектов",
|
||||||
"backup_manual_cancelled": "Cancelled",
|
"backup_manual_cancelled": "Отменено",
|
||||||
"backup_manual_failed": "Failed",
|
"backup_manual_failed": "Неудачно",
|
||||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
"backup_manual_in_progress": "Загрузка уже в процессе, попробуйте позже",
|
||||||
"backup_manual_success": "Success",
|
"backup_manual_success": "Успешно",
|
||||||
"backup_manual_title": "Upload status",
|
"backup_manual_title": "Статус загрузки",
|
||||||
"cache_settings_album_thumbnails": "Миниатюры страниц библиотеки ({} объектов)",
|
"cache_settings_album_thumbnails": "Миниатюры страниц библиотеки ({} объектов)",
|
||||||
"cache_settings_clear_cache_button": "Очистить кэш",
|
"cache_settings_clear_cache_button": "Очистить кэш",
|
||||||
"cache_settings_clear_cache_button_title": "Очищает кэш приложения. Это значительно повлияет на производительность приложения, до тех пор, пока кэш не будет перестроен заново.",
|
"cache_settings_clear_cache_button_title": "Очищает кэш приложения. Это значительно повлияет на производительность приложения, до тех пор, пока кэш не будет перестроен заново.",
|
||||||
@@ -118,66 +118,66 @@
|
|||||||
"common_add_to_album": "Добавить в альбом",
|
"common_add_to_album": "Добавить в альбом",
|
||||||
"common_change_password": "Изменить пароль",
|
"common_change_password": "Изменить пароль",
|
||||||
"common_create_new_album": "Создать новый альбом",
|
"common_create_new_album": "Создать новый альбом",
|
||||||
"common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.",
|
"common_server_error": "Пожалуйста, проверьте подключение к сети и убедитесь, что ваш сервер доступен, а версии приложения и сервера — совместимы.",
|
||||||
"common_shared": "Общие",
|
"common_shared": "Общие",
|
||||||
"control_bottom_app_bar_add_to_album": "Добавить в альбом",
|
"control_bottom_app_bar_add_to_album": "Добавить в альбом",
|
||||||
"control_bottom_app_bar_album_info": "{} файлов",
|
"control_bottom_app_bar_album_info": "{} файлов",
|
||||||
"control_bottom_app_bar_album_info_shared": "{} файлов · Общий",
|
"control_bottom_app_bar_album_info_shared": "{} файлов · Общий",
|
||||||
"control_bottom_app_bar_archive": "Archive",
|
"control_bottom_app_bar_archive": "Архив",
|
||||||
"control_bottom_app_bar_create_new_album": "\nСоздать новый альбом",
|
"control_bottom_app_bar_create_new_album": "\nСоздать новый альбом",
|
||||||
"control_bottom_app_bar_delete": "Удалить",
|
"control_bottom_app_bar_delete": "Удалить",
|
||||||
"control_bottom_app_bar_favorite": "Избранное",
|
"control_bottom_app_bar_favorite": "Избранное",
|
||||||
"control_bottom_app_bar_share": "Поделиться",
|
"control_bottom_app_bar_share": "Поделиться",
|
||||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
"control_bottom_app_bar_unarchive": "Восстановить",
|
||||||
"create_album_page_untitled": "Без названия",
|
"create_album_page_untitled": "Без названия",
|
||||||
"create_shared_album_page_create": "Создать",
|
"create_shared_album_page_create": "Создать",
|
||||||
"create_shared_album_page_share": "Поделиться",
|
"create_shared_album_page_share": "Поделиться",
|
||||||
"create_shared_album_page_share_add_assets": "ДОБАВИТЬ ОБЪЕКТЫ",
|
"create_shared_album_page_share_add_assets": "ДОБАВИТЬ ОБЪЕКТЫ",
|
||||||
"create_shared_album_page_share_select_photos": "Выберите фотографии",
|
"create_shared_album_page_share_select_photos": "Выберите фотографии",
|
||||||
"curated_location_page_title": "Places",
|
"curated_location_page_title": "Места",
|
||||||
"curated_object_page_title": "Things",
|
"curated_object_page_title": "Предметы",
|
||||||
"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",
|
||||||
"date_format": "E, LLL d, y • h:mm a",
|
"date_format": "E, LLL d, y • h:mm a",
|
||||||
"delete_dialog_alert": "Эти объекты будут безвозвратно удалены из приложения, а также с вашего устройства",
|
"delete_dialog_alert": "Эти элементы будут безвозвратно удалены из приложения, а также с вашего устройства",
|
||||||
"delete_dialog_cancel": "Отменить",
|
"delete_dialog_cancel": "Отменить",
|
||||||
"delete_dialog_ok": "Удалить",
|
"delete_dialog_ok": "Удалить",
|
||||||
"delete_dialog_title": "Удалить навсегда",
|
"delete_dialog_title": "Удалить навсегда",
|
||||||
"description_input_hint_text": "Add description...",
|
"description_input_hint_text": "Добавить описание...",
|
||||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
"description_input_submit_error": "Не удалось обновить описание, проверьте логи, чтобы узнать причину",
|
||||||
"exif_bottom_sheet_description": "Добавить описание...",
|
"exif_bottom_sheet_description": "Добавить описание...",
|
||||||
"exif_bottom_sheet_details": "ПОДРОБНОСТИ",
|
"exif_bottom_sheet_details": "ПОДРОБНОСТИ",
|
||||||
"exif_bottom_sheet_location": "МЕСТОПОЛОЖЕНИЕ",
|
"exif_bottom_sheet_location": "МЕСТОПОЛОЖЕНИЕ",
|
||||||
"experimental_settings_new_asset_list_subtitle": "Работа ведётся",
|
"experimental_settings_new_asset_list_subtitle": "Ведутся работы",
|
||||||
"experimental_settings_new_asset_list_title": "Включить экспериментальную сетку фотографий",
|
"experimental_settings_new_asset_list_title": "Включить экспериментальную сетку фотографий",
|
||||||
"experimental_settings_subtitle": "Используйте на свой страх и риск!",
|
"experimental_settings_subtitle": "Используйте на свой страх и риск!",
|
||||||
"experimental_settings_title": "Экспериментальные функции",
|
"experimental_settings_title": "Экспериментальные функции",
|
||||||
"favorites_page_no_favorites": "No favorite assets found",
|
"favorites_page_no_favorites": "В избранном сейчас пусто",
|
||||||
"favorites_page_title": "Избранное",
|
"favorites_page_title": "Избранное",
|
||||||
"home_page_add_to_album_conflicts": "Добавлено {added} объектов в альбом {album}. Объекты {failed} уже есть в альбоме.",
|
"home_page_add_to_album_conflicts": "Добавлено {added} объектов в альбом {album}. Объекты {failed} уже есть в альбоме.",
|
||||||
"home_page_add_to_album_err_local": "Пока нельзя добавлять локальные объекты в альбомы, пропускаем",
|
"home_page_add_to_album_err_local": "Пока нельзя добавлять локальные объекты в альбомы, пропускаем",
|
||||||
"home_page_add_to_album_success": "Добавлено {added} объектов в альбом {album}.",
|
"home_page_add_to_album_success": "Добавлено {added} объектов в альбом {album}.",
|
||||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
"home_page_archive_err_local": "Пока невозможно добавить локальные объекты в архив, пропускаем",
|
||||||
"home_page_building_timeline": "Построение временной шкалы",
|
"home_page_building_timeline": "Построение временной шкалы",
|
||||||
"home_page_favorite_err_local": "Пока не удается добавить в избранное локальные объекты, пропускаем",
|
"home_page_favorite_err_local": "Пока не удается добавить в избранное локальные объекты, пропускаем",
|
||||||
"home_page_first_time_notice": "Если вы используете приложение впервые, убедитесь, что вы выбрали резервный(е) альбом(ы), чтобы временная шкала могла заполнить фотографии и видео в альбоме(ах).",
|
"home_page_first_time_notice": "Если вы используете приложение впервые, убедитесь, что вы выбрали резервный(е) альбом(ы), чтобы временная шкала могла заполнить фотографии и видео в альбоме(ах).",
|
||||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
"home_page_upload_err_limit": "Вы можете выгрузить максимум 30 файлов за раз",
|
||||||
"image_viewer_page_state_provider_download_error": "Ошибка загрузки",
|
"image_viewer_page_state_provider_download_error": "Ошибка загрузки",
|
||||||
"image_viewer_page_state_provider_download_success": "Успешно загружено",
|
"image_viewer_page_state_provider_download_success": "Успешно загружено",
|
||||||
"library_page_albums": "Альбомы",
|
"library_page_albums": "Альбомы",
|
||||||
"library_page_archive": "Archive",
|
"library_page_archive": "Архив",
|
||||||
"library_page_device_albums": "Albums on Device",
|
"library_page_device_albums": "Альбомы на устройстве",
|
||||||
"library_page_favorites": "Избранное",
|
"library_page_favorites": "Избранное",
|
||||||
"library_page_new_album": "Новый альбом",
|
"library_page_new_album": "Новый альбом",
|
||||||
"library_page_sharing": "Общие",
|
"library_page_sharing": "Общие",
|
||||||
"library_page_sort_created": "По новизне",
|
"library_page_sort_created": "По новизне",
|
||||||
"library_page_sort_title": "По названию альбома",
|
"library_page_sort_title": "По названию альбома",
|
||||||
"login_disabled": "Login has been disabled",
|
"login_disabled": "Вход отключен",
|
||||||
"login_form_api_exception": "API exception. Please check the server URL and try again.",
|
"login_form_api_exception": "Ошибка при попытке взаимодействия с сервером. Проверьте URL-адрес до него и попробуйте еще раз.",
|
||||||
"login_form_button_text": "Войти",
|
"login_form_button_text": "Войти",
|
||||||
"login_form_email_hint": "youremail@email.com",
|
"login_form_email_hint": "youremail@email.com",
|
||||||
"login_form_endpoint_hint": "http://your-server-ip:port/api",
|
"login_form_endpoint_hint": "http://your-server-ip:port/api",
|
||||||
"login_form_endpoint_url": "URL Адрес сервера",
|
"login_form_endpoint_url": "URL-aдрес сервера",
|
||||||
"login_form_err_http": "Пожалуйста, укажите http:// или https://",
|
"login_form_err_http": "Пожалуйста, укажите http:// или https://",
|
||||||
"login_form_err_invalid_email": "Неверный адрес Email",
|
"login_form_err_invalid_email": "Неверный адрес Email",
|
||||||
"login_form_err_invalid_url": "Неверная ссылка",
|
"login_form_err_invalid_url": "Неверная ссылка",
|
||||||
@@ -188,60 +188,60 @@
|
|||||||
"login_form_failed_login": "Ошибка при входе в систему, проверьте URL-адрес сервера, адрес электронной почты и пароль",
|
"login_form_failed_login": "Ошибка при входе в систему, проверьте URL-адрес сервера, адрес электронной почты и пароль",
|
||||||
"login_form_label_email": "Email",
|
"login_form_label_email": "Email",
|
||||||
"login_form_label_password": "Пароль",
|
"login_form_label_password": "Пароль",
|
||||||
"login_form_next_button": "Next",
|
"login_form_next_button": "Далее",
|
||||||
"login_form_password_hint": "пароль",
|
"login_form_password_hint": "пароль",
|
||||||
"login_form_save_login": "Оставаться в системе",
|
"login_form_save_login": "Оставаться в системе",
|
||||||
"login_form_server_empty": "Enter a server URL.",
|
"login_form_server_empty": "Введите URL-адрес вашего сервера.",
|
||||||
"login_form_server_error": "Could not connect to server.",
|
"login_form_server_error": "Нет соединения с сервером.",
|
||||||
"monthly_title_text_date_format": "MMMM y",
|
"monthly_title_text_date_format": "MMMM y",
|
||||||
"motion_photos_page_title": "Motion Photos",
|
"motion_photos_page_title": "Динамические фото",
|
||||||
"notification_permission_dialog_cancel": "Отмена",
|
"notification_permission_dialog_cancel": "Отмена",
|
||||||
"notification_permission_dialog_content": "Чтобы включить уведомления, перейдите в «Настройки» и выберите «Разрешить».",
|
"notification_permission_dialog_content": "Чтобы включить уведомления, перейдите в «Настройки» и выберите «Разрешить».",
|
||||||
"notification_permission_dialog_settings": "Настройки",
|
"notification_permission_dialog_settings": "Настройки",
|
||||||
"notification_permission_list_tile_content": "Предоставьте разрешение на включение уведомлений",
|
"notification_permission_list_tile_content": "Предоставьте разрешение на включение уведомлений",
|
||||||
"notification_permission_list_tile_enable_button": "Включить уведомления",
|
"notification_permission_list_tile_enable_button": "Включить уведомления",
|
||||||
"notification_permission_list_tile_title": "Разрешение на уведомление",
|
"notification_permission_list_tile_title": "Разрешение на уведомление",
|
||||||
"partner_page_add_partner": "Add partner",
|
"partner_page_add_partner": "Добавить партнёра",
|
||||||
"partner_page_empty_message": "Your photos are not yet shared with any partner.",
|
"partner_page_empty_message": "У вашего партнёра еще пока нет доступа к вашим фото",
|
||||||
"partner_page_no_more_users": "No more users to add",
|
"partner_page_no_more_users": "Выбраны все доступные пользователи",
|
||||||
"partner_page_partner_add_failed": "Failed to add partner",
|
"partner_page_partner_add_failed": "Не удалось добавить партнёра",
|
||||||
"partner_page_select_partner": "Select partner",
|
"partner_page_select_partner": "Выбрать партнёра",
|
||||||
"partner_page_shared_to_title": "Shared to",
|
"partner_page_shared_to_title": "Поделиться с...",
|
||||||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
"partner_page_stop_sharing_content": "{} больше не сможет получить доступ к вашим фотографиям",
|
||||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
"partner_page_stop_sharing_title": "Закрыть доступ партнёра к вашим фото?",
|
||||||
"partner_page_title": "Partner",
|
"partner_page_title": "Партнёр",
|
||||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
"permission_onboarding_continue_anyway": "Все равно продолжить",
|
||||||
"permission_onboarding_get_started": "Get started",
|
"permission_onboarding_get_started": "Давайте начнём",
|
||||||
"permission_onboarding_go_to_settings": "Go to settings",
|
"permission_onboarding_go_to_settings": "Перейти в настройки",
|
||||||
"permission_onboarding_grant_permission": "Grant permission",
|
"permission_onboarding_grant_permission": "Предоставить разрешение",
|
||||||
"permission_onboarding_log_out": "Log out",
|
"permission_onboarding_log_out": "Выйти",
|
||||||
"permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.",
|
"permission_onboarding_permission_denied": "Не удалось получить доступ.",
|
||||||
"permission_onboarding_permission_granted": "Permission granted! You are all set.",
|
"permission_onboarding_permission_granted": "Доступ получен! Всё готово.",
|
||||||
"permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.",
|
"permission_onboarding_permission_limited": "Доступ к файлам ограничен. Чтобы Immich мог создавать резервные копии и управлять вашей галереей, пожалуйста, предоставьте приложению разрешение на доступ к \"Фото и видео\" в Настройках.",
|
||||||
"permission_onboarding_request": "Immich requires permission to view your photos and videos.",
|
"permission_onboarding_request": "Immich просит вас предоставить разрешение на доступ к вашим фото и видео",
|
||||||
"profile_drawer_app_logs": "Журналы",
|
"profile_drawer_app_logs": "Журнал",
|
||||||
"profile_drawer_client_server_up_to_date": "Клиент и сервер обновлены",
|
"profile_drawer_client_server_up_to_date": "Клиент и сервер обновлены",
|
||||||
"profile_drawer_settings": "Настройки",
|
"profile_drawer_settings": "Настройки",
|
||||||
"profile_drawer_sign_out": "Выйти",
|
"profile_drawer_sign_out": "Выйти",
|
||||||
"recently_added_page_title": "Recently Added",
|
"recently_added_page_title": "Недавно добавленные",
|
||||||
"search_bar_hint": "Поиск фотографий",
|
"search_bar_hint": "Поиск фотографий",
|
||||||
"search_page_categories": "Categories",
|
"search_page_categories": "Категории",
|
||||||
"search_page_favorites": "Favorites",
|
"search_page_favorites": "Избранное",
|
||||||
"search_page_motion_photos": "Motion Photos",
|
"search_page_motion_photos": "Динамические фото",
|
||||||
"search_page_no_objects": "Нет доступной информации об объектах",
|
"search_page_no_objects": "Нет доступной информации об объектах",
|
||||||
"search_page_no_places": "Информация о местах отсутствует",
|
"search_page_no_places": "Информация о местах отсутствует",
|
||||||
"search_page_people": "People",
|
"search_page_people": "Люди",
|
||||||
"search_page_places": "Места",
|
"search_page_places": "Места",
|
||||||
"search_page_recently_added": "Recently added",
|
"search_page_recently_added": "Недавно добавленные",
|
||||||
"search_page_screenshots": "Screenshots",
|
"search_page_screenshots": "Скриншоты",
|
||||||
"search_page_selfies": "Selfies",
|
"search_page_selfies": "Селфи",
|
||||||
"search_page_things": "Предметы",
|
"search_page_things": "Предметы",
|
||||||
"search_page_videos": "Videos",
|
"search_page_videos": "Видео",
|
||||||
"search_page_view_all_button": "View all",
|
"search_page_view_all_button": "Посмотреть все",
|
||||||
"search_page_your_activity": "Your activity",
|
"search_page_your_activity": "Ваша активность",
|
||||||
"search_result_page_new_search_hint": "Новый поиск",
|
"search_result_page_new_search_hint": "Новый поиск",
|
||||||
"search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ",
|
"search_suggestion_list_smart_search_hint_1": "Интеллектуальный поиск включен по умолчанию, для поиска метаданных используйте специальный синтаксис",
|
||||||
"search_suggestion_list_smart_search_hint_2": "m:your-search-term",
|
"search_suggestion_list_smart_search_hint_2": "m:ваш-запрос",
|
||||||
"select_additional_user_for_sharing_page_suggestions": "Предложения",
|
"select_additional_user_for_sharing_page_suggestions": "Предложения",
|
||||||
"select_user_for_sharing_page_err_album": "\nНе удалось создать альбом",
|
"select_user_for_sharing_page_err_album": "\nНе удалось создать альбом",
|
||||||
"select_user_for_sharing_page_share_suggestions": "Предложения",
|
"select_user_for_sharing_page_share_suggestions": "Предложения",
|
||||||
@@ -276,7 +276,7 @@
|
|||||||
"sharing_page_description": "Создавайте общие альбомы, чтобы делиться фотографиями и видео с людьми в вашей сети.",
|
"sharing_page_description": "Создавайте общие альбомы, чтобы делиться фотографиями и видео с людьми в вашей сети.",
|
||||||
"sharing_page_empty_list": "ПУСТОЙ СПИСОК",
|
"sharing_page_empty_list": "ПУСТОЙ СПИСОК",
|
||||||
"sharing_silver_appbar_create_shared_album": "Создать общий альбом",
|
"sharing_silver_appbar_create_shared_album": "Создать общий альбом",
|
||||||
"sharing_silver_appbar_share_partner": "Поделиться с партнером",
|
"sharing_silver_appbar_share_partner": "Поделиться с партнёром",
|
||||||
"tab_controller_nav_library": "Библиотека",
|
"tab_controller_nav_library": "Библиотека",
|
||||||
"tab_controller_nav_photos": "Фото",
|
"tab_controller_nav_photos": "Фото",
|
||||||
"tab_controller_nav_search": "Поиск",
|
"tab_controller_nav_search": "Поиск",
|
||||||
@@ -284,17 +284,17 @@
|
|||||||
"theme_setting_asset_list_storage_indicator_title": "Показать индикатор хранилища на плитках объектов",
|
"theme_setting_asset_list_storage_indicator_title": "Показать индикатор хранилища на плитках объектов",
|
||||||
"theme_setting_asset_list_tiles_per_row_title": "Количество объектов в строке ({})",
|
"theme_setting_asset_list_tiles_per_row_title": "Количество объектов в строке ({})",
|
||||||
"theme_setting_dark_mode_switch": "Тёмная тема",
|
"theme_setting_dark_mode_switch": "Тёмная тема",
|
||||||
"theme_setting_image_viewer_quality_subtitle": "Настройте качество просмотра подробного изображения",
|
"theme_setting_image_viewer_quality_subtitle": "Настройка качества детального просмотра изображения",
|
||||||
"theme_setting_image_viewer_quality_title": "Качество просмотра изображений",
|
"theme_setting_image_viewer_quality_title": "Качество просмотра изображений",
|
||||||
"theme_setting_system_theme_switch": "Автоматически (Как в системе)",
|
"theme_setting_system_theme_switch": "Автоматически (Как в системе)",
|
||||||
"theme_setting_theme_subtitle": "Выберите настройки темы приложения",
|
"theme_setting_theme_subtitle": "Выберите настройки темы приложения",
|
||||||
"theme_setting_theme_title": "Тема",
|
"theme_setting_theme_title": "Тема",
|
||||||
"theme_setting_three_stage_loading_subtitle": "Трехэтапная загрузка может повысить производительность загрузки, но вызывает значительно более высокую нагрузку на сеть",
|
"theme_setting_three_stage_loading_subtitle": "Трехэтапная загрузка может повысить производительность загрузки, но вызывает значительно более высокую нагрузку на сеть",
|
||||||
"theme_setting_three_stage_loading_title": "Включить трехэтапную загрузку",
|
"theme_setting_three_stage_loading_title": "Включить трехэтапную загрузку",
|
||||||
"upload_dialog_cancel": "Cancel",
|
"upload_dialog_cancel": "Отмена",
|
||||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
"upload_dialog_info": "Вы хотите загрузить выбранный объект(ы) на ваш сервер?",
|
||||||
"upload_dialog_ok": "Upload",
|
"upload_dialog_ok": "Загрузить",
|
||||||
"upload_dialog_title": "Upload Asset",
|
"upload_dialog_title": "Загрузить объект",
|
||||||
"version_announcement_overlay_ack": "Подтверждение",
|
"version_announcement_overlay_ack": "Подтверждение",
|
||||||
"version_announcement_overlay_release_notes": "примечания к выпуску",
|
"version_announcement_overlay_release_notes": "примечания к выпуску",
|
||||||
"version_announcement_overlay_text_1": "Привет друг, вышел новый релиз",
|
"version_announcement_overlay_text_1": "Привет друг, вышел новый релиз",
|
||||||
|
|||||||
@@ -269,7 +269,7 @@
|
|||||||
"share_add": "Pridať",
|
"share_add": "Pridať",
|
||||||
"share_add_photos": "Pridať fotografie",
|
"share_add_photos": "Pridať fotografie",
|
||||||
"share_add_title": "Pridať názov",
|
"share_add_title": "Pridať názov",
|
||||||
"share_create_album": "Tvorba albumu",
|
"share_create_album": "Vytvoriť album",
|
||||||
"share_dialog_preparing": "Pripravujem...",
|
"share_dialog_preparing": "Pripravujem...",
|
||||||
"share_invite": "Pozvať do albumu",
|
"share_invite": "Pozvať do albumu",
|
||||||
"sharing_page_album": "Zdieľané albumy",
|
"sharing_page_album": "Zdieľané albumy",
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
"backup_controller_page_uploading_file_info": "Uploading file info",
|
"backup_controller_page_uploading_file_info": "Uploading file info",
|
||||||
"backup_err_only_album": "ไม่สามารถนำอัลบั้มสุดท้ายออกได้",
|
"backup_err_only_album": "ไม่สามารถนำอัลบั้มสุดท้ายออกได้",
|
||||||
"backup_info_card_assets": "ทรัพยากร",
|
"backup_info_card_assets": "ทรัพยากร",
|
||||||
"backup_manual_cancelled": "Cancelled",
|
"backup_manual_cancelled": "ถูกยกเลิก",
|
||||||
"backup_manual_failed": "ล้มเหลว",
|
"backup_manual_failed": "ล้มเหลว",
|
||||||
"backup_manual_in_progress": "อัปโหลดกำลังดำเนินการอยู่ โปรดลองใหม่ในสักพัก",
|
"backup_manual_in_progress": "อัปโหลดกำลังดำเนินการอยู่ โปรดลองใหม่ในสักพัก",
|
||||||
"backup_manual_success": "สำเร็จ",
|
"backup_manual_success": "สำเร็จ",
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
"backup_controller_page_info": "备份信息",
|
"backup_controller_page_info": "备份信息",
|
||||||
"backup_controller_page_none_selected": "未选择",
|
"backup_controller_page_none_selected": "未选择",
|
||||||
"backup_controller_page_remainder": "剩余",
|
"backup_controller_page_remainder": "剩余",
|
||||||
"backup_controller_page_remainder_sub": "选中的数据中尚未备份的数据",
|
"backup_controller_page_remainder_sub": "所选数据中尚未备份的数据",
|
||||||
"backup_controller_page_select": "选择",
|
"backup_controller_page_select": "选择",
|
||||||
"backup_controller_page_server_storage": "服务器存储",
|
"backup_controller_page_server_storage": "服务器存储",
|
||||||
"backup_controller_page_start_backup": "开始备份",
|
"backup_controller_page_start_backup": "开始备份",
|
||||||
@@ -89,26 +89,26 @@
|
|||||||
"backup_controller_page_total_sub": "选中相册中的所有不重复的视频和图像",
|
"backup_controller_page_total_sub": "选中相册中的所有不重复的视频和图像",
|
||||||
"backup_controller_page_turn_off": "关闭前台备份",
|
"backup_controller_page_turn_off": "关闭前台备份",
|
||||||
"backup_controller_page_turn_on": "开启前台备份",
|
"backup_controller_page_turn_on": "开启前台备份",
|
||||||
"backup_controller_page_uploading_file_info": "正在上传文件信息",
|
"backup_controller_page_uploading_file_info": "正在上传中的文件信息",
|
||||||
"backup_err_only_album": "不能移除唯一的一个相册",
|
"backup_err_only_album": "不能移除唯一的一个相册",
|
||||||
"backup_info_card_assets": "张",
|
"backup_info_card_assets": "项",
|
||||||
"backup_manual_cancelled": "Cancelled",
|
"backup_manual_cancelled": "已取消",
|
||||||
"backup_manual_failed": "失败",
|
"backup_manual_failed": "失败",
|
||||||
"backup_manual_in_progress": "上传正在进行中,请稍后再试",
|
"backup_manual_in_progress": "上传正在进行中,请稍后再试",
|
||||||
"backup_manual_success": "成功",
|
"backup_manual_success": "成功",
|
||||||
"backup_manual_title": "上传状态",
|
"backup_manual_title": "上传状态",
|
||||||
"cache_settings_album_thumbnails": "图库缩略图({} 张)",
|
"cache_settings_album_thumbnails": "图库缩略图({} 项)",
|
||||||
"cache_settings_clear_cache_button": "清除缓存",
|
"cache_settings_clear_cache_button": "清除缓存",
|
||||||
"cache_settings_clear_cache_button_title": "清除应用缓存。在重新生成缓存之前,将显著影响应用的性能。",
|
"cache_settings_clear_cache_button_title": "清除应用缓存。在重新生成缓存之前,将显著影响应用的性能。",
|
||||||
"cache_settings_image_cache_size": "图像缓存大小({} 张)",
|
"cache_settings_image_cache_size": "图像缓存大小({} 项)",
|
||||||
"cache_settings_statistics_album": "图库缩略图",
|
"cache_settings_statistics_album": "图库缩略图",
|
||||||
"cache_settings_statistics_assets": "{} 张({})",
|
"cache_settings_statistics_assets": "{} 项({})",
|
||||||
"cache_settings_statistics_full": "完整图像",
|
"cache_settings_statistics_full": "完整图像",
|
||||||
"cache_settings_statistics_shared": "共享相册缩略图",
|
"cache_settings_statistics_shared": "共享相册缩略图",
|
||||||
"cache_settings_statistics_thumbnail": "缩略图",
|
"cache_settings_statistics_thumbnail": "缩略图",
|
||||||
"cache_settings_statistics_title": "缓存使用情况",
|
"cache_settings_statistics_title": "缓存使用情况",
|
||||||
"cache_settings_subtitle": "控制 Immich 的缓存行为",
|
"cache_settings_subtitle": "控制 Immich 的缓存行为",
|
||||||
"cache_settings_thumbnail_size": "缩略图缓存大小({} 张)",
|
"cache_settings_thumbnail_size": "缩略图缓存大小({} 项)",
|
||||||
"cache_settings_title": "缓存设置",
|
"cache_settings_title": "缓存设置",
|
||||||
"change_password_form_confirm_password": "确认密码",
|
"change_password_form_confirm_password": "确认密码",
|
||||||
"change_password_form_description": "{firstName} {lastName} 您好,\n\n这是您首次登录系统,或被管理员要求更改密码。\n请在下方输入新密码。",
|
"change_password_form_description": "{firstName} {lastName} 您好,\n\n这是您首次登录系统,或被管理员要求更改密码。\n请在下方输入新密码。",
|
||||||
@@ -139,7 +139,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",
|
||||||
"date_format": "E, LLL d, y • h:mm a",
|
"date_format": "E, LLL d, y • h:mm a",
|
||||||
"delete_dialog_alert": "这些项目将从 Immich 和您的设备中永久删除",
|
"delete_dialog_alert": "这些项目将从 Immich 和 您的设备 中永久删除",
|
||||||
"delete_dialog_cancel": "取消",
|
"delete_dialog_cancel": "取消",
|
||||||
"delete_dialog_ok": "删除",
|
"delete_dialog_ok": "删除",
|
||||||
"delete_dialog_title": "永久删除",
|
"delete_dialog_title": "永久删除",
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
"login_form_button_text": "登录",
|
"login_form_button_text": "登录",
|
||||||
"login_form_email_hint": "youremail@email.com",
|
"login_form_email_hint": "youremail@email.com",
|
||||||
"login_form_endpoint_hint": "http(s)://你的服务器地址:端口/api",
|
"login_form_endpoint_hint": "http(s)://你的服务器地址:端口/api",
|
||||||
"login_form_endpoint_url": "服务器终结点地址",
|
"login_form_endpoint_url": "服务器链接地址",
|
||||||
"login_form_err_http": "请注明 http:// 或 https://",
|
"login_form_err_http": "请注明 http:// 或 https://",
|
||||||
"login_form_err_invalid_email": "无效的电子邮件",
|
"login_form_err_invalid_email": "无效的电子邮件",
|
||||||
"login_form_err_invalid_url": "无效的地址",
|
"login_form_err_invalid_url": "无效的地址",
|
||||||
@@ -270,7 +270,7 @@
|
|||||||
"share_add_photos": "添加项目",
|
"share_add_photos": "添加项目",
|
||||||
"share_add_title": "添加标题",
|
"share_add_title": "添加标题",
|
||||||
"share_create_album": "创建相册",
|
"share_create_album": "创建相册",
|
||||||
"share_dialog_preparing": "这种准备...",
|
"share_dialog_preparing": "正在准备...",
|
||||||
"share_invite": "邀请相册共享",
|
"share_invite": "邀请相册共享",
|
||||||
"sharing_page_album": "共享相册",
|
"sharing_page_album": "共享相册",
|
||||||
"sharing_page_description": "创建共享相册以与网络中的人共享照片和视频。",
|
"sharing_page_description": "创建共享相册以与网络中的人共享照片和视频。",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"add_to_album_bottom_sheet_added": "添加到 {album}",
|
"add_to_album_bottom_sheet_added": "添加到 {album}",
|
||||||
"add_to_album_bottom_sheet_already_exists": "已在 {album} 中",
|
"add_to_album_bottom_sheet_already_exists": "已在 {album} 中",
|
||||||
"advanced_settings_prefer_remote_subtitle": "在某些设备上,从本地的项目加载缩略图的速度非常慢。\n启用此选项以加载远程\n项目。",
|
"advanced_settings_prefer_remote_subtitle": "在某些设备上,从本地的项目加载缩略图的速度非常慢。\n启用此选项以加载远程。",
|
||||||
"advanced_settings_prefer_remote_title": "优先远程项目",
|
"advanced_settings_prefer_remote_title": "优先远程项目",
|
||||||
"advanced_settings_tile_subtitle": "高级用户设置",
|
"advanced_settings_tile_subtitle": "高级用户设置",
|
||||||
"advanced_settings_tile_title": "高级",
|
"advanced_settings_tile_title": "高级",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"backup_controller_page_info": "备份信息",
|
"backup_controller_page_info": "备份信息",
|
||||||
"backup_controller_page_none_selected": "未选择",
|
"backup_controller_page_none_selected": "未选择",
|
||||||
"backup_controller_page_remainder": "剩余",
|
"backup_controller_page_remainder": "剩余",
|
||||||
"backup_controller_page_remainder_sub": "要从所选内容备份的剩余照片和视频",
|
"backup_controller_page_remainder_sub": "所选数据中尚未备份的数据",
|
||||||
"backup_controller_page_select": "选择",
|
"backup_controller_page_select": "选择",
|
||||||
"backup_controller_page_server_storage": "服务器存储",
|
"backup_controller_page_server_storage": "服务器存储",
|
||||||
"backup_controller_page_start_backup": "开始备份",
|
"backup_controller_page_start_backup": "开始备份",
|
||||||
@@ -89,26 +89,26 @@
|
|||||||
"backup_controller_page_total_sub": "选中相册中的所有不重复的视频和图像",
|
"backup_controller_page_total_sub": "选中相册中的所有不重复的视频和图像",
|
||||||
"backup_controller_page_turn_off": "关闭前台备份",
|
"backup_controller_page_turn_off": "关闭前台备份",
|
||||||
"backup_controller_page_turn_on": "开启前台备份",
|
"backup_controller_page_turn_on": "开启前台备份",
|
||||||
"backup_controller_page_uploading_file_info": "正在上传文件信息",
|
"backup_controller_page_uploading_file_info": "正在上传中的文件信息",
|
||||||
"backup_err_only_album": "不能移除唯一的一个相册",
|
"backup_err_only_album": "不能移除唯一的一个相册",
|
||||||
"backup_info_card_assets": "张",
|
"backup_info_card_assets": "项",
|
||||||
"backup_manual_cancelled": "Cancelled",
|
"backup_manual_cancelled": "已取消",
|
||||||
"backup_manual_failed": "失败",
|
"backup_manual_failed": "失败",
|
||||||
"backup_manual_in_progress": "上传正在进行中,请稍后再试",
|
"backup_manual_in_progress": "上传正在进行中,请稍后再试",
|
||||||
"backup_manual_success": "成功",
|
"backup_manual_success": "成功",
|
||||||
"backup_manual_title": "上传状态",
|
"backup_manual_title": "上传状态",
|
||||||
"cache_settings_album_thumbnails": "图库缩略图({} 张)",
|
"cache_settings_album_thumbnails": "图库缩略图({} 项)",
|
||||||
"cache_settings_clear_cache_button": "清除缓存",
|
"cache_settings_clear_cache_button": "清除缓存",
|
||||||
"cache_settings_clear_cache_button_title": "清除应用缓存。在重新生成缓存之前,将显著影响应用的性能。",
|
"cache_settings_clear_cache_button_title": "清除应用缓存。在重新生成缓存之前,将显著影响应用的性能。",
|
||||||
"cache_settings_image_cache_size": "图像缓存大小({} 张)",
|
"cache_settings_image_cache_size": "图像缓存大小({} 项)",
|
||||||
"cache_settings_statistics_album": "图库缩略图",
|
"cache_settings_statistics_album": "图库缩略图",
|
||||||
"cache_settings_statistics_assets": "{} 张({})",
|
"cache_settings_statistics_assets": "{} 项({})",
|
||||||
"cache_settings_statistics_full": "完整图像",
|
"cache_settings_statistics_full": "完整图像",
|
||||||
"cache_settings_statistics_shared": "共享相册缩略图",
|
"cache_settings_statistics_shared": "共享相册缩略图",
|
||||||
"cache_settings_statistics_thumbnail": "缩略图",
|
"cache_settings_statistics_thumbnail": "缩略图",
|
||||||
"cache_settings_statistics_title": "缓存使用情况",
|
"cache_settings_statistics_title": "缓存使用情况",
|
||||||
"cache_settings_subtitle": "控制 Immich 的缓存行为",
|
"cache_settings_subtitle": "控制 Immich 的缓存行为",
|
||||||
"cache_settings_thumbnail_size": "缩略图缓存大小({} 张)",
|
"cache_settings_thumbnail_size": "缩略图缓存大小({} 项)",
|
||||||
"cache_settings_title": "缓存设置",
|
"cache_settings_title": "缓存设置",
|
||||||
"change_password_form_confirm_password": "确认密码",
|
"change_password_form_confirm_password": "确认密码",
|
||||||
"change_password_form_description": "{firstName} {lastName} 您好,\n\n这是您首次登录系统,或被管理员要求更改密码。\n请在下方输入新密码。",
|
"change_password_form_description": "{firstName} {lastName} 您好,\n\n这是您首次登录系统,或被管理员要求更改密码。\n请在下方输入新密码。",
|
||||||
@@ -139,7 +139,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",
|
||||||
"date_format": "E, LLL d, y • h:mm a",
|
"date_format": "E, LLL d, y • h:mm a",
|
||||||
"delete_dialog_alert": "这些项目将从 Immich 和您的设备中永久删除",
|
"delete_dialog_alert": "这些项目将从 Immich 和 您的设备 中永久删除",
|
||||||
"delete_dialog_cancel": "取消",
|
"delete_dialog_cancel": "取消",
|
||||||
"delete_dialog_ok": "删除",
|
"delete_dialog_ok": "删除",
|
||||||
"delete_dialog_title": "永久删除",
|
"delete_dialog_title": "永久删除",
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
"login_form_button_text": "登录",
|
"login_form_button_text": "登录",
|
||||||
"login_form_email_hint": "youremail@email.com",
|
"login_form_email_hint": "youremail@email.com",
|
||||||
"login_form_endpoint_hint": "http(s)://你的服务器地址:端口/api",
|
"login_form_endpoint_hint": "http(s)://你的服务器地址:端口/api",
|
||||||
"login_form_endpoint_url": "服务器终结点地址",
|
"login_form_endpoint_url": "服务器链接地址",
|
||||||
"login_form_err_http": "请注明 http:// 或 https://",
|
"login_form_err_http": "请注明 http:// 或 https://",
|
||||||
"login_form_err_invalid_email": "无效的电子邮件",
|
"login_form_err_invalid_email": "无效的电子邮件",
|
||||||
"login_form_err_invalid_url": "无效的地址",
|
"login_form_err_invalid_url": "无效的地址",
|
||||||
@@ -270,7 +270,7 @@
|
|||||||
"share_add_photos": "添加项目",
|
"share_add_photos": "添加项目",
|
||||||
"share_add_title": "添加标题",
|
"share_add_title": "添加标题",
|
||||||
"share_create_album": "创建相册",
|
"share_create_album": "创建相册",
|
||||||
"share_dialog_preparing": "这种准备...",
|
"share_dialog_preparing": "正在准备...",
|
||||||
"share_invite": "邀请相册共享",
|
"share_invite": "邀请相册共享",
|
||||||
"sharing_page_album": "共享相册",
|
"sharing_page_album": "共享相册",
|
||||||
"sharing_page_description": "创建共享相册以与网络中的人共享照片和视频。",
|
"sharing_page_description": "创建共享相册以与网络中的人共享照片和视频。",
|
||||||
|
|||||||
@@ -169,4 +169,4 @@ SPEC CHECKSUMS:
|
|||||||
|
|
||||||
PODFILE CHECKSUM: 599d8aeb73728400c15364e734525722250a5382
|
PODFILE CHECKSUM: 599d8aeb73728400c15364e734525722250a5382
|
||||||
|
|
||||||
COCOAPODS: 1.11.3
|
COCOAPODS: 1.12.1
|
||||||
|
|||||||
@@ -379,7 +379,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 113;
|
CURRENT_PROJECT_VERSION = 116;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -515,7 +515,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 113;
|
CURRENT_PROJECT_VERSION = 116;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -543,7 +543,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 113;
|
CURRENT_PROJECT_VERSION = 116;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|||||||
@@ -59,11 +59,11 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.73.0</string>
|
<string>1.76.0</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>113</string>
|
<string>116</string>
|
||||||
<key>FLTEnableImpeller</key>
|
<key>FLTEnableImpeller</key>
|
||||||
<true />
|
<true />
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ platform :ios do
|
|||||||
desc "iOS Beta"
|
desc "iOS Beta"
|
||||||
lane :beta do
|
lane :beta do
|
||||||
increment_version_number(
|
increment_version_number(
|
||||||
version_number: "1.76.0"
|
version_number: "1.78.0"
|
||||||
)
|
)
|
||||||
increment_build_number(
|
increment_build_number(
|
||||||
build_number: latest_testflight_build_number + 1,
|
build_number: latest_testflight_build_number + 1,
|
||||||
|
|||||||
@@ -5,32 +5,32 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000187">
|
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000243">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="2.403882">
|
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="2.611762">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="5.068392">
|
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="6.937008">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="1.988079">
|
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="2.740416">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="4: build_app" time="96.47923">
|
<testcase classname="fastlane.lanes" name="4: build_app" time="93.625943">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="57.517755">
|
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="62.107671">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import 'package:immich_mobile/shared/services/immich_logger.service.dart';
|
|||||||
import 'package:immich_mobile/shared/services/local_notification.service.dart';
|
import 'package:immich_mobile/shared/services/local_notification.service.dart';
|
||||||
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
|
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
|
||||||
import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
|
import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
|
||||||
|
import 'package:immich_mobile/utils/http_ssl_cert_override.dart';
|
||||||
import 'package:immich_mobile/utils/immich_app_theme.dart';
|
import 'package:immich_mobile/utils/immich_app_theme.dart';
|
||||||
import 'package:immich_mobile/utils/migration.dart';
|
import 'package:immich_mobile/utils/migration.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
@@ -41,6 +42,7 @@ void main() async {
|
|||||||
final db = await loadDb();
|
final db = await loadDb();
|
||||||
await initApp();
|
await initApp();
|
||||||
await migrateDatabaseIfNeeded(db);
|
await migrateDatabaseIfNeeded(db);
|
||||||
|
HttpOverrides.global = HttpSSLCertOverride();
|
||||||
runApp(getMainWidget(db));
|
runApp(getMainWidget(db));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/modules/asset_viewer/ui/description_input.dart';
|
import 'package:immich_mobile/modules/asset_viewer/ui/description_input.dart';
|
||||||
import 'package:immich_mobile/modules/map/ui/map_thumbnail.dart';
|
import 'package:immich_mobile/modules/map/ui/map_thumbnail.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/exif_info.dart';
|
||||||
|
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||||
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
|
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:immich_mobile/utils/bytes_units.dart';
|
import 'package:immich_mobile/utils/bytes_units.dart';
|
||||||
@@ -17,8 +19,12 @@ class ExifBottomSheet extends HookConsumerWidget {
|
|||||||
|
|
||||||
const ExifBottomSheet({Key? key, required this.asset}) : super(key: key);
|
const ExifBottomSheet({Key? key, required this.asset}) : super(key: key);
|
||||||
|
|
||||||
bool get hasCoordinates =>
|
bool hasCoordinates(ExifInfo? exifInfo) =>
|
||||||
asset.exifInfo?.latitude != null && asset.exifInfo?.longitude != null;
|
exifInfo != null &&
|
||||||
|
exifInfo.latitude != null &&
|
||||||
|
exifInfo.longitude != null &&
|
||||||
|
exifInfo.latitude != 0 &&
|
||||||
|
exifInfo.longitude != 0;
|
||||||
|
|
||||||
String get formattedDateTime {
|
String get formattedDateTime {
|
||||||
final fileCreatedAt = asset.fileCreatedAt.toLocal();
|
final fileCreatedAt = asset.fileCreatedAt.toLocal();
|
||||||
@@ -28,13 +34,13 @@ class ExifBottomSheet extends HookConsumerWidget {
|
|||||||
return '$date • $time';
|
return '$date • $time';
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uri?> _createCoordinatesUri() async {
|
Future<Uri?> _createCoordinatesUri(ExifInfo? exifInfo) async {
|
||||||
if (!hasCoordinates) {
|
if (!hasCoordinates(exifInfo)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
double latitude = asset.exifInfo!.latitude!;
|
final double latitude = exifInfo!.latitude!;
|
||||||
double longitude = asset.exifInfo!.longitude!;
|
final double longitude = exifInfo.longitude!;
|
||||||
|
|
||||||
const zoomLevel = 16;
|
const zoomLevel = 16;
|
||||||
|
|
||||||
@@ -72,7 +78,8 @@ class ExifBottomSheet extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final exifInfo = asset.exifInfo;
|
final assetWithExif = ref.watch(assetDetailProvider(asset));
|
||||||
|
final exifInfo = (assetWithExif.value ?? asset).exifInfo;
|
||||||
var isDarkTheme = Theme.of(context).brightness == Brightness.dark;
|
var isDarkTheme = Theme.of(context).brightness == Brightness.dark;
|
||||||
var textColor = isDarkTheme ? Colors.white : Colors.black;
|
var textColor = isDarkTheme ? Colors.white : Colors.black;
|
||||||
|
|
||||||
@@ -101,7 +108,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
onTap: (tapPosition, latLong) async {
|
onTap: (tapPosition, latLong) async {
|
||||||
Uri? uri = await _createCoordinatesUri();
|
Uri? uri = await _createCoordinatesUri(exifInfo);
|
||||||
|
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
return;
|
return;
|
||||||
@@ -124,7 +131,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
|||||||
? formatBytes(a.exifInfo!.fileSize!)
|
? formatBytes(a.exifInfo!.fileSize!)
|
||||||
: "";
|
: "";
|
||||||
String text = resolution + fileSize;
|
String text = resolution + fileSize;
|
||||||
return text.isEmpty ? null : Text(text);
|
return text.isNotEmpty ? text : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildDragHeader() {
|
buildDragHeader() {
|
||||||
@@ -143,7 +150,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
|||||||
|
|
||||||
buildLocation() {
|
buildLocation() {
|
||||||
// Guard no lat/lng
|
// Guard no lat/lng
|
||||||
if (!hasCoordinates) {
|
if (!hasCoordinates(exifInfo)) {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +215,58 @@ class ExifBottomSheet extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildImageProperties() {
|
||||||
|
// Helper to create the ListTile and avoid repeating code
|
||||||
|
createImagePropertiesListStyle(title, subtitle) => ListTile(
|
||||||
|
contentPadding: const EdgeInsets.all(0),
|
||||||
|
dense: true,
|
||||||
|
leading: Icon(
|
||||||
|
Icons.image,
|
||||||
|
color: textColor.withAlpha(200),
|
||||||
|
),
|
||||||
|
titleAlignment: ListTileTitleAlignment.center,
|
||||||
|
title: Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: textColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: subtitle,
|
||||||
|
);
|
||||||
|
|
||||||
|
final imgSizeString = buildSizeText(asset);
|
||||||
|
|
||||||
|
if (imgSizeString == null && asset.fileName.isNotEmpty) {
|
||||||
|
// There is only filename
|
||||||
|
return createImagePropertiesListStyle(
|
||||||
|
asset.fileName,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
} else if (imgSizeString != null && asset.fileName.isNotEmpty) {
|
||||||
|
// There is both filename and size information
|
||||||
|
return createImagePropertiesListStyle(
|
||||||
|
asset.fileName,
|
||||||
|
Text(imgSizeString),
|
||||||
|
);
|
||||||
|
} else if (imgSizeString != null && asset.fileName.isEmpty) {
|
||||||
|
// There is only size information
|
||||||
|
return createImagePropertiesListStyle(
|
||||||
|
imgSizeString,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildDetail() {
|
buildDetail() {
|
||||||
|
final imgProperties = buildImageProperties();
|
||||||
|
|
||||||
|
// There are no details
|
||||||
|
if (imgProperties == null &&
|
||||||
|
(exifInfo == null || exifInfo.make == null)) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -223,22 +281,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
).tr(),
|
).tr(),
|
||||||
),
|
),
|
||||||
ListTile(
|
if (imgProperties != null) imgProperties,
|
||||||
contentPadding: const EdgeInsets.all(0),
|
|
||||||
dense: true,
|
|
||||||
leading: Icon(
|
|
||||||
Icons.image,
|
|
||||||
color: textColor.withAlpha(200),
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
asset.fileName,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: textColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: buildSizeText(asset),
|
|
||||||
),
|
|
||||||
if (exifInfo?.make != null)
|
if (exifInfo?.make != null)
|
||||||
ListTile(
|
ListTile(
|
||||||
contentPadding: const EdgeInsets.all(0),
|
contentPadding: const EdgeInsets.all(0),
|
||||||
@@ -294,7 +337,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: hasCoordinates ? 5 : 0,
|
flex: hasCoordinates(exifInfo) ? 5 : 0,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(right: 8.0),
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
child: buildLocation(),
|
child: buildLocation(),
|
||||||
@@ -324,7 +367,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
|||||||
if (asset.isRemote) DescriptionInput(asset: asset),
|
if (asset.isRemote) DescriptionInput(asset: asset),
|
||||||
const SizedBox(height: 8.0),
|
const SizedBox(height: 8.0),
|
||||||
buildLocation(),
|
buildLocation(),
|
||||||
SizedBox(height: hasCoordinates ? 16.0 : 0.0),
|
SizedBox(height: hasCoordinates(exifInfo) ? 16.0 : 0.0),
|
||||||
buildDetail(),
|
buildDetail(),
|
||||||
const SizedBox(height: 50),
|
const SizedBox(height: 50),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -64,9 +64,8 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
final authToken = 'Bearer ${Store.get(StoreKey.accessToken)}';
|
final authToken = 'Bearer ${Store.get(StoreKey.accessToken)}';
|
||||||
final currentIndex = useState(initialIndex);
|
final currentIndex = useState(initialIndex);
|
||||||
final currentAsset = loadAsset(currentIndex.value);
|
final currentAsset = loadAsset(currentIndex.value);
|
||||||
final watchedAsset = ref.watch(assetDetailProvider(currentAsset));
|
|
||||||
|
|
||||||
Asset asset() => watchedAsset.value ?? currentAsset;
|
Asset asset() => currentAsset;
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
@@ -194,7 +193,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||||
),
|
),
|
||||||
child: ExifBottomSheet(asset: asset()),
|
child: ExifBottomSheet(asset: currentAsset),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -459,6 +459,7 @@ class BackgroundService {
|
|||||||
notifySingleProgress ? _onProgress : (sent, total) {},
|
notifySingleProgress ? _onProgress : (sent, total) {},
|
||||||
notifySingleProgress ? _onSetCurrentBackupAsset : (asset) {},
|
notifySingleProgress ? _onSetCurrentBackupAsset : (asset) {},
|
||||||
_onBackupError,
|
_onBackupError,
|
||||||
|
sortAssets: true,
|
||||||
);
|
);
|
||||||
if (!ok && !_cancellationToken!.isCancelled) {
|
if (!ok && !_cancellationToken!.isCancelled) {
|
||||||
_showErrorNotification(
|
_showErrorNotification(
|
||||||
|
|||||||
@@ -202,8 +202,9 @@ class BackupService {
|
|||||||
Function(String, String, bool) uploadSuccessCb,
|
Function(String, String, bool) uploadSuccessCb,
|
||||||
Function(int, int) uploadProgressCb,
|
Function(int, int) uploadProgressCb,
|
||||||
Function(CurrentUploadAsset) setCurrentUploadAssetCb,
|
Function(CurrentUploadAsset) setCurrentUploadAssetCb,
|
||||||
Function(ErrorUploadAsset) errorCb,
|
Function(ErrorUploadAsset) errorCb, {
|
||||||
) async {
|
bool sortAssets = false,
|
||||||
|
}) async {
|
||||||
if (Platform.isAndroid &&
|
if (Platform.isAndroid &&
|
||||||
!(await Permission.accessMediaLocation.status).isGranted) {
|
!(await Permission.accessMediaLocation.status).isGranted) {
|
||||||
// double check that permission is granted here, to guard against
|
// double check that permission is granted here, to guard against
|
||||||
@@ -218,7 +219,22 @@ class BackupService {
|
|||||||
bool anyErrors = false;
|
bool anyErrors = false;
|
||||||
final List<String> duplicatedAssetIds = [];
|
final List<String> duplicatedAssetIds = [];
|
||||||
|
|
||||||
for (var entity in assetList) {
|
// DON'T KNOW WHY BUT THIS HELPS BACKGROUND BACKUP TO WORK ON IOS
|
||||||
|
await PhotoManager.requestPermissionExtend();
|
||||||
|
|
||||||
|
List<AssetEntity> assetsToUpload = sortAssets
|
||||||
|
// Upload images before video assets
|
||||||
|
// these are further sorted by using their creation date
|
||||||
|
? assetList.sorted(
|
||||||
|
(a, b) {
|
||||||
|
final cmp = a.typeInt - b.typeInt;
|
||||||
|
if (cmp != 0) return cmp;
|
||||||
|
return a.createDateTime.compareTo(b.createDateTime);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: assetList.toList();
|
||||||
|
|
||||||
|
for (var entity in assetsToUpload) {
|
||||||
try {
|
try {
|
||||||
if (entity.type == AssetType.video) {
|
if (entity.type == AssetType.video) {
|
||||||
file = await entity.originFile;
|
file = await entity.originFile;
|
||||||
@@ -248,7 +264,8 @@ class BackupService {
|
|||||||
|
|
||||||
req.fields['deviceAssetId'] = entity.id;
|
req.fields['deviceAssetId'] = entity.id;
|
||||||
req.fields['deviceId'] = deviceId;
|
req.fields['deviceId'] = deviceId;
|
||||||
req.fields['fileCreatedAt'] = entity.createDateTime.toUtc().toIso8601String();
|
req.fields['fileCreatedAt'] =
|
||||||
|
entity.createDateTime.toUtc().toIso8601String();
|
||||||
req.fields['fileModifiedAt'] =
|
req.fields['fileModifiedAt'] =
|
||||||
entity.modifiedDateTime.toUtc().toIso8601String();
|
entity.modifiedDateTime.toUtc().toIso8601String();
|
||||||
req.fields['isFavorite'] = entity.isFavorite.toString();
|
req.fields['isFavorite'] = entity.isFavorite.toString();
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/ui/immich_image.dart';
|
import 'package:immich_mobile/shared/ui/immich_image.dart';
|
||||||
import 'package:immich_mobile/utils/storage_indicator.dart';
|
import 'package:immich_mobile/utils/storage_indicator.dart';
|
||||||
|
|
||||||
class ThumbnailImage extends HookConsumerWidget {
|
class ThumbnailImage extends StatelessWidget {
|
||||||
final Asset asset;
|
final Asset asset;
|
||||||
final int index;
|
final int index;
|
||||||
final Asset Function(int index) loadAsset;
|
final Asset Function(int index) loadAsset;
|
||||||
@@ -36,7 +35,7 @@ class ThumbnailImage extends HookConsumerWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context) {
|
||||||
final isDarkTheme = Theme.of(context).brightness == Brightness.dark;
|
final isDarkTheme = Theme.of(context).brightness == Brightness.dark;
|
||||||
final assetContainerColor =
|
final assetContainerColor =
|
||||||
isDarkTheme ? Colors.blueGrey : Theme.of(context).primaryColorLight;
|
isDarkTheme ? Colors.blueGrey : Theme.of(context).primaryColorLight;
|
||||||
@@ -61,8 +60,8 @@ class ThumbnailImage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildImage(Asset asset) {
|
Widget buildImage() {
|
||||||
var image = SizedBox(
|
final image = SizedBox(
|
||||||
width: 300,
|
width: 300,
|
||||||
height: 300,
|
height: 300,
|
||||||
child: Hero(
|
child: Hero(
|
||||||
@@ -133,7 +132,7 @@ class ThumbnailImage extends HookConsumerWidget {
|
|||||||
)
|
)
|
||||||
: const Border(),
|
: const Border(),
|
||||||
),
|
),
|
||||||
child: buildImage(asset),
|
child: buildImage(),
|
||||||
),
|
),
|
||||||
if (multiselectEnabled)
|
if (multiselectEnabled)
|
||||||
Padding(
|
Padding(
|
||||||
|
|||||||
@@ -113,7 +113,17 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
|||||||
Store.delete(StoreKey.accessToken),
|
Store.delete(StoreKey.accessToken),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
state = state.copyWith(isAuthenticated: false);
|
state = state.copyWith(
|
||||||
|
deviceId: "",
|
||||||
|
userId: "",
|
||||||
|
userEmail: "",
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
profileImagePath: '',
|
||||||
|
isAdmin: false,
|
||||||
|
shouldChangePassword: false,
|
||||||
|
isAuthenticated: false,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.severe("Error logging out $e");
|
log.severe("Error logging out $e");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
||||||
import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart';
|
import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart';
|
||||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
|
||||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
||||||
|
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
||||||
|
|
||||||
class ChangePasswordForm extends HookConsumerWidget {
|
class ChangePasswordForm extends HookConsumerWidget {
|
||||||
const ChangePasswordForm({Key? key}) : super(key: key);
|
const ChangePasswordForm({Key? key}) : super(key: key);
|
||||||
@@ -84,14 +85,35 @@ class ChangePasswordForm extends HookConsumerWidget {
|
|||||||
.read(manualUploadProvider.notifier)
|
.read(manualUploadProvider.notifier)
|
||||||
.cancelBackup();
|
.cancelBackup();
|
||||||
ref.read(backupProvider.notifier).cancelBackup();
|
ref.read(backupProvider.notifier).cancelBackup();
|
||||||
ref.read(assetProvider.notifier).clearAllAsset();
|
await ref
|
||||||
|
.read(assetProvider.notifier)
|
||||||
|
.clearAllAsset();
|
||||||
ref.read(websocketProvider.notifier).disconnect();
|
ref.read(websocketProvider.notifier).disconnect();
|
||||||
|
|
||||||
AutoRouter.of(context).replace(const LoginRoute());
|
AutoRouter.of(context).navigateBack();
|
||||||
|
|
||||||
|
ImmichToast.show(
|
||||||
|
context: context,
|
||||||
|
msg: "login_password_changed_success".tr(),
|
||||||
|
toastType: ToastType.success,
|
||||||
|
gravity: ToastGravity.TOP,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ImmichToast.show(
|
||||||
|
context: context,
|
||||||
|
msg: "login_password_changed_error".tr(),
|
||||||
|
toastType: ToastType.error,
|
||||||
|
gravity: ToastGravity.TOP,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
TextButton.icon(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () => AutoRouter.of(context).navigateBack(),
|
||||||
|
label: const Text('Back'),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:io';
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -88,6 +89,16 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
isPasswordLoginEnable.value = true;
|
isPasswordLoginEnable.value = true;
|
||||||
isLoadingServer.value = false;
|
isLoadingServer.value = false;
|
||||||
return false;
|
return false;
|
||||||
|
} on HandshakeException {
|
||||||
|
ImmichToast.show(
|
||||||
|
context: context,
|
||||||
|
msg: 'login_form_handshake_exception'.tr(),
|
||||||
|
toastType: ToastType.error,
|
||||||
|
);
|
||||||
|
isOauthEnable.value = false;
|
||||||
|
isPasswordLoginEnable.value = true;
|
||||||
|
isLoadingServer.value = false;
|
||||||
|
return false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ImmichToast.show(
|
ImmichToast.show(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -226,6 +237,7 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildSelectServer() {
|
buildSelectServer() {
|
||||||
|
const buttonRadius = 25.0;
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
@@ -235,24 +247,51 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
onSubmit: getServerLoginCredential,
|
onSubmit: getServerLoginCredential,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 18),
|
const SizedBox(height: 18),
|
||||||
ElevatedButton.icon(
|
Row(
|
||||||
style: ElevatedButton.styleFrom(
|
children: [
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
Expanded(
|
||||||
),
|
child: ElevatedButton.icon(
|
||||||
onPressed: isLoadingServer.value ? null : getServerLoginCredential,
|
style: ElevatedButton.styleFrom(
|
||||||
icon: const Icon(Icons.arrow_forward_rounded),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
label: const Text(
|
shape: const RoundedRectangleBorder(
|
||||||
'login_form_next_button',
|
borderRadius: BorderRadius.only(
|
||||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
topLeft: Radius.circular(buttonRadius),
|
||||||
).tr(),
|
bottomLeft: Radius.circular(buttonRadius),
|
||||||
),
|
),
|
||||||
if (isLoadingServer.value)
|
),
|
||||||
const Padding(
|
),
|
||||||
padding: EdgeInsets.only(top: 18.0),
|
onPressed: () =>
|
||||||
child: Center(
|
AutoRouter.of(context).push(const SettingsRoute()),
|
||||||
child: CircularProgressIndicator(),
|
icon: const Icon(Icons.settings_rounded),
|
||||||
|
label: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 1),
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topRight: Radius.circular(buttonRadius),
|
||||||
|
bottomRight: Radius.circular(buttonRadius),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed:
|
||||||
|
isLoadingServer.value ? null : getServerLoginCredential,
|
||||||
|
icon: const Icon(Icons.arrow_forward_rounded),
|
||||||
|
label: const Text(
|
||||||
|
'login_form_next_button',
|
||||||
|
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 18),
|
||||||
|
if (isLoadingServer.value) const LoadingIcon(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -285,18 +324,7 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
// Note: This used to have an AnimatedSwitcher, but was removed
|
// Note: This used to have an AnimatedSwitcher, but was removed
|
||||||
// because of https://github.com/flutter/flutter/issues/120874
|
// because of https://github.com/flutter/flutter/issues/120874
|
||||||
isLoading.value
|
isLoading.value
|
||||||
? const Padding(
|
? const LoadingIcon()
|
||||||
padding: EdgeInsets.only(top: 18.0),
|
|
||||||
child: SizedBox(
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
child: FittedBox(
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Column(
|
: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -572,3 +600,23 @@ class OAuthLoginButton extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LoadingIcon extends StatelessWidget {
|
||||||
|
const LoadingIcon({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Padding(
|
||||||
|
padding: EdgeInsets.only(top: 18.0),
|
||||||
|
child: SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: FittedBox(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import 'package:immich_mobile/utils/image_url_builder.dart';
|
|||||||
|
|
||||||
class AssetMarkerIcon extends StatelessWidget {
|
class AssetMarkerIcon extends StatelessWidget {
|
||||||
const AssetMarkerIcon({
|
const AssetMarkerIcon({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.id,
|
required this.id,
|
||||||
this.isDarkTheme = false,
|
this.isDarkTheme = false,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
final bool isDarkTheme;
|
final bool isDarkTheme;
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ class MapAppBar extends HookWidget implements PreferredSizeWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 30),
|
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top + 15),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -41,7 +42,7 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
|||||||
// Non-State variables
|
// Non-State variables
|
||||||
bool userTappedOnMap = false;
|
bool userTappedOnMap = false;
|
||||||
RenderList? _cachedRenderList;
|
RenderList? _cachedRenderList;
|
||||||
int lastAssetOffsetInSheet = -1;
|
int assetOffsetInSheet = -1;
|
||||||
late final DraggableScrollableController bottomSheetController;
|
late final DraggableScrollableController bottomSheetController;
|
||||||
late final Debounce debounce;
|
late final Debounce debounce;
|
||||||
|
|
||||||
@@ -50,14 +51,16 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
bottomSheetController = DraggableScrollableController();
|
bottomSheetController = DraggableScrollableController();
|
||||||
debounce = Debounce(
|
debounce = Debounce(
|
||||||
const Duration(milliseconds: 200),
|
const Duration(milliseconds: 100),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||||
double maxHeight = MediaQuery.of(context).size.height;
|
final bottomPadding =
|
||||||
|
Platform.isAndroid ? MediaQuery.of(context).padding.bottom - 10 : 0.0;
|
||||||
|
final maxHeight = MediaQuery.of(context).size.height - bottomPadding;
|
||||||
final isSheetScrolled = useState(false);
|
final isSheetScrolled = useState(false);
|
||||||
final isSheetExpanded = useState(false);
|
final isSheetExpanded = useState(false);
|
||||||
final assetsInBound = useState(<Asset>[]);
|
final assetsInBound = useState(<Asset>[]);
|
||||||
@@ -68,7 +71,7 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
|||||||
assetsInBound.value = event.assets;
|
assetsInBound.value = event.assets;
|
||||||
} else if (event is MapPageOnTapEvent) {
|
} else if (event is MapPageOnTapEvent) {
|
||||||
userTappedOnMap = true;
|
userTappedOnMap = true;
|
||||||
lastAssetOffsetInSheet = -1;
|
assetOffsetInSheet = -1;
|
||||||
bottomSheetController.animateTo(
|
bottomSheetController.animateTo(
|
||||||
0.1,
|
0.1,
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
@@ -98,8 +101,8 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
|||||||
columnOffset = columnOffset < renderElement.totalCount
|
columnOffset = columnOffset < renderElement.totalCount
|
||||||
? columnOffset
|
? columnOffset
|
||||||
: renderElement.totalCount - 1;
|
: renderElement.totalCount - 1;
|
||||||
lastAssetOffsetInSheet = rowOffset + columnOffset;
|
assetOffsetInSheet = rowOffset + columnOffset;
|
||||||
final asset = _cachedRenderList?.allAssets?[lastAssetOffsetInSheet];
|
final asset = _cachedRenderList?.allAssets?[assetOffsetInSheet];
|
||||||
userTappedOnMap = false;
|
userTappedOnMap = false;
|
||||||
if (!userTappedOnMap && isSheetExpanded.value) {
|
if (!userTappedOnMap && isSheetExpanded.value) {
|
||||||
widget.bottomSheetEventSC.add(
|
widget.bottomSheetEventSC.add(
|
||||||
@@ -162,10 +165,10 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onTapMapButton() {
|
void onTapMapButton() {
|
||||||
if (lastAssetOffsetInSheet != -1) {
|
if (assetOffsetInSheet != -1) {
|
||||||
widget.bottomSheetEventSC.add(
|
widget.bottomSheetEventSC.add(
|
||||||
MapPageZoomToAsset(
|
MapPageZoomToAsset(
|
||||||
_cachedRenderList?.allAssets?[lastAssetOffsetInSheet],
|
_cachedRenderList?.allAssets?[assetOffsetInSheet],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -176,7 +179,7 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
|||||||
? "${assetsInBound.value.length} photo${assetsInBound.value.length > 1 ? "s" : ""}"
|
? "${assetsInBound.value.length} photo${assetsInBound.value.length > 1 ? "s" : ""}"
|
||||||
: "map_no_assets_in_bounds".tr();
|
: "map_no_assets_in_bounds".tr();
|
||||||
final dragHandle = Container(
|
final dragHandle = Container(
|
||||||
height: 75,
|
height: 60,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
|
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
|
||||||
@@ -187,9 +190,9 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 5),
|
||||||
const CustomDraggingHandle(),
|
const CustomDraggingHandle(),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 15),
|
||||||
Text(
|
Text(
|
||||||
textToDisplay,
|
textToDisplay,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -199,6 +202,7 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Divider(
|
Divider(
|
||||||
|
height: 10,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.displayLarge
|
.displayLarge
|
||||||
@@ -226,6 +230,7 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
|||||||
);
|
);
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
|
physics: const ClampingScrollPhysics(),
|
||||||
child: dragHandle,
|
child: dragHandle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -238,118 +243,125 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
|||||||
if (!sheetExtended) {
|
if (!sheetExtended) {
|
||||||
// reset state
|
// reset state
|
||||||
userTappedOnMap = false;
|
userTappedOnMap = false;
|
||||||
lastAssetOffsetInSheet = -1;
|
assetOffsetInSheet = -1;
|
||||||
isSheetScrolled.value = false;
|
isSheetScrolled.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
child: Stack(
|
child: Padding(
|
||||||
children: [
|
padding: EdgeInsets.only(
|
||||||
DraggableScrollableSheet(
|
bottom: bottomPadding,
|
||||||
controller: bottomSheetController,
|
),
|
||||||
initialChildSize: 0.1,
|
child: Stack(
|
||||||
minChildSize: 0.1,
|
children: [
|
||||||
maxChildSize: 0.55,
|
DraggableScrollableSheet(
|
||||||
snap: true,
|
controller: bottomSheetController,
|
||||||
builder: (
|
initialChildSize: 0.1,
|
||||||
BuildContext context,
|
minChildSize: 0.1,
|
||||||
ScrollController scrollController,
|
maxChildSize: 0.55,
|
||||||
) {
|
snap: true,
|
||||||
return Card(
|
builder: (
|
||||||
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
|
BuildContext context,
|
||||||
surfaceTintColor: Colors.transparent,
|
ScrollController scrollController,
|
||||||
elevation: 18.0,
|
) {
|
||||||
margin: const EdgeInsets.all(0),
|
return Card(
|
||||||
child: Column(
|
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
|
||||||
children: [
|
surfaceTintColor: Colors.transparent,
|
||||||
buildDragHandle(scrollController),
|
elevation: 18.0,
|
||||||
if (isSheetExpanded.value && assetsInBound.value.isNotEmpty)
|
margin: const EdgeInsets.all(0),
|
||||||
ref
|
child: Column(
|
||||||
.watch(
|
children: [
|
||||||
renderListProvider(
|
buildDragHandle(scrollController),
|
||||||
assetsInBound.value,
|
if (isSheetExpanded.value &&
|
||||||
),
|
assetsInBound.value.isNotEmpty)
|
||||||
)
|
ref
|
||||||
.when(
|
.watch(
|
||||||
data: (renderList) {
|
renderListProvider(
|
||||||
_cachedRenderList = renderList;
|
assetsInBound.value,
|
||||||
final assetGrid = ImmichAssetGrid(
|
),
|
||||||
shrinkWrap: true,
|
)
|
||||||
renderList: renderList,
|
.when(
|
||||||
showDragScroll: false,
|
data: (renderList) {
|
||||||
selectionActive: widget.selectionEnabled,
|
_cachedRenderList = renderList;
|
||||||
showMultiSelectIndicator: false,
|
final assetGrid = ImmichAssetGrid(
|
||||||
listener: widget.selectionlistener,
|
shrinkWrap: true,
|
||||||
visibleItemsListener: visibleItemsListener,
|
renderList: renderList,
|
||||||
);
|
showDragScroll: false,
|
||||||
|
selectionActive: widget.selectionEnabled,
|
||||||
|
showMultiSelectIndicator: false,
|
||||||
|
listener: widget.selectionlistener,
|
||||||
|
visibleItemsListener: visibleItemsListener,
|
||||||
|
);
|
||||||
|
|
||||||
return Expanded(child: assetGrid);
|
return Expanded(child: assetGrid);
|
||||||
},
|
},
|
||||||
error: (error, stackTrace) {
|
error: (error, stackTrace) {
|
||||||
log.warning(
|
log.warning(
|
||||||
"Cannot get assets in the current map bounds ${error.toString()}",
|
"Cannot get assets in the current map bounds ${error.toString()}",
|
||||||
error,
|
error,
|
||||||
stackTrace,
|
stackTrace,
|
||||||
);
|
);
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
},
|
},
|
||||||
loading: () => const SizedBox.shrink(),
|
loading: () => const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
if (isSheetExpanded.value && assetsInBound.value.isEmpty)
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: buildNoPhotosWidget(),
|
||||||
),
|
),
|
||||||
if (isSheetExpanded.value && assetsInBound.value.isEmpty)
|
|
||||||
Expanded(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: buildNoPhotosWidget(),
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: maxHeight * currentExtend.value,
|
||||||
|
left: 0,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => launchUrl(
|
||||||
|
Uri.parse('https://openstreetmap.org/copyright'),
|
||||||
),
|
),
|
||||||
);
|
child: ColoredBox(
|
||||||
},
|
color: (widget.isDarkTheme
|
||||||
),
|
? Colors.grey[900]
|
||||||
Positioned(
|
: Colors.grey[100])!,
|
||||||
bottom: maxHeight * currentExtend.value,
|
child: Padding(
|
||||||
left: 0,
|
padding: const EdgeInsets.all(3),
|
||||||
child: GestureDetector(
|
child: Text(
|
||||||
onTap: () => launchUrl(
|
'© OpenStreetMap contributors',
|
||||||
Uri.parse('https://openstreetmap.org/copyright'),
|
style: TextStyle(
|
||||||
),
|
fontSize: 6,
|
||||||
child: ColoredBox(
|
color: !widget.isDarkTheme
|
||||||
color:
|
? Colors.grey[900]
|
||||||
(widget.isDarkTheme ? Colors.grey[900] : Colors.grey[100])!,
|
: Colors.grey[100],
|
||||||
child: Padding(
|
),
|
||||||
padding: const EdgeInsets.all(3),
|
|
||||||
child: Text(
|
|
||||||
'© OpenStreetMap contributors',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 6,
|
|
||||||
color: !widget.isDarkTheme
|
|
||||||
? Colors.grey[900]
|
|
||||||
: Colors.grey[100],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Positioned(
|
||||||
Positioned(
|
bottom: maxHeight * (0.14 + (currentExtend.value - 0.1)),
|
||||||
bottom: maxHeight * (0.14 + (currentExtend.value - 0.1)),
|
right: 15,
|
||||||
right: 15,
|
child: ElevatedButton(
|
||||||
child: ElevatedButton(
|
onPressed: () => widget.bottomSheetEventSC
|
||||||
onPressed: () =>
|
.add(const MapPageZoomToLocation()),
|
||||||
widget.bottomSheetEventSC.add(const MapPageZoomToLocation()),
|
style: ElevatedButton.styleFrom(
|
||||||
style: ElevatedButton.styleFrom(
|
shape: const CircleBorder(),
|
||||||
shape: const CircleBorder(),
|
padding: const EdgeInsets.all(12),
|
||||||
padding: const EdgeInsets.all(12),
|
),
|
||||||
),
|
child: const Icon(
|
||||||
child: const Icon(
|
Icons.my_location,
|
||||||
Icons.my_location,
|
size: 22,
|
||||||
size: 22,
|
fill: 1,
|
||||||
fill: 1,
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,14 +166,15 @@ class MapPageState extends ConsumerState<MapPage> {
|
|||||||
final mapMarker = mapMarkerData.value
|
final mapMarker = mapMarkerData.value
|
||||||
.firstWhereOrNull((e) => e.asset.id == assetInBottomSheet.id);
|
.firstWhereOrNull((e) => e.asset.id == assetInBottomSheet.id);
|
||||||
if (mapMarker != null) {
|
if (mapMarker != null) {
|
||||||
|
const zoomLevel = 16.0;
|
||||||
LatLng? newCenter = mapController.centerBoundsWithPadding(
|
LatLng? newCenter = mapController.centerBoundsWithPadding(
|
||||||
mapMarker.point,
|
mapMarker.point,
|
||||||
const Offset(0, -120),
|
const Offset(0, -120),
|
||||||
zoomLevel: 6,
|
zoomLevel: zoomLevel,
|
||||||
);
|
);
|
||||||
if (newCenter != null) {
|
if (newCenter != null) {
|
||||||
forceAssetUpdate = true;
|
forceAssetUpdate = true;
|
||||||
mapController.move(newCenter, 6);
|
mapController.move(newCenter, zoomLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -385,6 +386,7 @@ class MapPageState extends ConsumerState<MapPage> {
|
|||||||
builder: (ctx) => GestureDetector(
|
builder: (ctx) => GestureDetector(
|
||||||
onTap: () => openAssetInViewer(closestAssetMarker.value!.asset),
|
onTap: () => openAssetInViewer(closestAssetMarker.value!.asset),
|
||||||
child: AssetMarkerIcon(
|
child: AssetMarkerIcon(
|
||||||
|
key: Key(closestAssetMarker.value!.asset.remoteId!),
|
||||||
isDarkTheme: isDarkTheme,
|
isDarkTheme: isDarkTheme,
|
||||||
id: closestAssetMarker.value!.asset.remoteId!,
|
id: closestAssetMarker.value!.asset.remoteId!,
|
||||||
),
|
),
|
||||||
@@ -421,8 +423,15 @@ class MapPageState extends ConsumerState<MapPage> {
|
|||||||
|
|
||||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
value: SystemUiOverlayStyle(
|
value: SystemUiOverlayStyle(
|
||||||
statusBarColor: Colors.black.withOpacity(0.5),
|
statusBarColor:
|
||||||
statusBarIconBrightness: Brightness.light,
|
(isDarkTheme ? Colors.black : Colors.white).withOpacity(0.5),
|
||||||
|
statusBarIconBrightness:
|
||||||
|
isDarkTheme ? Brightness.light : Brightness.dark,
|
||||||
|
systemNavigationBarColor:
|
||||||
|
isDarkTheme ? Colors.grey[900] : Colors.grey[100],
|
||||||
|
systemNavigationBarIconBrightness:
|
||||||
|
isDarkTheme ? Brightness.light : Brightness.dark,
|
||||||
|
systemNavigationBarDividerColor: Colors.transparent,
|
||||||
),
|
),
|
||||||
child: Theme(
|
child: Theme(
|
||||||
// Override app theme based on map theme
|
// Override app theme based on map theme
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
|
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
|
||||||
import 'package:immich_mobile/shared/models/user.dart';
|
import 'package:immich_mobile/shared/models/user.dart';
|
||||||
@@ -14,6 +15,14 @@ class PartnerDetailPage extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final assets = ref.watch(assetsProvider(partner.isarId));
|
final assets = ref.watch(assetsProvider(partner.isarId));
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() {
|
||||||
|
ref.read(assetProvider.notifier).getPartnerAssets(partner);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text("${partner.firstName} ${partner.lastName}"),
|
title: Text("${partner.firstName} ${partner.lastName}"),
|
||||||
@@ -30,7 +39,8 @@ class PartnerDetailPage extends HookConsumerWidget {
|
|||||||
)
|
)
|
||||||
: ImmichAssetGrid(
|
: ImmichAssetGrid(
|
||||||
renderList: renderList,
|
renderList: renderList,
|
||||||
onRefresh: () => ref.read(assetProvider.notifier).getAllAsset(),
|
onRefresh: () =>
|
||||||
|
ref.read(assetProvider.notifier).getPartnerAssets(partner),
|
||||||
),
|
),
|
||||||
error: (e, _) => Text("Error loading partners:\n$e"),
|
error: (e, _) => Text("Error loading partners:\n$e"),
|
||||||
loading: () => const Center(child: ImmichLoadingIndicator()),
|
loading: () => const Center(child: ImmichLoadingIndicator()),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -27,7 +28,7 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
final curatedLocation = ref.watch(getCuratedLocationProvider);
|
final curatedLocation = ref.watch(getCuratedLocationProvider);
|
||||||
final curatedPeople = ref.watch(getCuratedPeopleProvider);
|
final curatedPeople = ref.watch(getCuratedPeopleProvider);
|
||||||
var isDarkTheme = Theme.of(context).brightness == Brightness.dark;
|
var isDarkTheme = Theme.of(context).brightness == Brightness.dark;
|
||||||
double imageSize = MediaQuery.of(context).size.width / 3;
|
double imageSize = math.min(MediaQuery.of(context).size.width / 3, 150);
|
||||||
|
|
||||||
TextStyle categoryTitleStyle = const TextStyle(
|
TextStyle categoryTitleStyle = const TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ enum AppSettingsEnum<T> {
|
|||||||
mapThemeMode<bool>(StoreKey.mapThemeMode, null, false),
|
mapThemeMode<bool>(StoreKey.mapThemeMode, null, false),
|
||||||
mapShowFavoriteOnly<bool>(StoreKey.mapShowFavoriteOnly, null, false),
|
mapShowFavoriteOnly<bool>(StoreKey.mapShowFavoriteOnly, null, false),
|
||||||
mapRelativeDate<int>(StoreKey.mapRelativeDate, null, 0),
|
mapRelativeDate<int>(StoreKey.mapRelativeDate, null, 0),
|
||||||
|
allowSelfSignedSSLCert<bool>(StoreKey.selfSignedCert, null, false),
|
||||||
;
|
;
|
||||||
|
|
||||||
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);
|
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);
|
||||||
|
|||||||
@@ -1,23 +1,29 @@
|
|||||||
|
import 'dart:io';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart' show useEffect, useState;
|
||||||
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/modules/settings/ui/settings_switch_list_tile.dart';
|
import 'package:immich_mobile/modules/settings/ui/settings_switch_list_tile.dart';
|
||||||
import 'package:immich_mobile/shared/services/immich_logger.service.dart';
|
import 'package:immich_mobile/shared/services/immich_logger.service.dart';
|
||||||
|
import 'package:immich_mobile/utils/http_ssl_cert_override.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
class AdvancedSettings extends HookConsumerWidget {
|
class AdvancedSettings extends HookConsumerWidget {
|
||||||
const AdvancedSettings({super.key});
|
const AdvancedSettings({super.key});
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
bool isLoggedIn = Store.tryGet(StoreKey.currentUser) != null;
|
||||||
final appSettingService = ref.watch(appSettingsServiceProvider);
|
final appSettingService = ref.watch(appSettingsServiceProvider);
|
||||||
final isEnabled =
|
final isEnabled =
|
||||||
useState(AppSettingsEnum.advancedTroubleshooting.defaultValue);
|
useState(AppSettingsEnum.advancedTroubleshooting.defaultValue);
|
||||||
final levelId = useState(AppSettingsEnum.logLevel.defaultValue);
|
final levelId = useState(AppSettingsEnum.logLevel.defaultValue);
|
||||||
final preferRemote =
|
final preferRemote =
|
||||||
useState(AppSettingsEnum.preferRemoteImage.defaultValue);
|
useState(AppSettingsEnum.preferRemoteImage.defaultValue);
|
||||||
|
final allowSelfSignedSSLCert =
|
||||||
|
useState(AppSettingsEnum.allowSelfSignedSSLCert.defaultValue);
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
@@ -27,6 +33,8 @@ class AdvancedSettings extends HookConsumerWidget {
|
|||||||
levelId.value = appSettingService.getSetting(AppSettingsEnum.logLevel);
|
levelId.value = appSettingService.getSetting(AppSettingsEnum.logLevel);
|
||||||
preferRemote.value =
|
preferRemote.value =
|
||||||
appSettingService.getSetting(AppSettingsEnum.preferRemoteImage);
|
appSettingService.getSetting(AppSettingsEnum.preferRemoteImage);
|
||||||
|
allowSelfSignedSSLCert.value = appSettingService
|
||||||
|
.getSetting(AppSettingsEnum.allowSelfSignedSSLCert);
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
@@ -88,6 +96,17 @@ class AdvancedSettings extends HookConsumerWidget {
|
|||||||
title: "advanced_settings_prefer_remote_title".tr(),
|
title: "advanced_settings_prefer_remote_title".tr(),
|
||||||
subtitle: "advanced_settings_prefer_remote_subtitle".tr(),
|
subtitle: "advanced_settings_prefer_remote_subtitle".tr(),
|
||||||
),
|
),
|
||||||
|
SettingsSwitchListTile(
|
||||||
|
enabled: !isLoggedIn,
|
||||||
|
appSettingService: appSettingService,
|
||||||
|
valueNotifier: allowSelfSignedSSLCert,
|
||||||
|
settingsEnum: AppSettingsEnum.allowSelfSignedSSLCert,
|
||||||
|
title: "advanced_settings_self_signed_ssl_title".tr(),
|
||||||
|
subtitle: "advanced_settings_self_signed_ssl_subtitle".tr(),
|
||||||
|
onChanged: (value) {
|
||||||
|
HttpOverrides.global = HttpSSLCertOverride();
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class SettingsSwitchListTile extends StatelessWidget {
|
|||||||
final String title;
|
final String title;
|
||||||
final bool enabled;
|
final bool enabled;
|
||||||
final String? subtitle;
|
final String? subtitle;
|
||||||
|
final Function(bool)? onChanged;
|
||||||
|
|
||||||
SettingsSwitchListTile({
|
SettingsSwitchListTile({
|
||||||
required this.appSettingService,
|
required this.appSettingService,
|
||||||
@@ -16,19 +17,26 @@ class SettingsSwitchListTile extends StatelessWidget {
|
|||||||
required this.title,
|
required this.title,
|
||||||
this.subtitle,
|
this.subtitle,
|
||||||
this.enabled = true,
|
this.enabled = true,
|
||||||
|
this.onChanged,
|
||||||
}) : super(key: Key(settingsEnum.name));
|
}) : super(key: Key(settingsEnum.name));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SwitchListTile.adaptive(
|
return SwitchListTile.adaptive(
|
||||||
|
selectedTileColor: enabled ? null : Theme.of(context).disabledColor,
|
||||||
value: valueNotifier.value,
|
value: valueNotifier.value,
|
||||||
onChanged: !enabled
|
onChanged: (bool value) {
|
||||||
? null
|
if (enabled) {
|
||||||
: (value) {
|
valueNotifier.value = value;
|
||||||
valueNotifier.value = value;
|
appSettingService.setSetting(settingsEnum, value);
|
||||||
appSettingService.setSetting(settingsEnum, value);
|
}
|
||||||
},
|
if (onChanged != null) {
|
||||||
activeColor: Theme.of(context).primaryColor,
|
onChanged!(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
activeColor: enabled
|
||||||
|
? Theme.of(context).primaryColor
|
||||||
|
: Theme.of(context).disabledColor,
|
||||||
dense: true,
|
dense: true,
|
||||||
title: Text(
|
title: Text(
|
||||||
title,
|
title,
|
||||||
|
|||||||
@@ -125,7 +125,6 @@ part 'router.gr.dart';
|
|||||||
AutoRoute(
|
AutoRoute(
|
||||||
page: SettingsPage,
|
page: SettingsPage,
|
||||||
guards: [
|
guards: [
|
||||||
AuthGuard,
|
|
||||||
DuplicateGuard,
|
DuplicateGuard,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -550,10 +550,7 @@ class _$AppRouter extends RootStackRouter {
|
|||||||
RouteConfig(
|
RouteConfig(
|
||||||
SettingsRoute.name,
|
SettingsRoute.name,
|
||||||
path: '/settings-page',
|
path: '/settings-page',
|
||||||
guards: [
|
guards: [duplicateGuard],
|
||||||
authGuard,
|
|
||||||
duplicateGuard,
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
RouteConfig(
|
RouteConfig(
|
||||||
AppLogRoute.name,
|
AppLogRoute.name,
|
||||||
@@ -1351,6 +1348,7 @@ class MemoryRouteArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
/// [MapPage]
|
/// [MapPage]
|
||||||
class MapRoute extends PageRouteInfo<void> {
|
class MapRoute extends PageRouteInfo<void> {
|
||||||
const MapRoute()
|
const MapRoute()
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart
|
|||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/models/user.dart';
|
import 'package:immich_mobile/shared/models/user.dart';
|
||||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||||
|
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||||
|
|
||||||
class TabNavigationObserver extends AutoRouterObserver {
|
class TabNavigationObserver extends AutoRouterObserver {
|
||||||
@@ -42,6 +43,7 @@ class TabNavigationObserver extends AutoRouterObserver {
|
|||||||
|
|
||||||
if (route.name == 'SharingRoute') {
|
if (route.name == 'SharingRoute') {
|
||||||
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
||||||
|
ref.read(assetProvider.notifier).getPartnerAssets();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.name == 'LibraryRoute') {
|
if (route.name == 'LibraryRoute') {
|
||||||
@@ -50,6 +52,7 @@ class TabNavigationObserver extends AutoRouterObserver {
|
|||||||
|
|
||||||
if (route.name == 'HomeRoute') {
|
if (route.name == 'HomeRoute') {
|
||||||
ref.invalidate(memoryFutureProvider);
|
ref.invalidate(memoryFutureProvider);
|
||||||
|
Future(() => ref.read(assetProvider.notifier).getAllAsset());
|
||||||
|
|
||||||
// Update user info
|
// Update user info
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -417,17 +417,17 @@ enum AssetState {
|
|||||||
|
|
||||||
extension AssetsHelper on IsarCollection<Asset> {
|
extension AssetsHelper on IsarCollection<Asset> {
|
||||||
Future<int> deleteAllByRemoteId(Iterable<String> ids) =>
|
Future<int> deleteAllByRemoteId(Iterable<String> ids) =>
|
||||||
ids.isEmpty ? Future.value(0) : _remote(ids).deleteAll();
|
ids.isEmpty ? Future.value(0) : remote(ids).deleteAll();
|
||||||
Future<int> deleteAllByLocalId(Iterable<String> ids) =>
|
Future<int> deleteAllByLocalId(Iterable<String> ids) =>
|
||||||
ids.isEmpty ? Future.value(0) : _local(ids).deleteAll();
|
ids.isEmpty ? Future.value(0) : local(ids).deleteAll();
|
||||||
Future<List<Asset>> getAllByRemoteId(Iterable<String> ids) =>
|
Future<List<Asset>> getAllByRemoteId(Iterable<String> ids) =>
|
||||||
ids.isEmpty ? Future.value([]) : _remote(ids).findAll();
|
ids.isEmpty ? Future.value([]) : remote(ids).findAll();
|
||||||
Future<List<Asset>> getAllByLocalId(Iterable<String> ids) =>
|
Future<List<Asset>> getAllByLocalId(Iterable<String> ids) =>
|
||||||
ids.isEmpty ? Future.value([]) : _local(ids).findAll();
|
ids.isEmpty ? Future.value([]) : local(ids).findAll();
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterWhereClause> _remote(Iterable<String> ids) =>
|
QueryBuilder<Asset, Asset, QAfterWhereClause> remote(Iterable<String> ids) =>
|
||||||
where().anyOf(ids, (q, String e) => q.remoteIdEqualTo(e));
|
where().anyOf(ids, (q, String e) => q.remoteIdEqualTo(e));
|
||||||
QueryBuilder<Asset, Asset, QAfterWhereClause> _local(Iterable<String> ids) {
|
QueryBuilder<Asset, Asset, QAfterWhereClause> local(Iterable<String> ids) {
|
||||||
return where().anyOf(ids, (q, String e) => q.localIdEqualTo(e));
|
return where().anyOf(ids, (q, String e) => q.localIdEqualTo(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ part 'etag.g.dart';
|
|||||||
|
|
||||||
@Collection(inheritance: false)
|
@Collection(inheritance: false)
|
||||||
class ETag {
|
class ETag {
|
||||||
ETag({required this.id, this.value});
|
ETag({required this.id, this.assetCount, this.time});
|
||||||
Id get isarId => fastHash(id);
|
Id get isarId => fastHash(id);
|
||||||
@Index(unique: true, replace: true, type: IndexType.hash)
|
@Index(unique: true, replace: true, type: IndexType.hash)
|
||||||
String id;
|
String id;
|
||||||
String? value;
|
int? assetCount;
|
||||||
|
DateTime? time;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,15 +17,20 @@ const ETagSchema = CollectionSchema(
|
|||||||
name: r'ETag',
|
name: r'ETag',
|
||||||
id: -644290296585643859,
|
id: -644290296585643859,
|
||||||
properties: {
|
properties: {
|
||||||
r'id': PropertySchema(
|
r'assetCount': PropertySchema(
|
||||||
id: 0,
|
id: 0,
|
||||||
|
name: r'assetCount',
|
||||||
|
type: IsarType.long,
|
||||||
|
),
|
||||||
|
r'id': PropertySchema(
|
||||||
|
id: 1,
|
||||||
name: r'id',
|
name: r'id',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'value': PropertySchema(
|
r'time': PropertySchema(
|
||||||
id: 1,
|
id: 2,
|
||||||
name: r'value',
|
name: r'time',
|
||||||
type: IsarType.string,
|
type: IsarType.dateTime,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
estimateSize: _eTagEstimateSize,
|
estimateSize: _eTagEstimateSize,
|
||||||
@@ -63,12 +68,6 @@ int _eTagEstimateSize(
|
|||||||
) {
|
) {
|
||||||
var bytesCount = offsets.last;
|
var bytesCount = offsets.last;
|
||||||
bytesCount += 3 + object.id.length * 3;
|
bytesCount += 3 + object.id.length * 3;
|
||||||
{
|
|
||||||
final value = object.value;
|
|
||||||
if (value != null) {
|
|
||||||
bytesCount += 3 + value.length * 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bytesCount;
|
return bytesCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,8 +77,9 @@ void _eTagSerialize(
|
|||||||
List<int> offsets,
|
List<int> offsets,
|
||||||
Map<Type, List<int>> allOffsets,
|
Map<Type, List<int>> allOffsets,
|
||||||
) {
|
) {
|
||||||
writer.writeString(offsets[0], object.id);
|
writer.writeLong(offsets[0], object.assetCount);
|
||||||
writer.writeString(offsets[1], object.value);
|
writer.writeString(offsets[1], object.id);
|
||||||
|
writer.writeDateTime(offsets[2], object.time);
|
||||||
}
|
}
|
||||||
|
|
||||||
ETag _eTagDeserialize(
|
ETag _eTagDeserialize(
|
||||||
@@ -89,8 +89,9 @@ ETag _eTagDeserialize(
|
|||||||
Map<Type, List<int>> allOffsets,
|
Map<Type, List<int>> allOffsets,
|
||||||
) {
|
) {
|
||||||
final object = ETag(
|
final object = ETag(
|
||||||
id: reader.readString(offsets[0]),
|
assetCount: reader.readLongOrNull(offsets[0]),
|
||||||
value: reader.readStringOrNull(offsets[1]),
|
id: reader.readString(offsets[1]),
|
||||||
|
time: reader.readDateTimeOrNull(offsets[2]),
|
||||||
);
|
);
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
@@ -103,9 +104,11 @@ P _eTagDeserializeProp<P>(
|
|||||||
) {
|
) {
|
||||||
switch (propertyId) {
|
switch (propertyId) {
|
||||||
case 0:
|
case 0:
|
||||||
return (reader.readString(offset)) as P;
|
return (reader.readLongOrNull(offset)) as P;
|
||||||
case 1:
|
case 1:
|
||||||
return (reader.readStringOrNull(offset)) as P;
|
return (reader.readString(offset)) as P;
|
||||||
|
case 2:
|
||||||
|
return (reader.readDateTimeOrNull(offset)) as P;
|
||||||
default:
|
default:
|
||||||
throw IsarError('Unknown property with id $propertyId');
|
throw IsarError('Unknown property with id $propertyId');
|
||||||
}
|
}
|
||||||
@@ -294,6 +297,75 @@ extension ETagQueryWhere on QueryBuilder<ETag, ETag, QWhereClause> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension ETagQueryFilter on QueryBuilder<ETag, ETag, QFilterCondition> {
|
extension ETagQueryFilter on QueryBuilder<ETag, ETag, QFilterCondition> {
|
||||||
|
QueryBuilder<ETag, ETag, QAfterFilterCondition> assetCountIsNull() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(const FilterCondition.isNull(
|
||||||
|
property: r'assetCount',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ETag, ETag, QAfterFilterCondition> assetCountIsNotNull() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||||
|
property: r'assetCount',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ETag, ETag, QAfterFilterCondition> assetCountEqualTo(
|
||||||
|
int? value) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'assetCount',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ETag, ETag, QAfterFilterCondition> assetCountGreaterThan(
|
||||||
|
int? value, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
include: include,
|
||||||
|
property: r'assetCount',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ETag, ETag, QAfterFilterCondition> assetCountLessThan(
|
||||||
|
int? value, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.lessThan(
|
||||||
|
include: include,
|
||||||
|
property: r'assetCount',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ETag, ETag, QAfterFilterCondition> assetCountBetween(
|
||||||
|
int? lower,
|
||||||
|
int? upper, {
|
||||||
|
bool includeLower = true,
|
||||||
|
bool includeUpper = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.between(
|
||||||
|
property: r'assetCount',
|
||||||
|
lower: lower,
|
||||||
|
includeLower: includeLower,
|
||||||
|
upper: upper,
|
||||||
|
includeUpper: includeUpper,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> idEqualTo(
|
QueryBuilder<ETag, ETag, QAfterFilterCondition> idEqualTo(
|
||||||
String value, {
|
String value, {
|
||||||
bool caseSensitive = true,
|
bool caseSensitive = true,
|
||||||
@@ -474,146 +546,70 @@ extension ETagQueryFilter on QueryBuilder<ETag, ETag, QFilterCondition> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueIsNull() {
|
QueryBuilder<ETag, ETag, QAfterFilterCondition> timeIsNull() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(const FilterCondition.isNull(
|
return query.addFilterCondition(const FilterCondition.isNull(
|
||||||
property: r'value',
|
property: r'time',
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueIsNotNull() {
|
QueryBuilder<ETag, ETag, QAfterFilterCondition> timeIsNotNull() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||||
property: r'value',
|
property: r'time',
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueEqualTo(
|
QueryBuilder<ETag, ETag, QAfterFilterCondition> timeEqualTo(DateTime? value) {
|
||||||
String? value, {
|
|
||||||
bool caseSensitive = true,
|
|
||||||
}) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(FilterCondition.equalTo(
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
property: r'value',
|
property: r'time',
|
||||||
value: value,
|
value: value,
|
||||||
caseSensitive: caseSensitive,
|
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueGreaterThan(
|
QueryBuilder<ETag, ETag, QAfterFilterCondition> timeGreaterThan(
|
||||||
String? value, {
|
DateTime? value, {
|
||||||
bool include = false,
|
bool include = false,
|
||||||
bool caseSensitive = true,
|
|
||||||
}) {
|
}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
include: include,
|
include: include,
|
||||||
property: r'value',
|
property: r'time',
|
||||||
value: value,
|
value: value,
|
||||||
caseSensitive: caseSensitive,
|
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueLessThan(
|
QueryBuilder<ETag, ETag, QAfterFilterCondition> timeLessThan(
|
||||||
String? value, {
|
DateTime? value, {
|
||||||
bool include = false,
|
bool include = false,
|
||||||
bool caseSensitive = true,
|
|
||||||
}) {
|
}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(FilterCondition.lessThan(
|
return query.addFilterCondition(FilterCondition.lessThan(
|
||||||
include: include,
|
include: include,
|
||||||
property: r'value',
|
property: r'time',
|
||||||
value: value,
|
value: value,
|
||||||
caseSensitive: caseSensitive,
|
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueBetween(
|
QueryBuilder<ETag, ETag, QAfterFilterCondition> timeBetween(
|
||||||
String? lower,
|
DateTime? lower,
|
||||||
String? upper, {
|
DateTime? upper, {
|
||||||
bool includeLower = true,
|
bool includeLower = true,
|
||||||
bool includeUpper = true,
|
bool includeUpper = true,
|
||||||
bool caseSensitive = true,
|
|
||||||
}) {
|
}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(FilterCondition.between(
|
return query.addFilterCondition(FilterCondition.between(
|
||||||
property: r'value',
|
property: r'time',
|
||||||
lower: lower,
|
lower: lower,
|
||||||
includeLower: includeLower,
|
includeLower: includeLower,
|
||||||
upper: upper,
|
upper: upper,
|
||||||
includeUpper: includeUpper,
|
includeUpper: includeUpper,
|
||||||
caseSensitive: caseSensitive,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueStartsWith(
|
|
||||||
String value, {
|
|
||||||
bool caseSensitive = true,
|
|
||||||
}) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addFilterCondition(FilterCondition.startsWith(
|
|
||||||
property: r'value',
|
|
||||||
value: value,
|
|
||||||
caseSensitive: caseSensitive,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueEndsWith(
|
|
||||||
String value, {
|
|
||||||
bool caseSensitive = true,
|
|
||||||
}) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addFilterCondition(FilterCondition.endsWith(
|
|
||||||
property: r'value',
|
|
||||||
value: value,
|
|
||||||
caseSensitive: caseSensitive,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueContains(String value,
|
|
||||||
{bool caseSensitive = true}) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addFilterCondition(FilterCondition.contains(
|
|
||||||
property: r'value',
|
|
||||||
value: value,
|
|
||||||
caseSensitive: caseSensitive,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueMatches(String pattern,
|
|
||||||
{bool caseSensitive = true}) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addFilterCondition(FilterCondition.matches(
|
|
||||||
property: r'value',
|
|
||||||
wildcard: pattern,
|
|
||||||
caseSensitive: caseSensitive,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueIsEmpty() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addFilterCondition(FilterCondition.equalTo(
|
|
||||||
property: r'value',
|
|
||||||
value: '',
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueIsNotEmpty() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
|
||||||
property: r'value',
|
|
||||||
value: '',
|
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -624,6 +620,18 @@ extension ETagQueryObject on QueryBuilder<ETag, ETag, QFilterCondition> {}
|
|||||||
extension ETagQueryLinks on QueryBuilder<ETag, ETag, QFilterCondition> {}
|
extension ETagQueryLinks on QueryBuilder<ETag, ETag, QFilterCondition> {}
|
||||||
|
|
||||||
extension ETagQuerySortBy on QueryBuilder<ETag, ETag, QSortBy> {
|
extension ETagQuerySortBy on QueryBuilder<ETag, ETag, QSortBy> {
|
||||||
|
QueryBuilder<ETag, ETag, QAfterSortBy> sortByAssetCount() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'assetCount', Sort.asc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ETag, ETag, QAfterSortBy> sortByAssetCountDesc() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'assetCount', Sort.desc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterSortBy> sortById() {
|
QueryBuilder<ETag, ETag, QAfterSortBy> sortById() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'id', Sort.asc);
|
return query.addSortBy(r'id', Sort.asc);
|
||||||
@@ -636,20 +644,32 @@ extension ETagQuerySortBy on QueryBuilder<ETag, ETag, QSortBy> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterSortBy> sortByValue() {
|
QueryBuilder<ETag, ETag, QAfterSortBy> sortByTime() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'value', Sort.asc);
|
return query.addSortBy(r'time', Sort.asc);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterSortBy> sortByValueDesc() {
|
QueryBuilder<ETag, ETag, QAfterSortBy> sortByTimeDesc() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'value', Sort.desc);
|
return query.addSortBy(r'time', Sort.desc);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ETagQuerySortThenBy on QueryBuilder<ETag, ETag, QSortThenBy> {
|
extension ETagQuerySortThenBy on QueryBuilder<ETag, ETag, QSortThenBy> {
|
||||||
|
QueryBuilder<ETag, ETag, QAfterSortBy> thenByAssetCount() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'assetCount', Sort.asc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ETag, ETag, QAfterSortBy> thenByAssetCountDesc() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'assetCount', Sort.desc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterSortBy> thenById() {
|
QueryBuilder<ETag, ETag, QAfterSortBy> thenById() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'id', Sort.asc);
|
return query.addSortBy(r'id', Sort.asc);
|
||||||
@@ -674,20 +694,26 @@ extension ETagQuerySortThenBy on QueryBuilder<ETag, ETag, QSortThenBy> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterSortBy> thenByValue() {
|
QueryBuilder<ETag, ETag, QAfterSortBy> thenByTime() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'value', Sort.asc);
|
return query.addSortBy(r'time', Sort.asc);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QAfterSortBy> thenByValueDesc() {
|
QueryBuilder<ETag, ETag, QAfterSortBy> thenByTimeDesc() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'value', Sort.desc);
|
return query.addSortBy(r'time', Sort.desc);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ETagQueryWhereDistinct on QueryBuilder<ETag, ETag, QDistinct> {
|
extension ETagQueryWhereDistinct on QueryBuilder<ETag, ETag, QDistinct> {
|
||||||
|
QueryBuilder<ETag, ETag, QDistinct> distinctByAssetCount() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addDistinctBy(r'assetCount');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QDistinct> distinctById(
|
QueryBuilder<ETag, ETag, QDistinct> distinctById(
|
||||||
{bool caseSensitive = true}) {
|
{bool caseSensitive = true}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
@@ -695,10 +721,9 @@ extension ETagQueryWhereDistinct on QueryBuilder<ETag, ETag, QDistinct> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<ETag, ETag, QDistinct> distinctByValue(
|
QueryBuilder<ETag, ETag, QDistinct> distinctByTime() {
|
||||||
{bool caseSensitive = true}) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addDistinctBy(r'value', caseSensitive: caseSensitive);
|
return query.addDistinctBy(r'time');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -710,15 +735,21 @@ extension ETagQueryProperty on QueryBuilder<ETag, ETag, QQueryProperty> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ETag, int?, QQueryOperations> assetCountProperty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addPropertyName(r'assetCount');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<ETag, String, QQueryOperations> idProperty() {
|
QueryBuilder<ETag, String, QQueryOperations> idProperty() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addPropertyName(r'id');
|
return query.addPropertyName(r'id');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<ETag, String?, QQueryOperations> valueProperty() {
|
QueryBuilder<ETag, DateTime?, QQueryOperations> timeProperty() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addPropertyName(r'value');
|
return query.addPropertyName(r'time');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,6 +178,7 @@ enum StoreKey<T> {
|
|||||||
mapThemeMode<bool>(117, type: bool),
|
mapThemeMode<bool>(117, type: bool),
|
||||||
mapShowFavoriteOnly<bool>(118, type: bool),
|
mapShowFavoriteOnly<bool>(118, type: bool),
|
||||||
mapRelativeDate<int>(119, type: int),
|
mapRelativeDate<int>(119, type: int),
|
||||||
|
selfSignedCert<bool>(120, type: bool),
|
||||||
;
|
;
|
||||||
|
|
||||||
const StoreKey(
|
const StoreKey(
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
|
||||||
|
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
|
||||||
import 'package:immich_mobile/modules/backup/background_service/background.service.dart';
|
import 'package:immich_mobile/modules/backup/background_service/background.service.dart';
|
||||||
import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
|
import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
|
||||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
||||||
@@ -11,6 +13,7 @@ import 'package:immich_mobile/modules/settings/providers/notification_permission
|
|||||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/release_info.provider.dart';
|
import 'package:immich_mobile/shared/providers/release_info.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||||
|
import 'package:immich_mobile/shared/providers/tab.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
||||||
import 'package:immich_mobile/shared/services/immich_logger.service.dart';
|
import 'package:immich_mobile/shared/services/immich_logger.service.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
@@ -47,8 +50,18 @@ class AppStateNotiifer extends StateNotifier<AppStateEnum> {
|
|||||||
if (isAuthenticated && (permission.isGranted || permission.isLimited)) {
|
if (isAuthenticated && (permission.isGranted || permission.isLimited)) {
|
||||||
ref.read(backupProvider.notifier).resumeBackup();
|
ref.read(backupProvider.notifier).resumeBackup();
|
||||||
ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
|
ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
|
||||||
ref.watch(assetProvider.notifier).getAllAsset();
|
ref.read(serverInfoProvider.notifier).getServerVersion();
|
||||||
ref.watch(serverInfoProvider.notifier).getServerVersion();
|
switch (ref.read(tabProvider)) {
|
||||||
|
case TabEnum.home:
|
||||||
|
ref.read(assetProvider.notifier).getAllAsset();
|
||||||
|
case TabEnum.search:
|
||||||
|
// nothing to do
|
||||||
|
case TabEnum.sharing:
|
||||||
|
ref.read(assetProvider.notifier).getPartnerAssets();
|
||||||
|
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
||||||
|
case TabEnum.library:
|
||||||
|
ref.read(albumProvider.notifier).getAllAlbums();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ref.watch(websocketProvider.notifier).connect();
|
ref.watch(websocketProvider.notifier).connect();
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class AssetNotifier extends StateNotifier<bool> {
|
|||||||
final log = Logger('AssetNotifier');
|
final log = Logger('AssetNotifier');
|
||||||
bool _getAllAssetInProgress = false;
|
bool _getAllAssetInProgress = false;
|
||||||
bool _deleteInProgress = false;
|
bool _deleteInProgress = false;
|
||||||
|
bool _getPartnerAssetsInProgress = false;
|
||||||
|
|
||||||
AssetNotifier(
|
AssetNotifier(
|
||||||
this._assetService,
|
this._assetService,
|
||||||
@@ -49,15 +50,10 @@ class AssetNotifier extends StateNotifier<bool> {
|
|||||||
await clearAssetsAndAlbums(_db);
|
await clearAssetsAndAlbums(_db);
|
||||||
log.info("Manual refresh requested, cleared assets and albums from db");
|
log.info("Manual refresh requested, cleared assets and albums from db");
|
||||||
}
|
}
|
||||||
await _userService.refreshUsers();
|
|
||||||
final bool newRemote = await _assetService.refreshRemoteAssets();
|
final bool newRemote = await _assetService.refreshRemoteAssets();
|
||||||
final bool newLocal = await _albumService.refreshDeviceAlbums();
|
final bool newLocal = await _albumService.refreshDeviceAlbums();
|
||||||
debugPrint("newRemote: $newRemote, newLocal: $newLocal");
|
debugPrint("newRemote: $newRemote, newLocal: $newLocal");
|
||||||
final List<User> partners =
|
|
||||||
await _db.users.filter().isPartnerSharedWithEqualTo(true).findAll();
|
|
||||||
for (User u in partners) {
|
|
||||||
await _assetService.refreshRemoteAssets(u);
|
|
||||||
}
|
|
||||||
log.info("Load assets: ${stopwatch.elapsedMilliseconds}ms");
|
log.info("Load assets: ${stopwatch.elapsedMilliseconds}ms");
|
||||||
} finally {
|
} finally {
|
||||||
_getAllAssetInProgress = false;
|
_getAllAssetInProgress = false;
|
||||||
@@ -65,6 +61,27 @@ class AssetNotifier extends StateNotifier<bool> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> getPartnerAssets([User? partner]) async {
|
||||||
|
if (_getPartnerAssetsInProgress) return;
|
||||||
|
try {
|
||||||
|
final stopwatch = Stopwatch()..start();
|
||||||
|
_getPartnerAssetsInProgress = true;
|
||||||
|
if (partner == null) {
|
||||||
|
await _userService.refreshUsers();
|
||||||
|
final List<User> partners =
|
||||||
|
await _db.users.filter().isPartnerSharedWithEqualTo(true).findAll();
|
||||||
|
for (User u in partners) {
|
||||||
|
await _assetService.refreshRemoteAssets(u);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await _assetService.refreshRemoteAssets(partner);
|
||||||
|
}
|
||||||
|
log.info("Load partner assets: ${stopwatch.elapsedMilliseconds}ms");
|
||||||
|
} finally {
|
||||||
|
_getPartnerAssetsInProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> clearAllAsset() {
|
Future<void> clearAllAsset() {
|
||||||
return clearAssetsAndAlbums(_db);
|
return clearAssetsAndAlbums(_db);
|
||||||
}
|
}
|
||||||
|
|||||||
13
mobile/lib/shared/providers/tab.provider.dart
Normal file
13
mobile/lib/shared/providers/tab.provider.dart
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
|
enum TabEnum {
|
||||||
|
home,
|
||||||
|
search,
|
||||||
|
sharing,
|
||||||
|
library,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides the currently active tab
|
||||||
|
final tabProvider = StateProvider<TabEnum>(
|
||||||
|
(ref) => TabEnum.home,
|
||||||
|
);
|
||||||
@@ -20,6 +20,7 @@ class ApiService {
|
|||||||
late ServerInfoApi serverInfoApi;
|
late ServerInfoApi serverInfoApi;
|
||||||
late PartnerApi partnerApi;
|
late PartnerApi partnerApi;
|
||||||
late PersonApi personApi;
|
late PersonApi personApi;
|
||||||
|
late AuditApi auditApi;
|
||||||
|
|
||||||
ApiService() {
|
ApiService() {
|
||||||
final endpoint = Store.tryGet(StoreKey.serverEndpoint);
|
final endpoint = Store.tryGet(StoreKey.serverEndpoint);
|
||||||
@@ -43,6 +44,7 @@ class ApiService {
|
|||||||
searchApi = SearchApi(_apiClient);
|
searchApi = SearchApi(_apiClient);
|
||||||
partnerApi = PartnerApi(_apiClient);
|
partnerApi = PartnerApi(_apiClient);
|
||||||
personApi = PersonApi(_apiClient);
|
personApi = PersonApi(_apiClient);
|
||||||
|
auditApi = AuditApi(_apiClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> resolveAndSetEndpoint(String serverUrl) async {
|
Future<String> resolveAndSetEndpoint(String serverUrl) async {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'dart:async';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/models/etag.dart';
|
|
||||||
import 'package:immich_mobile/shared/models/exif_info.dart';
|
import 'package:immich_mobile/shared/models/exif_info.dart';
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/models/user.dart';
|
import 'package:immich_mobile/shared/models/user.dart';
|
||||||
@@ -11,7 +10,6 @@ import 'package:immich_mobile/shared/providers/api.provider.dart';
|
|||||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||||
import 'package:immich_mobile/shared/services/sync.service.dart';
|
import 'package:immich_mobile/shared/services/sync.service.dart';
|
||||||
import 'package:immich_mobile/utils/openapi_extensions.dart';
|
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
@@ -39,37 +37,34 @@ class AssetService {
|
|||||||
/// Checks the server for updated assets and updates the local database if
|
/// Checks the server for updated assets and updates the local database if
|
||||||
/// required. Returns `true` if there were any changes.
|
/// required. Returns `true` if there were any changes.
|
||||||
Future<bool> refreshRemoteAssets([User? user]) async {
|
Future<bool> refreshRemoteAssets([User? user]) async {
|
||||||
user ??= Store.get(StoreKey.currentUser);
|
user ??= Store.get<User>(StoreKey.currentUser);
|
||||||
final Stopwatch sw = Stopwatch()..start();
|
final Stopwatch sw = Stopwatch()..start();
|
||||||
final int numOwnedRemoteAssets = await _db.assets
|
|
||||||
.where()
|
|
||||||
.remoteIdIsNotNull()
|
|
||||||
.filter()
|
|
||||||
.ownerIdEqualTo(user!.isarId)
|
|
||||||
.count();
|
|
||||||
final bool changes = await _syncService.syncRemoteAssetsToDb(
|
final bool changes = await _syncService.syncRemoteAssetsToDb(
|
||||||
user,
|
user,
|
||||||
() async => (await _getRemoteAssets(
|
_getRemoteAssetChanges,
|
||||||
hasCache: numOwnedRemoteAssets > 0,
|
_getRemoteAssets,
|
||||||
user: user!,
|
|
||||||
)),
|
|
||||||
);
|
);
|
||||||
debugPrint("refreshRemoteAssets full took ${sw.elapsedMilliseconds}ms");
|
debugPrint("refreshRemoteAssets full took ${sw.elapsedMilliseconds}ms");
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `(null, null)` if changes are invalid -> requires full sync
|
||||||
|
Future<(List<Asset>? toUpsert, List<String>? toDelete)>
|
||||||
|
_getRemoteAssetChanges(User user, DateTime since) async {
|
||||||
|
final deleted = await _apiService.auditApi
|
||||||
|
.getAuditDeletes(EntityType.ASSET, since, userId: user.id);
|
||||||
|
if (deleted == null || deleted.needsFullSync) return (null, null);
|
||||||
|
final assetDto = await _apiService.assetApi
|
||||||
|
.getAllAssets(userId: user.id, updatedAfter: since);
|
||||||
|
if (assetDto == null) return (null, null);
|
||||||
|
return (assetDto.map(Asset.remote).toList(), deleted.ids);
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `null` if the server state did not change, else list of assets
|
/// Returns `null` if the server state did not change, else list of assets
|
||||||
Future<List<Asset>?> _getRemoteAssets({
|
Future<List<Asset>?> _getRemoteAssets(User user) async {
|
||||||
required bool hasCache,
|
|
||||||
required User user,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
final etag = hasCache ? _db.eTags.getByIdSync(user.id)?.value : null;
|
final List<AssetResponseDto>? assets =
|
||||||
final (List<AssetResponseDto>? assets, String? newETag) =
|
await _apiService.assetApi.getAllAssets(userId: user.id);
|
||||||
await _apiService.assetApi.getAllAssetsWithETag(
|
|
||||||
eTag: etag,
|
|
||||||
userId: user.id,
|
|
||||||
);
|
|
||||||
if (assets == null) {
|
if (assets == null) {
|
||||||
return null;
|
return null;
|
||||||
} else if (assets.isNotEmpty && assets.first.ownerId != user.id) {
|
} else if (assets.isNotEmpty && assets.first.ownerId != user.id) {
|
||||||
@@ -77,8 +72,6 @@ class AssetService {
|
|||||||
" The server returned assets for user ${assets.first.ownerId}"
|
" The server returned assets for user ${assets.first.ownerId}"
|
||||||
" while requesting assets of user ${user.id}");
|
" while requesting assets of user ${user.id}");
|
||||||
return null;
|
return null;
|
||||||
} else if (newETag != etag) {
|
|
||||||
_db.writeTxn(() => _db.eTags.put(ETag(id: user.id, value: newETag)));
|
|
||||||
}
|
}
|
||||||
return assets.map(Asset.remote).toList();
|
return assets.map(Asset.remote).toList();
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
|
|||||||
@@ -69,9 +69,17 @@ class SyncService {
|
|||||||
/// Returns `true` if there were any changes
|
/// Returns `true` if there were any changes
|
||||||
Future<bool> syncRemoteAssetsToDb(
|
Future<bool> syncRemoteAssetsToDb(
|
||||||
User user,
|
User user,
|
||||||
FutureOr<List<Asset>?> Function() loadAssets,
|
Future<(List<Asset>? toUpsert, List<String>? toDelete)> Function(
|
||||||
|
User user,
|
||||||
|
DateTime since,
|
||||||
|
) getChangedAssets,
|
||||||
|
FutureOr<List<Asset>?> Function(User user) loadAssets,
|
||||||
) =>
|
) =>
|
||||||
_lock.run(() => _syncRemoteAssetsToDb(user, loadAssets));
|
_lock.run(
|
||||||
|
() async =>
|
||||||
|
await _syncRemoteAssetChanges(user, getChangedAssets) ??
|
||||||
|
await _syncRemoteAssetsFull(user, loadAssets),
|
||||||
|
);
|
||||||
|
|
||||||
/// Syncs remote albums to the database
|
/// Syncs remote albums to the database
|
||||||
/// returns `true` if there were any changes
|
/// returns `true` if there were any changes
|
||||||
@@ -130,13 +138,59 @@ class SyncService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Syncs remote assets to the databas
|
/// Efficiently syncs assets via changes. Returns `null` when a full sync is required.
|
||||||
/// returns `true` if there were any changes
|
Future<bool?> _syncRemoteAssetChanges(
|
||||||
Future<bool> _syncRemoteAssetsToDb(
|
|
||||||
User user,
|
User user,
|
||||||
FutureOr<List<Asset>?> Function() loadAssets,
|
Future<(List<Asset>? toUpsert, List<String>? toDelete)> Function(
|
||||||
|
User user,
|
||||||
|
DateTime since,
|
||||||
|
) getChangedAssets,
|
||||||
) async {
|
) async {
|
||||||
final List<Asset>? remote = await loadAssets();
|
final DateTime? since = _db.eTags.getByIdSync(user.id)?.time?.toUtc();
|
||||||
|
if (since == null) return null;
|
||||||
|
final DateTime now = DateTime.now();
|
||||||
|
final (toUpsert, toDelete) = await getChangedAssets(user, since);
|
||||||
|
if (toUpsert == null || toDelete == null) return null;
|
||||||
|
try {
|
||||||
|
if (toDelete.isNotEmpty) {
|
||||||
|
await _handleRemoteAssetRemoval(toDelete);
|
||||||
|
}
|
||||||
|
if (toUpsert.isNotEmpty) {
|
||||||
|
final (_, updated) = await _linkWithExistingFromDb(toUpsert);
|
||||||
|
await upsertAssetsWithExif(updated);
|
||||||
|
}
|
||||||
|
if (toUpsert.isNotEmpty || toDelete.isNotEmpty) {
|
||||||
|
await _updateUserAssetsETag(user, now);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} on IsarError catch (e) {
|
||||||
|
_log.severe("Failed to sync remote assets to db: $e");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes remote-only assets, updates merged assets to be local-only
|
||||||
|
Future<void> _handleRemoteAssetRemoval(List<String> idsToDelete) {
|
||||||
|
return _db.writeTxn(() async {
|
||||||
|
await _db.assets.remote(idsToDelete).filter().localIdIsNull().deleteAll();
|
||||||
|
final onlyLocal = await _db.assets.remote(idsToDelete).findAll();
|
||||||
|
if (onlyLocal.isNotEmpty) {
|
||||||
|
for (final Asset a in onlyLocal) {
|
||||||
|
a.remoteId = null;
|
||||||
|
}
|
||||||
|
await _db.assets.putAll(onlyLocal);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Syncs assets by loading and comparing all assets from the server.
|
||||||
|
Future<bool> _syncRemoteAssetsFull(
|
||||||
|
User user,
|
||||||
|
FutureOr<List<Asset>?> Function(User user) loadAssets,
|
||||||
|
) async {
|
||||||
|
final DateTime now = DateTime.now();
|
||||||
|
final List<Asset>? remote = await loadAssets(user);
|
||||||
if (remote == null) {
|
if (remote == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -150,6 +204,7 @@ class SyncService {
|
|||||||
remote.sort(Asset.compareByChecksum);
|
remote.sort(Asset.compareByChecksum);
|
||||||
final (toAdd, toUpdate, toRemove) = _diffAssets(remote, inDb, remote: true);
|
final (toAdd, toUpdate, toRemove) = _diffAssets(remote, inDb, remote: true);
|
||||||
if (toAdd.isEmpty && toUpdate.isEmpty && toRemove.isEmpty) {
|
if (toAdd.isEmpty && toUpdate.isEmpty && toRemove.isEmpty) {
|
||||||
|
await _updateUserAssetsETag(user, now);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final idsToDelete = toRemove.map((e) => e.id).toList();
|
final idsToDelete = toRemove.map((e) => e.id).toList();
|
||||||
@@ -159,9 +214,13 @@ class SyncService {
|
|||||||
} on IsarError catch (e) {
|
} on IsarError catch (e) {
|
||||||
_log.severe("Failed to sync remote assets to db: $e");
|
_log.severe("Failed to sync remote assets to db: $e");
|
||||||
}
|
}
|
||||||
|
await _updateUserAssetsETag(user, now);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _updateUserAssetsETag(User user, DateTime time) =>
|
||||||
|
_db.writeTxn(() => _db.eTags.put(ETag(id: user.id, time: time)));
|
||||||
|
|
||||||
/// Syncs remote albums to the database
|
/// Syncs remote albums to the database
|
||||||
/// returns `true` if there were any changes
|
/// returns `true` if there were any changes
|
||||||
Future<bool> _syncRemoteAlbumsToDb(
|
Future<bool> _syncRemoteAlbumsToDb(
|
||||||
@@ -450,6 +509,14 @@ class SyncService {
|
|||||||
_log.fine(
|
_log.fine(
|
||||||
"Only excluded assets in local album ${ape.name} changed. Stopping sync.",
|
"Only excluded assets in local album ${ape.name} changed. Stopping sync.",
|
||||||
);
|
);
|
||||||
|
if (assetCountOnDevice !=
|
||||||
|
_db.eTags.getByIdSync(ape.eTagKeyAssetCount)?.assetCount) {
|
||||||
|
await _db.writeTxn(
|
||||||
|
() => _db.eTags.put(
|
||||||
|
ETag(id: ape.eTagKeyAssetCount, assetCount: assetCountOnDevice),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_log.fine(
|
_log.fine(
|
||||||
@@ -477,7 +544,7 @@ class SyncService {
|
|||||||
album.thumbnail.value ??= await album.assets.filter().findFirst();
|
album.thumbnail.value ??= await album.assets.filter().findFirst();
|
||||||
await album.thumbnail.save();
|
await album.thumbnail.save();
|
||||||
await _db.eTags.put(
|
await _db.eTags.put(
|
||||||
ETag(id: ape.eTagKeyAssetCount, value: assetCountOnDevice.toString()),
|
ETag(id: ape.eTagKeyAssetCount, assetCount: assetCountOnDevice),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
_log.info("Synced changes of local album ${ape.name} to DB");
|
_log.info("Synced changes of local album ${ape.name} to DB");
|
||||||
@@ -496,7 +563,7 @@ class SyncService {
|
|||||||
}
|
}
|
||||||
final int totalOnDevice = await ape.assetCountAsync;
|
final int totalOnDevice = await ape.assetCountAsync;
|
||||||
final int lastKnownTotal =
|
final int lastKnownTotal =
|
||||||
(await _db.eTags.getById(ape.eTagKeyAssetCount))?.value?.toInt() ?? 0;
|
(await _db.eTags.getById(ape.eTagKeyAssetCount))?.assetCount ?? 0;
|
||||||
final AssetPathEntity? modified = totalOnDevice > lastKnownTotal
|
final AssetPathEntity? modified = totalOnDevice > lastKnownTotal
|
||||||
? await ape.fetchPathProperties(
|
? await ape.fetchPathProperties(
|
||||||
filterOptionGroup: FilterOptionGroup(
|
filterOptionGroup: FilterOptionGroup(
|
||||||
@@ -523,9 +590,8 @@ class SyncService {
|
|||||||
await _db.assets.putAll(updated);
|
await _db.assets.putAll(updated);
|
||||||
await album.assets.update(link: existingInDb + updated);
|
await album.assets.update(link: existingInDb + updated);
|
||||||
await _db.albums.put(album);
|
await _db.albums.put(album);
|
||||||
await _db.eTags.put(
|
await _db.eTags
|
||||||
ETag(id: ape.eTagKeyAssetCount, value: totalOnDevice.toString()),
|
.put(ETag(id: ape.eTagKeyAssetCount, assetCount: totalOnDevice));
|
||||||
);
|
|
||||||
});
|
});
|
||||||
_log.info("Fast synced local album ${ape.name} to DB");
|
_log.info("Fast synced local album ${ape.name} to DB");
|
||||||
} on IsarError catch (e) {
|
} on IsarError catch (e) {
|
||||||
@@ -667,7 +733,7 @@ class SyncService {
|
|||||||
a.lastModified == null ||
|
a.lastModified == null ||
|
||||||
!a.lastModified!.isAtSameMomentAs(b.modifiedAt) ||
|
!a.lastModified!.isAtSameMomentAs(b.modifiedAt) ||
|
||||||
await a.assetCountAsync !=
|
await a.assetCountAsync !=
|
||||||
(await _db.eTags.getById(a.eTagKeyAssetCount))?.value?.toInt();
|
(await _db.eTags.getById(a.eTagKeyAssetCount))?.assetCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user