forked from Cutlery/immich
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd865d02b3 | |||
| 7bc8918067 | |||
| 29e103d96d | |||
| e8fdddf08e | |||
| 947132ac77 | |||
| b7b3735c40 | |||
| 89021ce995 | |||
| e00b37f4d2 | |||
| 7e28522cb1 | |||
| e682de67fa | |||
| 1eb3cdca42 | |||
| e2ad4ac5b3 | |||
| a7b62a5ad3 | |||
| 9d23d37601 | |||
| 66ebdf1556 | |||
| f7eab1cc9c | |||
| 048dbc83ba | |||
| 3576d078cf | |||
| 9bd2934aa2 | |||
| ac18f7515a | |||
| 968fc77183 | |||
| 207049df00 | |||
| 980c6b0dbb | |||
| ea6bc8fb10 | |||
| 6e2624da7c | |||
| a37adc0c96 | |||
| 37b4f8455b | |||
| 3c3de8b2af | |||
| fa8ce261d8 | |||
| 69c95e2b73 | |||
| 51ffac5d15 | |||
| 70cf100407 | |||
| 22e5c6ead2 | |||
| 4a34198b78 | |||
| a85780c930 | |||
| 717dfe51e7 | |||
| 5168278215 | |||
| d92dbbe655 |
@@ -19,7 +19,7 @@ jobs:
|
||||
build-sign-android:
|
||||
name: Build and sign Android
|
||||
# Skip when PR from a fork
|
||||
if: ${{ !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }}
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
runs-on: macos-12
|
||||
|
||||
steps:
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
ref="${input_ref:-$github_ref}"
|
||||
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ steps.get-ref.outputs.ref }}
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.13.3"
|
||||
flutter-version: "3.10.5"
|
||||
cache: true
|
||||
|
||||
- name: Create the Keystore
|
||||
@@ -64,12 +64,10 @@ jobs:
|
||||
ALIAS: ${{ secrets.ALIAS }}
|
||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
|
||||
run: |
|
||||
flutter build apk --release
|
||||
flutter build apk --release --split-per-abi --target-platform android-arm,android-arm64,android-x64
|
||||
run: flutter build apk --release
|
||||
|
||||
- name: Publish Android Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: release-apk-signed
|
||||
path: mobile/build/app/outputs/flutter-apk/*.apk
|
||||
path: mobile/build/app/outputs/flutter-apk/app-release.apk
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Cleanup
|
||||
run: |
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
-
|
||||
name: Clean temporary images
|
||||
if: "${{ env.TOKEN != '' }}"
|
||||
uses: stumpylog/image-cleaner-action/ephemeral@v0.3.0
|
||||
uses: stumpylog/image-cleaner-action/ephemeral@v0.2.0
|
||||
with:
|
||||
token: "${{ env.TOKEN }}"
|
||||
owner: "immich-app"
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
-
|
||||
name: Clean untagged images
|
||||
if: "${{ env.TOKEN != '' }}"
|
||||
uses: stumpylog/image-cleaner-action/untagged@v0.3.0
|
||||
uses: stumpylog/image-cleaner-action/untagged@v0.2.0
|
||||
with:
|
||||
token: "${{ env.TOKEN }}"
|
||||
owner: "immich-app"
|
||||
|
||||
@@ -36,13 +36,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
uses: docker/setup-qemu-action@v2.2.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
uses: docker/setup-buildx-action@v2.9.1
|
||||
# Workaround to fix error:
|
||||
# failed to push: failed to copy: io: read/write on closed pipe
|
||||
# See https://github.com/docker/build-push-action/issues/761
|
||||
@@ -53,13 +53,13 @@ jobs:
|
||||
- name: Login to Docker Hub
|
||||
# Only push to Docker Hub when making a release
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v2
|
||||
# Skip when PR from a fork
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
with:
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
|
||||
- name: Generate docker image tags
|
||||
id: metadata
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
flavor: |
|
||||
# Disable latest tag
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v5.0.0
|
||||
uses: docker/build-push-action@v4.1.1
|
||||
with:
|
||||
context: ${{ matrix.context }}
|
||||
platforms: ${{ matrix.platforms }}
|
||||
@@ -120,13 +120,13 @@ jobs:
|
||||
platforms: "linux/arm64,linux/amd64"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
uses: docker/setup-qemu-action@v2.2.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
uses: docker/setup-buildx-action@v2.9.1
|
||||
# Workaround to fix error:
|
||||
# failed to push: failed to copy: io: read/write on closed pipe
|
||||
# See https://github.com/docker/build-push-action/issues/761
|
||||
@@ -137,13 +137,13 @@ jobs:
|
||||
- name: Login to Docker Hub
|
||||
# Only push to Docker Hub when making a release
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v2
|
||||
# Skip when PR from a fork
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
with:
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
|
||||
- name: Generate docker image tags
|
||||
id: metadata
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
flavor: |
|
||||
# Disable latest tag
|
||||
@@ -181,7 +181,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v5.0.0
|
||||
uses: docker/build-push-action@v4.1.1
|
||||
with:
|
||||
context: ${{ matrix.context }}
|
||||
platforms: ${{ matrix.platforms }}
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.ORG_RELEASE_TOKEN }}
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.ORG_RELEASE_TOKEN }}
|
||||
|
||||
|
||||
@@ -17,13 +17,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Flutter SDK
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.13.3"
|
||||
flutter-version: "3.10.5"
|
||||
|
||||
- name: Install dependencies
|
||||
run: dart pub get
|
||||
|
||||
+11
-12
@@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run npm install
|
||||
run: npm ci
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run npm install
|
||||
run: npm ci
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run npm install
|
||||
run: npm ci
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run npm install
|
||||
run: npm ci
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run npm install
|
||||
run: npm ci
|
||||
@@ -144,12 +144,12 @@ jobs:
|
||||
name: Run mobile unit tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Flutter SDK
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.13.3"
|
||||
flutter-version: "3.10.5"
|
||||
- name: Run tests
|
||||
working-directory: ./mobile
|
||||
run: flutter test -j 1
|
||||
@@ -161,7 +161,7 @@ jobs:
|
||||
run:
|
||||
working-directory: ./machine-learning
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install poetry
|
||||
run: pipx install poetry
|
||||
- uses: actions/setup-python@v4
|
||||
@@ -171,7 +171,6 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
poetry install --with dev
|
||||
poetry run pip install --no-deps -r requirements.txt
|
||||
- name: Lint with ruff
|
||||
run: |
|
||||
poetry run ruff check --format=github app
|
||||
@@ -189,7 +188,7 @@ jobs:
|
||||
name: Check generated files are up-to-date
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run API generation
|
||||
run: npm --prefix server run api:generate
|
||||
- name: Find file changes
|
||||
@@ -224,7 +223,7 @@ jobs:
|
||||
ports:
|
||||
- 5432:5432
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install server dependencies
|
||||
run: npm --prefix server ci
|
||||
- name: Run existing migrations
|
||||
@@ -249,7 +248,7 @@ jobs:
|
||||
# name: Run mobile end-to-end integration tests
|
||||
# runs-on: macos-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - uses: actions/checkout@v3
|
||||
# - uses: actions/setup-java@v3
|
||||
# with:
|
||||
# distribution: 'zulu'
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
<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>
|
||||
|
||||
## Disclaimer
|
||||
@@ -86,7 +85,7 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
|
||||
| User-defined storage structure | Yes | Yes |
|
||||
| Public Sharing | No | Yes |
|
||||
| Archive and Favorites | Yes | Yes |
|
||||
| Global Map | Yes | Yes |
|
||||
| Global Map | No | Yes |
|
||||
| Partner Sharing | Yes | Yes |
|
||||
| Facial recognition and clustering | Yes | Yes |
|
||||
| Memories (x years ago) | Yes | Yes |
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
<a href="README_zh_CN.md">中文</a>
|
||||
<a href="README_tr_TR.md">Türkçe</a>
|
||||
<a href="README_ca_ES.md">Español</a>
|
||||
<a href="README_fr_FR.md">Français</a>
|
||||
</p>
|
||||
|
||||
## Avís legal
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
<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_fr_FR.md">Français</a>
|
||||
</p>
|
||||
|
||||
## Descargo de responsabilidad
|
||||
|
||||
-110
@@ -1,110 +0,0 @@
|
||||
<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,7 +22,6 @@
|
||||
<a href="README_zh_CN.md">中文</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>
|
||||
|
||||
## Feragatname
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
Generated
+623
-942
File diff suppressed because it is too large
Load Diff
Generated
+1
-1
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.78.0
|
||||
* The version of the OpenAPI document: 1.73.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
||||
Generated
+1
-1
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.78.0
|
||||
* The version of the OpenAPI document: 1.73.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
||||
Generated
+1
-1
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.78.0
|
||||
* The version of the OpenAPI document: 1.73.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
||||
Generated
+1
-1
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.78.0
|
||||
* The version of the OpenAPI document: 1.73.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
||||
@@ -4,14 +4,14 @@ import { SessionService } from '../services/session.service';
|
||||
import { LoginError } from '../cores/errors/login-error';
|
||||
import { exit } from 'node:process';
|
||||
import os from 'os';
|
||||
import { ServerVersionResponseDto, UserResponseDto } from 'src/api/open-api';
|
||||
import { ServerVersionReponseDto, UserResponseDto } from 'src/api/open-api';
|
||||
|
||||
export abstract class BaseCommand {
|
||||
protected sessionService!: SessionService;
|
||||
protected immichApi!: ImmichApi;
|
||||
protected deviceId!: string;
|
||||
protected user!: UserResponseDto;
|
||||
protected serverVersion!: ServerVersionResponseDto;
|
||||
protected serverVersion!: ServerVersionReponseDto;
|
||||
|
||||
protected configDir;
|
||||
protected authPath;
|
||||
|
||||
@@ -34,7 +34,7 @@ services:
|
||||
ports:
|
||||
- 3003:3003
|
||||
volumes:
|
||||
- ../machine-learning:/usr/src/app
|
||||
- ../machine-learning/app:/usr/src/app
|
||||
- model-cache:/cache
|
||||
env_file:
|
||||
- .env
|
||||
@@ -100,8 +100,8 @@ services:
|
||||
environment:
|
||||
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
|
||||
- TYPESENSE_DATA_DIR=/data
|
||||
# remove this to get debug messages
|
||||
- GLOG_minloglevel=1
|
||||
logging:
|
||||
driver: none
|
||||
volumes:
|
||||
- tsdata:/data
|
||||
|
||||
|
||||
@@ -68,8 +68,8 @@ services:
|
||||
environment:
|
||||
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
|
||||
- TYPESENSE_DATA_DIR=/data
|
||||
# remove this to get debug messages
|
||||
- GLOG_minloglevel=1
|
||||
logging:
|
||||
driver: none
|
||||
volumes:
|
||||
- tsdata:/data
|
||||
restart: always
|
||||
|
||||
@@ -54,8 +54,6 @@ services:
|
||||
environment:
|
||||
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
|
||||
- TYPESENSE_DATA_DIR=/data
|
||||
# remove this to get debug messages
|
||||
- GLOG_minloglevel=1
|
||||
volumes:
|
||||
- tsdata:/data
|
||||
restart: always
|
||||
|
||||
+10
-31
@@ -39,40 +39,15 @@ 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?
|
||||
|
||||
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.
|
||||
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_URL=false` in your .env file.
|
||||
|
||||
### How can I lower Immich's CPU usage?
|
||||
### How to disable machine-learning and TypeSense?
|
||||
|
||||
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:
|
||||
|
||||
- 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.
|
||||
:::warning
|
||||
Disabling both will result in poor search experience and typesense utilizes CLIP embeddings which are generated by machine-learning.
|
||||
:::
|
||||
|
||||
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.
|
||||
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_URL=false` & `TYPESENSE_ENABLED=false` in your .env file.
|
||||
|
||||
### What happens to existing files after I choose a new [Storage Template](/docs/administration/storage-template.mdx)?
|
||||
|
||||
@@ -84,7 +59,7 @@ This is fixed by running the storage migration job.
|
||||
|
||||
### Why is object detection not very good?
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
### How can I see Immich logs?
|
||||
|
||||
@@ -121,6 +96,10 @@ 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.
|
||||
|
||||
### 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?
|
||||
|
||||
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,12 +89,6 @@ 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.
|
||||
|
||||
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.
|
||||
|
||||
### 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"
|
||||
cd /DIRECTORY/WITH/IMAGES
|
||||
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
|
||||
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
|
||||
```
|
||||
|
||||
```bash title="Upload target directory"
|
||||
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
|
||||
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
|
||||
```
|
||||
|
||||
```bash title="Create an alias"
|
||||
alias immich='docker run -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest'
|
||||
immich upload --recursive --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
|
||||
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
|
||||
```
|
||||
|
||||
:::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.
|
||||
|
||||
```bash title="Upload to internal address"
|
||||
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
|
||||
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
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
# Config File
|
||||
|
||||
A config file can be provided as an alternative to the UI configuration.
|
||||
|
||||
### Step 1 - Create a new config file
|
||||
|
||||
In JSON format, create a new config file (e.g. `immich.config`) and put it in a location that can be accessed by Immich.
|
||||
The default configuration looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"ffmpeg": {
|
||||
"crf": 23,
|
||||
"threads": 0,
|
||||
"preset": "ultrafast",
|
||||
"targetVideoCodec": "h264",
|
||||
"targetAudioCodec": "aac",
|
||||
"targetResolution": "720",
|
||||
"maxBitrate": "0",
|
||||
"twoPass": false,
|
||||
"transcode": "required",
|
||||
"tonemap": "hable",
|
||||
"accel": "disabled"
|
||||
},
|
||||
"job": {
|
||||
"backgroundTask": {
|
||||
"concurrency": 5
|
||||
},
|
||||
"clipEncoding": {
|
||||
"concurrency": 2
|
||||
},
|
||||
"metadataExtraction": {
|
||||
"concurrency": 5
|
||||
},
|
||||
"objectTagging": {
|
||||
"concurrency": 2
|
||||
},
|
||||
"recognizeFaces": {
|
||||
"concurrency": 2
|
||||
},
|
||||
"search": {
|
||||
"concurrency": 5
|
||||
},
|
||||
"sidecar": {
|
||||
"concurrency": 5
|
||||
},
|
||||
"storageTemplateMigration": {
|
||||
"concurrency": 5
|
||||
},
|
||||
"thumbnailGeneration": {
|
||||
"concurrency": 5
|
||||
},
|
||||
"videoConversion": {
|
||||
"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": {
|
||||
"enabled": false,
|
||||
"issuerUrl": "",
|
||||
"clientId": "",
|
||||
"clientSecret": "",
|
||||
"mobileOverrideEnabled": false,
|
||||
"mobileRedirectUri": "",
|
||||
"scope": "openid email profile",
|
||||
"storageLabelClaim": "preferred_username",
|
||||
"buttonText": "Login with OAuth",
|
||||
"autoRegister": true,
|
||||
"autoLaunch": false
|
||||
},
|
||||
"passwordLogin": {
|
||||
"enabled": true
|
||||
},
|
||||
"storageTemplate": {
|
||||
"template": "{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}"
|
||||
},
|
||||
"thumbnail": {
|
||||
"webpSize": 250,
|
||||
"jpegSize": 1440,
|
||||
"quality": 90,
|
||||
"colorspace": "p3"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::tip
|
||||
In Administration > Settings is a button to copy the current configuration to your clipboard.
|
||||
So you can just grab it from there, paste it into a file and you're pretty much good to go.
|
||||
:::
|
||||
|
||||
### Step 2 - Specify the file location
|
||||
|
||||
In your `.env` file, set the variable `IMMICH_CONFIG_FILE` to the path of your config.
|
||||
For more information, refer to the [Environment Variables](https://docs.immich.app/docs/install/environment-variables) section.
|
||||
@@ -132,6 +132,7 @@ PUBLIC_LOGIN_PAGE_MESSAGE="My Family Photos and Videos Backup Server"
|
||||
|
||||
IMMICH_WEB_URL=http://immich-web:3000
|
||||
IMMICH_SERVER_URL=http://immich-server:3001
|
||||
IMMICH_MACHINE_LEARNING_URL=http://immich-machine-learning:3003
|
||||
|
||||
####################################################################################
|
||||
# Alternative API's External Address - Optional
|
||||
|
||||
@@ -1,20 +1,5 @@
|
||||
---
|
||||
sidebar_position: 90
|
||||
---
|
||||
|
||||
# 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
|
||||
|
||||
| Variable | Description | Default | Services |
|
||||
@@ -37,7 +22,6 @@ These environment variables are used by the `docker-compose.yml` file and do **N
|
||||
| `LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, microservices |
|
||||
| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload` | server, microservices |
|
||||
| `PUBLIC_LOGIN_PAGE_MESSAGE` | Public Login Page Message | | web |
|
||||
| `IMMICH_CONFIG_FILE` | Path to config file | | server |
|
||||
|
||||
:::tip
|
||||
|
||||
@@ -57,22 +41,22 @@ These environment variables are used by the `docker-compose.yml` file and do **N
|
||||
|
||||
## Ports
|
||||
|
||||
| Variable | Description | Default | Services |
|
||||
| :---------------------- | :-------------------- | :-------: | :--------------- |
|
||||
| `PORT` | Web Port | `3000` | web |
|
||||
| `SERVER_PORT` | Server Port | `3001` | server |
|
||||
| `MICROSERVICES_PORT` | Microservices Port | `3002` | microservices |
|
||||
| `MACHINE_LEARNING_HOST` | Machine Learning Host | `0.0.0.0` | machine learning |
|
||||
| `MACHINE_LEARNING_PORT` | Machine Learning Port | `3003` | machine learning |
|
||||
| Variable | Description | Default | Services |
|
||||
| :---------------------- | :-------------------- | :-----: | :--------------- |
|
||||
| `PORT` | Web Port | `3000` | web |
|
||||
| `SERVER_PORT` | Server Port | `3001` | server |
|
||||
| `MICROSERVICES_PORT` | Microservices Port | `3002` | microservices |
|
||||
| `MACHINE_LEARNING_PORT` | Machine Learning Port | `3003` | machine learning |
|
||||
|
||||
## URLs
|
||||
|
||||
| Variable | Description | Default | Services |
|
||||
| :------------------------- | :---------------------- | :-------------------------: | :--------- |
|
||||
| `IMMICH_WEB_URL` | Immich Web URL | `http://immich-web:3000` | proxy |
|
||||
| `IMMICH_SERVER_URL` | Immich Server URL | `http://immich-server:3001` | web, proxy |
|
||||
| `PUBLIC_IMMICH_SERVER_URL` | Public Immich URL | `http://immich-server:3001` | web |
|
||||
| `IMMICH_API_URL_EXTERNAL` | Immich API URL External | `/api` | web |
|
||||
| Variable | Description | Default | Services |
|
||||
| :---------------------------- | :------------------------------------------------------- | :-----------------------------------: | :-------------------- |
|
||||
| `IMMICH_WEB_URL` | Immich Web URL | `http://immich-web:3000` | proxy |
|
||||
| `IMMICH_SERVER_URL` | Immich Server URL | `http://immich-server:3001` | web, proxy |
|
||||
| `IMMICH_MACHINE_LEARNING_URL` | Immich Machine Learning URL, set `"false"` to disable ML | `http://immich-machine-learning:3003` | server, microservices |
|
||||
| `PUBLIC_IMMICH_SERVER_URL` | Public Immich URL | `http://immich-server:3001` | web |
|
||||
| `IMMICH_API_URL_EXTERNAL` | Immich API URL External | `/api` | web |
|
||||
|
||||
:::info
|
||||
|
||||
@@ -188,27 +172,18 @@ Typesense URL example JSON before encoding:
|
||||
|
||||
## Machine Learning
|
||||
|
||||
| Variable | Description | Default | Services |
|
||||
| :----------------------------------------------- | :---------------------------------------------------------------- | :-----------------: | :--------------- |
|
||||
| `MACHINE_LEARNING_MODEL_TTL`<sup>\*1</sup> | Inactivity time (s) before a model is unloaded (disabled if <= 0) | `0` | machine learning |
|
||||
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | 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_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
|
||||
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
|
||||
| `MACHINE_LEARNING_WORKERS`<sup>\*3</sup> | Number of worker processes to spawn | `1` | machine learning |
|
||||
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` | machine learning |
|
||||
|
||||
\*1: This is an experimental feature. It may result in increased memory use over time when loading models repeatedly.
|
||||
|
||||
\*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.
|
||||
|
||||
:::
|
||||
| Variable | Description | Default | Services |
|
||||
| :------------------------------------------ | :----------------------------- | :-------------------: | :--------------- |
|
||||
| `MACHINE_LEARNING_MIN_FACE_SCORE` | Minimum Face Score | `0.7` | machine learning |
|
||||
| `MACHINE_LEARNING_MODEL_TTL` | Model TTL | `300` | machine learning |
|
||||
| `MACHINE_LEARNING_EAGER_STARTUP` | Eager Startup | `true` | machine learning |
|
||||
| `MACHINE_LEARNING_MIN_TAG_SCORE` | Minimum Tag Score | `0.9` | machine learning |
|
||||
| `MACHINE_LEARNING_FACIAL_RECOGNITION_MODEL` | Facial Recognition Model | `buffalo_l` | machine learning |
|
||||
| `MACHINE_LEARNING_CLIP_TEXT_MODEL` | Clip Text Model | `clip-ViT-B-32` | machine learning |
|
||||
| `MACHINE_LEARNING_CLIP_IMAGE_MODEL` | Clip Image Model | `clip-ViT-B-32` | machine learning |
|
||||
| `MACHINE_LEARNING_CLASSIFICATION_MODEL` | Classification Model | `microsoft/resnet-50` | machine learning |
|
||||
| `MACHINE_LEARNING_CACHE_FOLDER` | ML Cache Location | `/cache` | machine learning |
|
||||
| `TRANSFORMERS_CACHE` | ML Transformers Cache Location | `/cache` | machine learning |
|
||||
|
||||
## Docker Secrets
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
sidebar_position: 80
|
||||
sidebar_position: 100
|
||||
---
|
||||
|
||||
import RegisterAdminUser from '../partials/_register-admin.md';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3.11-bookworm as builder
|
||||
FROM python:3.11.4-bullseye@sha256:5b401676aff858495a5c9c726c60b8b73fe52833e9e16eccdb59e93d52741727 as builder
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
@@ -10,13 +10,12 @@ RUN poetry config installer.max-workers 10 && \
|
||||
RUN python -m venv /opt/venv
|
||||
ENV VIRTUAL_ENV="/opt/venv" PATH="/opt/venv/bin:${PATH}"
|
||||
|
||||
COPY poetry.lock pyproject.toml requirements.txt ./
|
||||
COPY poetry.lock pyproject.toml ./
|
||||
RUN poetry install --sync --no-interaction --no-ansi --no-root --only main
|
||||
RUN pip install --no-deps -r requirements.txt
|
||||
|
||||
FROM python:3.11-slim-bookworm
|
||||
FROM python:3.11.4-slim-bullseye@sha256:91d194f58f50594cda71dcd2e8fdefd90e7ecc57d07823813b67c8521e565dcd
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends tini libmimalloc2.0 && rm -rf /var/lib/apt/lists/*
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends tini && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
ENV NODE_ENV=production \
|
||||
@@ -27,7 +26,6 @@ ENV NODE_ENV=production \
|
||||
PYTHONPATH=/usr/src
|
||||
|
||||
COPY --from=builder /opt/venv /opt/venv
|
||||
COPY start.sh log_conf.json ./
|
||||
COPY app .
|
||||
ENTRYPOINT ["tini", "--"]
|
||||
CMD ["./start.sh"]
|
||||
CMD ["python", "-m", "app.main"]
|
||||
|
||||
@@ -17,8 +17,6 @@ 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`.
|
||||
Locust works by querying the model endpoints and aggregating their statistics, meaning the app must be deployed.
|
||||
You can change the models or adjust options like score thresholds through the Locust UI.
|
||||
You can run `load_test.sh` to automatically deploy the app locally and start Locust, optionally adjusting its env variables as needed.
|
||||
|
||||
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.
|
||||
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.
|
||||
@@ -1,22 +0,0 @@
|
||||
# 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,69 +1,32 @@
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import gunicorn
|
||||
import starlette
|
||||
from pydantic import BaseSettings
|
||||
from rich.console import Console
|
||||
from rich.logging import RichHandler
|
||||
|
||||
from .schemas import ModelType
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
cache_folder: str = "/cache"
|
||||
classification_model: str = "microsoft/resnet-50"
|
||||
clip_image_model: str = "clip-ViT-B-32"
|
||||
clip_text_model: str = "clip-ViT-B-32"
|
||||
facial_recognition_model: str = "buffalo_l"
|
||||
min_tag_score: float = 0.9
|
||||
eager_startup: bool = True
|
||||
model_ttl: int = 0
|
||||
host: str = "0.0.0.0"
|
||||
port: int = 3003
|
||||
workers: int = 1
|
||||
min_face_score: float = 0.7
|
||||
test_full: bool = False
|
||||
request_threads: int = os.cpu_count() or 4
|
||||
model_inter_op_threads: int = 1
|
||||
model_intra_op_threads: int = 2
|
||||
|
||||
class Config:
|
||||
env_prefix = "MACHINE_LEARNING_"
|
||||
case_sensitive = False
|
||||
|
||||
|
||||
class LogSettings(BaseSettings):
|
||||
log_level: str = "info"
|
||||
no_color: bool = False
|
||||
|
||||
class Config:
|
||||
case_sensitive = False
|
||||
|
||||
|
||||
_clean_name = str.maketrans(":\\/", "___", ".")
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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()
|
||||
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,5 +1,4 @@
|
||||
import json
|
||||
from typing import Any, Iterator, TypeAlias
|
||||
from typing import Iterator, TypeAlias
|
||||
from unittest import mock
|
||||
|
||||
import numpy as np
|
||||
@@ -32,8 +31,3 @@ def mock_get_model() -> Iterator[mock.Mock]:
|
||||
def deployed_app() -> TestClient:
|
||||
init_state()
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def responses() -> dict[str, Any]:
|
||||
return json.load(open("responses.json", "r"))
|
||||
|
||||
@@ -1,46 +1,58 @@
|
||||
import asyncio
|
||||
import threading
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import os
|
||||
from io import BytesIO
|
||||
from typing import Any
|
||||
from zipfile import BadZipFile
|
||||
|
||||
import orjson
|
||||
from fastapi import FastAPI, Form, HTTPException, UploadFile
|
||||
from fastapi.responses import ORJSONResponse
|
||||
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf, NoSuchFile # type: ignore
|
||||
from starlette.formparsers import MultiPartParser
|
||||
import cv2
|
||||
import numpy as np
|
||||
import uvicorn
|
||||
from fastapi import Body, Depends, FastAPI
|
||||
from PIL import Image
|
||||
|
||||
from app.models.base import InferenceModel
|
||||
|
||||
from .config import log, settings
|
||||
from .config import settings
|
||||
from .models.cache import ModelCache
|
||||
from .schemas import (
|
||||
EmbeddingResponse,
|
||||
FaceResponse,
|
||||
MessageResponse,
|
||||
ModelType,
|
||||
TagResponse,
|
||||
TextModelRequest,
|
||||
TextResponse,
|
||||
)
|
||||
|
||||
MultiPartParser.max_file_size = 2**24 # spools to disk if payload is 16 MiB or larger
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
def init_state() -> None:
|
||||
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
|
||||
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.")
|
||||
|
||||
|
||||
async def load_models() -> None:
|
||||
models = [
|
||||
(settings.classification_model, ModelType.IMAGE_CLASSIFICATION),
|
||||
(settings.clip_image_model, ModelType.CLIP),
|
||||
(settings.clip_text_model, ModelType.CLIP),
|
||||
(settings.facial_recognition_model, ModelType.FACIAL_RECOGNITION),
|
||||
]
|
||||
|
||||
# Get all models
|
||||
for model_name, model_type in models:
|
||||
await app.state.model_cache.get(model_name, model_type, eager=settings.eager_startup)
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup_event() -> None:
|
||||
init_state()
|
||||
await load_models()
|
||||
|
||||
|
||||
def dep_pil_image(byte_image: bytes = Body(...)) -> Image.Image:
|
||||
return Image.open(BytesIO(byte_image))
|
||||
|
||||
|
||||
def dep_cv_image(byte_image: bytes = Body(...)) -> cv2.Mat:
|
||||
byte_image_np = np.frombuffer(byte_image, np.uint8)
|
||||
return cv2.imdecode(byte_image_np, cv2.IMREAD_COLOR)
|
||||
|
||||
|
||||
@app.get("/", response_model=MessageResponse)
|
||||
@@ -53,63 +65,62 @@ def ping() -> str:
|
||||
return "pong"
|
||||
|
||||
|
||||
@app.post("/predict")
|
||||
async def predict(
|
||||
model_name: str = Form(alias="modelName"),
|
||||
model_type: ModelType = Form(alias="modelType"),
|
||||
options: str = Form(default="{}"),
|
||||
text: str | None = Form(default=None),
|
||||
image: UploadFile | None = None,
|
||||
) -> Any:
|
||||
if image is not None:
|
||||
inputs: str | bytes = await image.read()
|
||||
elif text is not None:
|
||||
inputs = text
|
||||
else:
|
||||
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 = await load(await app.state.model_cache.get(model_name, model_type, **kwargs))
|
||||
model.configure(**kwargs)
|
||||
outputs = await run(model, inputs)
|
||||
return ORJSONResponse(outputs)
|
||||
@app.post(
|
||||
"/image-classifier/tag-image",
|
||||
response_model=TagResponse,
|
||||
status_code=200,
|
||||
)
|
||||
async def image_classification(
|
||||
image: Image.Image = Depends(dep_pil_image),
|
||||
) -> list[str]:
|
||||
model = await app.state.model_cache.get(settings.classification_model, ModelType.IMAGE_CLASSIFICATION)
|
||||
labels = model.predict(image)
|
||||
return labels
|
||||
|
||||
|
||||
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)
|
||||
@app.post(
|
||||
"/sentence-transformer/encode-image",
|
||||
response_model=EmbeddingResponse,
|
||||
status_code=200,
|
||||
)
|
||||
async def clip_encode_image(
|
||||
image: Image.Image = Depends(dep_pil_image),
|
||||
) -> list[float]:
|
||||
model = await app.state.model_cache.get(settings.clip_image_model, ModelType.CLIP)
|
||||
embedding = model.predict(image)
|
||||
return embedding
|
||||
|
||||
|
||||
async def load(model: InferenceModel) -> InferenceModel:
|
||||
if model.loaded:
|
||||
return model
|
||||
@app.post(
|
||||
"/sentence-transformer/encode-text",
|
||||
response_model=EmbeddingResponse,
|
||||
status_code=200,
|
||||
)
|
||||
async def clip_encode_text(payload: TextModelRequest) -> list[float]:
|
||||
model = await app.state.model_cache.get(settings.clip_text_model, ModelType.CLIP)
|
||||
embedding = model.predict(payload.text)
|
||||
return embedding
|
||||
|
||||
def _load() -> None:
|
||||
with app.state.locks[model.model_type]:
|
||||
model.load()
|
||||
|
||||
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
|
||||
@app.post(
|
||||
"/facial-recognition/detect-faces",
|
||||
response_model=FaceResponse,
|
||||
status_code=200,
|
||||
)
|
||||
async def facial_recognition(
|
||||
image: cv2.Mat = Depends(dep_cv_image),
|
||||
) -> list[dict[str, Any]]:
|
||||
model = await app.state.model_cache.get(settings.facial_recognition_model, ModelType.FACIAL_RECOGNITION)
|
||||
faces = model.predict(image)
|
||||
return faces
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
is_dev = os.getenv("NODE_ENV") == "development"
|
||||
uvicorn.run(
|
||||
"app.main:app",
|
||||
host=settings.host,
|
||||
port=settings.port,
|
||||
reload=is_dev,
|
||||
workers=settings.workers,
|
||||
)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
from .clip import CLIPEncoder
|
||||
from .clip import CLIPSTEncoder
|
||||
from .facial_recognition import FaceRecognizer
|
||||
from .image_classification import ImageClassifier
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pickle
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
from shutil import rmtree
|
||||
from typing import Any
|
||||
from zipfile import BadZipFile
|
||||
|
||||
import onnxruntime as ort
|
||||
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf # type: ignore
|
||||
|
||||
from ..config import get_cache_dir, log, settings
|
||||
from ..config import get_cache_dir
|
||||
from ..schemas import ModelType
|
||||
|
||||
|
||||
@@ -16,74 +16,42 @@ class InferenceModel(ABC):
|
||||
_model_type: ModelType
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str,
|
||||
cache_dir: Path | str | None = None,
|
||||
inter_op_num_threads: int = settings.model_inter_op_threads,
|
||||
intra_op_num_threads: int = settings.model_intra_op_threads,
|
||||
**model_kwargs: Any,
|
||||
self, model_name: str, cache_dir: Path | str | None = None, eager: bool = True, **model_kwargs: Any
|
||||
) -> None:
|
||||
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.providers = model_kwargs.pop("providers", ["CPUExecutionProvider"])
|
||||
# don't pre-allocate more memory than needed
|
||||
self.provider_options = model_kwargs.pop(
|
||||
"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()
|
||||
# avoid thread contention between models
|
||||
if inter_op_num_threads > 1:
|
||||
self.sess_options.execution_mode = ort.ExecutionMode.ORT_PARALLEL
|
||||
loader = self.load if eager else self.download
|
||||
try:
|
||||
loader(**model_kwargs)
|
||||
except (OSError, InvalidProtobuf, BadZipFile):
|
||||
self.clear_cache()
|
||||
loader(**model_kwargs)
|
||||
|
||||
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.intra_op_num_threads = intra_op_num_threads
|
||||
self.sess_options.enable_cpu_mem_arena = False
|
||||
|
||||
def download(self) -> None:
|
||||
def download(self, **model_kwargs: Any) -> None:
|
||||
if not self.cached:
|
||||
log.info(
|
||||
(f"Downloading {self.model_type.replace('-', ' ')} model '{self.model_name}'." "This may take a while.")
|
||||
)
|
||||
self._download()
|
||||
self._download(**model_kwargs)
|
||||
|
||||
def load(self) -> None:
|
||||
if self.loaded:
|
||||
return
|
||||
self.download()
|
||||
log.info(f"Loading {self.model_type.replace('-', ' ')} model '{self.model_name}'")
|
||||
self._load()
|
||||
self.loaded = True
|
||||
def load(self, **model_kwargs: Any) -> None:
|
||||
self.download(**model_kwargs)
|
||||
self._load(**model_kwargs)
|
||||
self._loaded = True
|
||||
|
||||
def predict(self, inputs: Any, **model_kwargs: Any) -> Any:
|
||||
self.load()
|
||||
if model_kwargs:
|
||||
self.configure(**model_kwargs)
|
||||
def predict(self, inputs: Any) -> Any:
|
||||
if not self._loaded:
|
||||
self.load()
|
||||
return self._predict(inputs)
|
||||
|
||||
@abstractmethod
|
||||
def _predict(self, inputs: Any) -> Any:
|
||||
...
|
||||
|
||||
def configure(self, **model_kwargs: Any) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _download(self) -> None:
|
||||
def _download(self, **model_kwargs: Any) -> None:
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def _load(self) -> None:
|
||||
def _load(self, **model_kwargs: Any) -> None:
|
||||
...
|
||||
|
||||
@property
|
||||
@@ -112,33 +80,12 @@ class InferenceModel(ABC):
|
||||
|
||||
def clear_cache(self) -> None:
|
||||
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
|
||||
if not rmtree.avoids_symlink_attacks:
|
||||
raise RuntimeError("Attempted to clear cache, but rmtree is not safe on this platform.")
|
||||
|
||||
if self.cache_dir.is_dir():
|
||||
log.info(f"Cleared cache directory for model '{self.model_name}'.")
|
||||
rmtree(self.cache_dir)
|
||||
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.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
# HF deep copies configs, so we need to make session options picklable
|
||||
class PicklableSessionOptions(ort.SessionOptions):
|
||||
def __getstate__(self) -> bytes:
|
||||
return pickle.dumps([(attr, getattr(self, attr)) for attr in dir(self) if not callable(getattr(self, attr))])
|
||||
|
||||
def __setstate__(self, state: Any) -> None:
|
||||
self.__init__() # type: ignore
|
||||
for attr, val in pickle.loads(state):
|
||||
setattr(self, attr, val)
|
||||
|
||||
@@ -17,7 +17,7 @@ class ModelCache:
|
||||
revalidate: bool = False,
|
||||
timeout: int | None = None,
|
||||
profiling: bool = False,
|
||||
) -> None:
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
ttl: Unloads model after this duration. Disabled if None. Defaults to None.
|
||||
@@ -46,7 +46,7 @@ class ModelCache:
|
||||
model: The requested model.
|
||||
"""
|
||||
|
||||
key = f"{model_name}{model_type.value}{model_kwargs.get('mode', '')}"
|
||||
key = self.cache.build_key(model_name, model_type.value)
|
||||
async with OptimisticLock(self.cache, key) as lock:
|
||||
model = await self.cache.get(key)
|
||||
if model is None:
|
||||
|
||||
@@ -1,154 +1,31 @@
|
||||
import os
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
from typing import Any, Literal
|
||||
from typing import Any
|
||||
|
||||
import onnxruntime as ort
|
||||
import torch
|
||||
from clip_server.model.clip import BICUBIC, _convert_image_to_rgb
|
||||
from clip_server.model.clip_onnx import _MODELS, _S3_BUCKET_V2, CLIPOnnxModel, download_model
|
||||
from clip_server.model.pretrained_models import _VISUAL_MODEL_IMAGE_SIZE
|
||||
from clip_server.model.tokenization import Tokenizer
|
||||
from PIL import Image
|
||||
from torchvision.transforms import CenterCrop, Compose, Normalize, Resize, ToTensor
|
||||
from PIL.Image import Image
|
||||
from sentence_transformers import SentenceTransformer
|
||||
from sentence_transformers.util import snapshot_download
|
||||
|
||||
from ..config import log
|
||||
from ..schemas import ModelType
|
||||
from .base import InferenceModel
|
||||
|
||||
_ST_TO_JINA_MODEL_NAME = {
|
||||
"clip-ViT-B-16": "ViT-B-16::openai",
|
||||
"clip-ViT-B-32": "ViT-B-32::openai",
|
||||
"clip-ViT-B-32-multilingual-v1": "M-CLIP/XLM-Roberta-Large-Vit-B-32",
|
||||
"clip-ViT-L-14": "ViT-L-14::openai",
|
||||
}
|
||||
|
||||
|
||||
class CLIPEncoder(InferenceModel):
|
||||
class CLIPSTEncoder(InferenceModel):
|
||||
_model_type = ModelType.CLIP
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str,
|
||||
cache_dir: str | None = None,
|
||||
mode: Literal["text", "vision"] | None = None,
|
||||
**model_kwargs: Any,
|
||||
) -> None:
|
||||
if mode is not None and mode not in ("text", "vision"):
|
||||
raise ValueError(f"Mode must be 'text', 'vision', or omitted; got '{mode}'")
|
||||
if "vit-b" not in model_name.lower():
|
||||
raise ValueError(f"Only ViT-B models are currently supported; got '{model_name}'")
|
||||
self.mode = mode
|
||||
jina_model_name = self._get_jina_model_name(model_name)
|
||||
super().__init__(jina_model_name, cache_dir, **model_kwargs)
|
||||
|
||||
def _download(self) -> None:
|
||||
models: tuple[tuple[str, str], tuple[str, str]] = _MODELS[self.model_name]
|
||||
text_onnx_path = self.cache_dir / "textual.onnx"
|
||||
vision_onnx_path = self.cache_dir / "visual.onnx"
|
||||
|
||||
if not text_onnx_path.is_file():
|
||||
self._download_model(*models[0])
|
||||
|
||||
if not vision_onnx_path.is_file():
|
||||
self._download_model(*models[1])
|
||||
|
||||
def _load(self) -> 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.cache_dir / "textual.onnx",
|
||||
sess_options=self.sess_options,
|
||||
providers=self.providers,
|
||||
provider_options=self.provider_options,
|
||||
)
|
||||
self.text_outputs = [output.name for output in self.text_model.get_outputs()]
|
||||
self.tokenizer = Tokenizer(self.model_name)
|
||||
|
||||
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.cache_dir / "visual.onnx",
|
||||
sess_options=self.sess_options,
|
||||
providers=self.providers,
|
||||
provider_options=self.provider_options,
|
||||
)
|
||||
self.vision_outputs = [output.name for output in self.vision_model.get_outputs()]
|
||||
|
||||
image_size = _VISUAL_MODEL_IMAGE_SIZE[CLIPOnnxModel.get_model_name(self.model_name)]
|
||||
self.transform = _transform_pil_image(image_size)
|
||||
|
||||
def _predict(self, image_or_text: Image.Image | str) -> list[float]:
|
||||
if isinstance(image_or_text, bytes):
|
||||
image_or_text = Image.open(BytesIO(image_or_text))
|
||||
|
||||
match image_or_text:
|
||||
case Image.Image():
|
||||
if self.mode == "text":
|
||||
raise TypeError("Cannot encode image as text-only model")
|
||||
pixel_values = self.transform(image_or_text)
|
||||
assert isinstance(pixel_values, torch.Tensor)
|
||||
pixel_values = torch.unsqueeze(pixel_values, 0).numpy()
|
||||
outputs = self.vision_model.run(self.vision_outputs, {"pixel_values": pixel_values})
|
||||
case str():
|
||||
if self.mode == "vision":
|
||||
raise TypeError("Cannot encode text as vision-only model")
|
||||
text_inputs: dict[str, torch.Tensor] = self.tokenizer(image_or_text)
|
||||
inputs = {
|
||||
"input_ids": text_inputs["input_ids"].int().numpy(),
|
||||
"attention_mask": text_inputs["attention_mask"].int().numpy(),
|
||||
}
|
||||
outputs = self.text_model.run(self.text_outputs, inputs)
|
||||
case _:
|
||||
raise TypeError(f"Expected Image or str, but got: {type(image_or_text)}")
|
||||
|
||||
return outputs[0][0].tolist()
|
||||
|
||||
def _get_jina_model_name(self, model_name: str) -> str:
|
||||
if model_name in _MODELS:
|
||||
return model_name
|
||||
elif model_name in _ST_TO_JINA_MODEL_NAME:
|
||||
log.warn(
|
||||
(
|
||||
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]
|
||||
else:
|
||||
raise ValueError(f"Unknown model name {model_name}.")
|
||||
|
||||
def _download_model(self, model_name: str, model_md5: str) -> bool:
|
||||
# downloading logic is adapted from clip-server's CLIPOnnxModel class
|
||||
download_model(
|
||||
url=_S3_BUCKET_V2 + model_name,
|
||||
target_folder=self.cache_dir.as_posix(),
|
||||
md5sum=model_md5,
|
||||
with_resume=True,
|
||||
def _download(self, **model_kwargs: Any) -> None:
|
||||
repo_id = self.model_name if "/" in self.model_name else f"sentence-transformers/{self.model_name}"
|
||||
snapshot_download(
|
||||
cache_dir=self.cache_dir,
|
||||
repo_id=repo_id,
|
||||
library_name="sentence-transformers",
|
||||
ignore_files=["flax_model.msgpack", "rust_model.ot", "tf_model.h5"],
|
||||
)
|
||||
file = self.cache_dir / model_name.split("/")[1]
|
||||
if file.suffix == ".zip":
|
||||
with zipfile.ZipFile(file, "r") as zip_ref:
|
||||
zip_ref.extractall(self.cache_dir)
|
||||
os.remove(file)
|
||||
return True
|
||||
|
||||
@property
|
||||
def cached(self) -> bool:
|
||||
return (self.cache_dir / "textual.onnx").is_file() and (self.cache_dir / "visual.onnx").is_file()
|
||||
def _load(self, **model_kwargs: Any) -> None:
|
||||
self.model = SentenceTransformer(
|
||||
self.model_name,
|
||||
cache_folder=self.cache_dir.as_posix(),
|
||||
**model_kwargs,
|
||||
)
|
||||
|
||||
|
||||
# same as `_transform_blob` without `_blob2image`
|
||||
def _transform_pil_image(n_px: int) -> Compose:
|
||||
return Compose(
|
||||
[
|
||||
Resize(n_px, interpolation=BICUBIC),
|
||||
CenterCrop(n_px),
|
||||
_convert_image_to_rgb,
|
||||
ToTensor(),
|
||||
Normalize(
|
||||
(0.48145466, 0.4578275, 0.40821073),
|
||||
(0.26862954, 0.26130258, 0.27577711),
|
||||
),
|
||||
]
|
||||
)
|
||||
def _predict(self, image_or_text: Image | str) -> list[float]:
|
||||
return self.model.encode(image_or_text).tolist()
|
||||
|
||||
@@ -4,11 +4,11 @@ from typing import Any
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import onnxruntime as ort
|
||||
from insightface.model_zoo import ArcFaceONNX, RetinaFace
|
||||
from insightface.utils.face_align import norm_crop
|
||||
from insightface.utils.storage import BASE_REPO_URL, download_file
|
||||
|
||||
from ..config import settings
|
||||
from ..schemas import ModelType
|
||||
from .base import InferenceModel
|
||||
|
||||
@@ -19,14 +19,14 @@ class FaceRecognizer(InferenceModel):
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str,
|
||||
min_score: float = 0.7,
|
||||
min_score: float = settings.min_face_score,
|
||||
cache_dir: Path | str | None = None,
|
||||
**model_kwargs: Any,
|
||||
) -> None:
|
||||
self.min_score = model_kwargs.pop("minScore", min_score)
|
||||
self.min_score = min_score
|
||||
super().__init__(model_name, cache_dir, **model_kwargs)
|
||||
|
||||
def _download(self) -> None:
|
||||
def _download(self, **model_kwargs: Any) -> None:
|
||||
zip_file = self.cache_dir / f"{self.model_name}.zip"
|
||||
download_file(f"{BASE_REPO_URL}/{self.model_name}.zip", zip_file)
|
||||
with zipfile.ZipFile(zip_file, "r") as zip:
|
||||
@@ -36,45 +36,27 @@ class FaceRecognizer(InferenceModel):
|
||||
zip.extractall(self.cache_dir, members=[det_file, rec_file])
|
||||
zip_file.unlink()
|
||||
|
||||
def _load(self) -> None:
|
||||
def _load(self, **model_kwargs: Any) -> None:
|
||||
try:
|
||||
det_file = next(self.cache_dir.glob("det_*.onnx"))
|
||||
rec_file = next(self.cache_dir.glob("w600k_*.onnx"))
|
||||
except StopIteration:
|
||||
raise FileNotFoundError("Facial recognition models not found in cache directory")
|
||||
|
||||
self.det_model = RetinaFace(
|
||||
session=ort.InferenceSession(
|
||||
det_file.as_posix(),
|
||||
sess_options=self.sess_options,
|
||||
providers=self.providers,
|
||||
provider_options=self.provider_options,
|
||||
),
|
||||
)
|
||||
self.rec_model = ArcFaceONNX(
|
||||
rec_file.as_posix(),
|
||||
session=ort.InferenceSession(
|
||||
rec_file.as_posix(),
|
||||
sess_options=self.sess_options,
|
||||
providers=self.providers,
|
||||
provider_options=self.provider_options,
|
||||
),
|
||||
)
|
||||
self.det_model = RetinaFace(det_file.as_posix())
|
||||
self.rec_model = ArcFaceONNX(rec_file.as_posix())
|
||||
|
||||
self.det_model.prepare(
|
||||
ctx_id=0,
|
||||
ctx_id=-1,
|
||||
det_thresh=self.min_score,
|
||||
input_size=(640, 640),
|
||||
)
|
||||
self.rec_model.prepare(ctx_id=0)
|
||||
self.rec_model.prepare(ctx_id=-1)
|
||||
|
||||
def _predict(self, image: np.ndarray[int, np.dtype[Any]] | bytes) -> list[dict[str, Any]]:
|
||||
if isinstance(image, bytes):
|
||||
image = cv2.imdecode(np.frombuffer(image, np.uint8), cv2.IMREAD_COLOR)
|
||||
def _predict(self, image: cv2.Mat) -> list[dict[str, Any]]:
|
||||
bboxes, kpss = self.det_model.detect(image)
|
||||
if bboxes.size == 0:
|
||||
return []
|
||||
assert isinstance(image, np.ndarray) and isinstance(kpss, np.ndarray)
|
||||
assert isinstance(kpss, np.ndarray)
|
||||
|
||||
scores = bboxes[:, 4].tolist()
|
||||
bboxes = bboxes[:, :4].round().tolist()
|
||||
@@ -103,6 +85,3 @@ class FaceRecognizer(InferenceModel):
|
||||
@property
|
||||
def cached(self) -> bool:
|
||||
return self.cache_dir.is_dir() and any(self.cache_dir.glob("*.onnx"))
|
||||
|
||||
def configure(self, **model_kwargs: Any) -> None:
|
||||
self.det_model.det_thresh = model_kwargs.pop("minScore", self.det_model.det_thresh)
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from huggingface_hub import snapshot_download
|
||||
from optimum.onnxruntime import ORTModelForImageClassification
|
||||
from optimum.pipelines import pipeline
|
||||
from PIL import Image
|
||||
from transformers import AutoImageProcessor
|
||||
from PIL.Image import Image
|
||||
from transformers.pipelines import pipeline
|
||||
|
||||
from ..config import log
|
||||
from ..config import settings
|
||||
from ..schemas import ModelType
|
||||
from .base import InferenceModel
|
||||
|
||||
@@ -19,57 +16,27 @@ class ImageClassifier(InferenceModel):
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str,
|
||||
min_score: float = 0.9,
|
||||
min_score: float = settings.min_tag_score,
|
||||
cache_dir: Path | str | None = None,
|
||||
**model_kwargs: Any,
|
||||
) -> None:
|
||||
self.min_score = model_kwargs.pop("minScore", min_score)
|
||||
self.min_score = min_score
|
||||
super().__init__(model_name, cache_dir, **model_kwargs)
|
||||
|
||||
def _download(self) -> None:
|
||||
def _download(self, **model_kwargs: Any) -> None:
|
||||
snapshot_download(
|
||||
cache_dir=self.cache_dir,
|
||||
repo_id=self.model_name,
|
||||
allow_patterns=["*.bin", "*.json", "*.txt"],
|
||||
local_dir=self.cache_dir,
|
||||
local_dir_use_symlinks=True,
|
||||
cache_dir=self.cache_dir, repo_id=self.model_name, allow_patterns=["*.bin", "*.json", "*.txt"]
|
||||
)
|
||||
|
||||
def _load(self) -> None:
|
||||
processor = AutoImageProcessor.from_pretrained(self.cache_dir, cache_dir=self.cache_dir)
|
||||
model_path = self.cache_dir / "model.onnx"
|
||||
model_kwargs = {
|
||||
"cache_dir": self.cache_dir,
|
||||
"provider": self.providers[0],
|
||||
"provider_options": self.provider_options[0],
|
||||
"session_options": self.sess_options,
|
||||
}
|
||||
def _load(self, **model_kwargs: Any) -> None:
|
||||
self.model = pipeline(
|
||||
self.model_type.value,
|
||||
self.model_name,
|
||||
model_kwargs={"cache_dir": self.cache_dir, **model_kwargs},
|
||||
)
|
||||
|
||||
if model_path.exists():
|
||||
model = ORTModelForImageClassification.from_pretrained(self.cache_dir, **model_kwargs)
|
||||
self.model = pipeline(self.model_type.value, model, feature_extractor=processor)
|
||||
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.model = pipeline(
|
||||
self.model_type.value,
|
||||
self.model_name,
|
||||
model_kwargs=model_kwargs,
|
||||
feature_extractor=processor,
|
||||
)
|
||||
|
||||
def _predict(self, image: Image.Image | bytes) -> list[str]:
|
||||
if isinstance(image, bytes):
|
||||
image = Image.open(BytesIO(image))
|
||||
def _predict(self, image: Image) -> list[str]:
|
||||
predictions: list[dict[str, Any]] = self.model(image) # type: ignore
|
||||
tags = [tag for pred in predictions for tag in pred["label"].split(", ") if pred["score"] >= self.min_score]
|
||||
|
||||
return tags
|
||||
|
||||
def configure(self, **model_kwargs: Any) -> None:
|
||||
self.min_score = model_kwargs.pop("minScore", self.min_score)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from enum import StrEnum
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -20,6 +20,18 @@ class MessageResponse(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
class TagResponse(BaseModel):
|
||||
__root__: list[str]
|
||||
|
||||
|
||||
class Embedding(BaseModel):
|
||||
__root__: list[float]
|
||||
|
||||
|
||||
class EmbeddingResponse(BaseModel):
|
||||
__root__: Embedding
|
||||
|
||||
|
||||
class BoundingBox(BaseModel):
|
||||
x1: int
|
||||
y1: int
|
||||
@@ -27,7 +39,23 @@ class BoundingBox(BaseModel):
|
||||
y2: int
|
||||
|
||||
|
||||
class ModelType(StrEnum):
|
||||
class Face(BaseModel):
|
||||
image_width: int
|
||||
image_height: int
|
||||
bounding_box: BoundingBox
|
||||
score: float
|
||||
embedding: Embedding
|
||||
|
||||
class Config:
|
||||
alias_generator = to_lower_camel
|
||||
allow_population_by_field_name = True
|
||||
|
||||
|
||||
class FaceResponse(BaseModel):
|
||||
__root__: list[Face]
|
||||
|
||||
|
||||
class ModelType(Enum):
|
||||
IMAGE_CLASSIFICATION = "image-classification"
|
||||
CLIP = "clip"
|
||||
FACIAL_RECOGNITION = "facial-recognition"
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import json
|
||||
import pickle
|
||||
from io import BytesIO
|
||||
from typing import Any, TypeAlias
|
||||
from typing import TypeAlias
|
||||
from unittest import mock
|
||||
|
||||
import cv2
|
||||
@@ -12,9 +10,8 @@ from PIL import Image
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from .config import settings
|
||||
from .models.base import PicklableSessionOptions
|
||||
from .models.cache import ModelCache
|
||||
from .models.clip import CLIPEncoder
|
||||
from .models.clip import CLIPSTEncoder
|
||||
from .models.facial_recognition import FaceRecognizer
|
||||
from .models.image_classification import ImageClassifier
|
||||
from .schemas import ModelType
|
||||
@@ -31,6 +28,23 @@ class TestImageClassifier:
|
||||
{"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:
|
||||
mocker.patch.object(ImageClassifier, "load")
|
||||
classifier = ImageClassifier("test_model_name", min_score=0.0)
|
||||
@@ -57,34 +71,66 @@ class TestImageClassifier:
|
||||
class TestCLIP:
|
||||
embedding = np.random.rand(512).astype(np.float32)
|
||||
|
||||
def test_eager_init(self, mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(CLIPSTEncoder, "download")
|
||||
mock_load = mocker.patch.object(CLIPSTEncoder, "load")
|
||||
clip_model = CLIPSTEncoder("test_model_name", cache_dir="test_cache", eager=True, test_arg="test_arg")
|
||||
|
||||
assert clip_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(CLIPSTEncoder, "download")
|
||||
mock_load = mocker.patch.object(CLIPSTEncoder, "load")
|
||||
clip_model = CLIPSTEncoder("test_model_name", cache_dir="test_cache", eager=False, test_arg="test_arg")
|
||||
|
||||
assert clip_model.model_name == "test_model_name"
|
||||
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:
|
||||
mocker.patch.object(CLIPEncoder, "download")
|
||||
mocked = mocker.patch("app.models.clip.ort.InferenceSession", autospec=True)
|
||||
mocked.return_value.run.return_value = [[self.embedding]]
|
||||
clip_encoder = CLIPEncoder("ViT-B-32::openai", cache_dir="test_cache", mode="vision")
|
||||
assert clip_encoder.mode == "vision"
|
||||
mocker.patch.object(CLIPSTEncoder, "load")
|
||||
clip_encoder = CLIPSTEncoder("test_model_name", cache_dir="test_cache")
|
||||
clip_encoder.model = mock.Mock()
|
||||
clip_encoder.model.encode.return_value = self.embedding
|
||||
embedding = clip_encoder.predict(pil_image)
|
||||
|
||||
assert isinstance(embedding, list)
|
||||
assert len(embedding) == 512
|
||||
assert all([isinstance(num, float) for num in embedding])
|
||||
clip_encoder.vision_model.run.assert_called_once()
|
||||
clip_encoder.model.encode.assert_called_once()
|
||||
|
||||
def test_basic_text(self, mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(CLIPEncoder, "download")
|
||||
mocked = mocker.patch("app.models.clip.ort.InferenceSession", autospec=True)
|
||||
mocked.return_value.run.return_value = [[self.embedding]]
|
||||
clip_encoder = CLIPEncoder("ViT-B-32::openai", cache_dir="test_cache", mode="text")
|
||||
assert clip_encoder.mode == "text"
|
||||
mocker.patch.object(CLIPSTEncoder, "load")
|
||||
clip_encoder = CLIPSTEncoder("test_model_name", cache_dir="test_cache")
|
||||
clip_encoder.model = mock.Mock()
|
||||
clip_encoder.model.encode.return_value = self.embedding
|
||||
embedding = clip_encoder.predict("test search query")
|
||||
|
||||
assert isinstance(embedding, list)
|
||||
assert len(embedding) == 512
|
||||
assert all([isinstance(num, float) for num in embedding])
|
||||
clip_encoder.text_model.run.assert_called_once()
|
||||
clip_encoder.model.encode.assert_called_once()
|
||||
|
||||
|
||||
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:
|
||||
mocker.patch.object(FaceRecognizer, "load")
|
||||
face_recognizer = FaceRecognizer("test_model_name", cache_dir="test_cache", min_score=0.5)
|
||||
@@ -169,71 +215,42 @@ class TestCache:
|
||||
reason="More time-consuming since it deploys the app and loads models.",
|
||||
)
|
||||
class TestEndpoints:
|
||||
def test_tagging_endpoint(
|
||||
self, pil_image: Image.Image, responses: dict[str, Any], deployed_app: TestClient
|
||||
) -> None:
|
||||
def test_tagging_endpoint(self, pil_image: Image.Image, deployed_app: TestClient) -> None:
|
||||
byte_image = BytesIO()
|
||||
pil_image.save(byte_image, format="jpeg")
|
||||
headers = {"Content-Type": "image/jpg"}
|
||||
response = deployed_app.post(
|
||||
"http://localhost:3003/predict",
|
||||
data={
|
||||
"modelName": "microsoft/resnet-50",
|
||||
"modelType": "image-classification",
|
||||
"options": json.dumps({"minScore": 0.0}),
|
||||
},
|
||||
files={"image": byte_image.getvalue()},
|
||||
"http://localhost:3003/image-classifier/tag-image",
|
||||
content=byte_image.getvalue(),
|
||||
headers=headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == responses["image-classification"]
|
||||
|
||||
def test_clip_image_endpoint(
|
||||
self, pil_image: Image.Image, responses: dict[str, Any], deployed_app: TestClient
|
||||
) -> None:
|
||||
def test_clip_image_endpoint(self, pil_image: Image.Image, deployed_app: TestClient) -> None:
|
||||
byte_image = BytesIO()
|
||||
pil_image.save(byte_image, format="jpeg")
|
||||
headers = {"Content-Type": "image/jpg"}
|
||||
response = deployed_app.post(
|
||||
"http://localhost:3003/predict",
|
||||
data={"modelName": "ViT-B-32::openai", "modelType": "clip", "options": json.dumps({"mode": "vision"})},
|
||||
files={"image": byte_image.getvalue()},
|
||||
"http://localhost:3003/sentence-transformer/encode-image",
|
||||
content=byte_image.getvalue(),
|
||||
headers=headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == responses["clip"]["image"]
|
||||
|
||||
def test_clip_text_endpoint(self, responses: dict[str, Any], deployed_app: TestClient) -> None:
|
||||
def test_clip_text_endpoint(self, deployed_app: TestClient) -> None:
|
||||
response = deployed_app.post(
|
||||
"http://localhost:3003/predict",
|
||||
data={
|
||||
"modelName": "ViT-B-32::openai",
|
||||
"modelType": "clip",
|
||||
"text": "test search query",
|
||||
"options": json.dumps({"mode": "text"}),
|
||||
},
|
||||
"http://localhost:3003/sentence-transformer/encode-text",
|
||||
json={"text": "test search query"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == responses["clip"]["text"]
|
||||
|
||||
def test_face_endpoint(self, pil_image: Image.Image, responses: dict[str, Any], deployed_app: TestClient) -> None:
|
||||
def test_face_endpoint(self, pil_image: Image.Image, deployed_app: TestClient) -> None:
|
||||
byte_image = BytesIO()
|
||||
pil_image.save(byte_image, format="jpeg")
|
||||
|
||||
headers = {"Content-Type": "image/jpg"}
|
||||
response = deployed_app.post(
|
||||
"http://localhost:3003/predict",
|
||||
data={
|
||||
"modelName": "buffalo_l",
|
||||
"modelType": "facial-recognition",
|
||||
"options": json.dumps({"minScore": 0.034}),
|
||||
},
|
||||
files={"image": byte_image.getvalue()},
|
||||
"http://localhost:3003/facial-recognition/detect-faces",
|
||||
content=byte_image.getvalue(),
|
||||
headers=headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == responses["facial-recognition"]
|
||||
|
||||
|
||||
def test_sess_options() -> None:
|
||||
sess_options = PicklableSessionOptions()
|
||||
sess_options.intra_op_num_threads = 1
|
||||
sess_options.inter_op_num_threads = 1
|
||||
pickled = pickle.dumps(sess_options)
|
||||
unpickled = pickle.loads(pickled)
|
||||
assert unpickled.intra_op_num_threads == 1
|
||||
assert unpickled.inter_op_num_threads == 1
|
||||
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
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,32 +1,13 @@
|
||||
from io import BytesIO
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from locust import HttpUser, events, task
|
||||
from locust.env import Environment
|
||||
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
|
||||
def on_test_start(environment: Environment, **kwargs: Any) -> None:
|
||||
def on_test_start(environment, **kwargs):
|
||||
global byte_image
|
||||
assert environment.parsed_options is not None
|
||||
image = Image.new("RGB", (environment.parsed_options.image_size, environment.parsed_options.image_size))
|
||||
image = Image.new("RGB", (1000, 1000))
|
||||
byte_image = BytesIO()
|
||||
image.save(byte_image, format="jpeg")
|
||||
|
||||
@@ -38,55 +19,34 @@ class InferenceLoadTest(HttpUser):
|
||||
headers: dict[str, str] = {"Content-Type": "image/jpg"}
|
||||
|
||||
# re-use the image across all instances in a process
|
||||
def on_start(self) -> None:
|
||||
def on_start(self):
|
||||
global byte_image
|
||||
self.data = byte_image.getvalue()
|
||||
|
||||
|
||||
class ClassificationFormDataLoadTest(InferenceLoadTest):
|
||||
class ClassificationLoadTest(InferenceLoadTest):
|
||||
@task
|
||||
def classify(self) -> None:
|
||||
data = [
|
||||
("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)
|
||||
def classify(self):
|
||||
self.client.post(
|
||||
"/image-classifier/tag-image", data=self.data, headers=self.headers
|
||||
)
|
||||
|
||||
|
||||
class CLIPTextFormDataLoadTest(InferenceLoadTest):
|
||||
class CLIPLoadTest(InferenceLoadTest):
|
||||
@task
|
||||
def encode_text(self) -> None:
|
||||
data = [
|
||||
("modelName", self.environment.parsed_options.clip_model),
|
||||
("modelType", "clip"),
|
||||
("options", json.dumps({"mode": "text"})),
|
||||
("text", "test search query")
|
||||
]
|
||||
self.client.post("/predict", data=data)
|
||||
def encode_image(self):
|
||||
self.client.post(
|
||||
"/sentence-transformer/encode-image",
|
||||
data=self.data,
|
||||
headers=self.headers,
|
||||
)
|
||||
|
||||
|
||||
class CLIPVisionFormDataLoadTest(InferenceLoadTest):
|
||||
class RecognitionLoadTest(InferenceLoadTest):
|
||||
@task
|
||||
def encode_image(self) -> None:
|
||||
data = [
|
||||
("modelName", self.environment.parsed_options.clip_model),
|
||||
("modelType", "clip"),
|
||||
("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)
|
||||
def recognize(self):
|
||||
self.client.post(
|
||||
"/facial-recognition/detect-faces",
|
||||
data=self.data,
|
||||
headers=self.headers,
|
||||
)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"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"] }
|
||||
}
|
||||
Generated
+744
-1748
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "machine-learning"
|
||||
version = "1.78.0"
|
||||
version = "1.73.0"
|
||||
description = ""
|
||||
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
||||
readme = "README.md"
|
||||
@@ -13,6 +13,7 @@ torch = [
|
||||
{markers = "platform_machine == 'amd64' or platform_machine == 'x86_64'", version = "=2.0.1", source = "pytorch-cpu"}
|
||||
]
|
||||
transformers = "^4.29.2"
|
||||
sentence-transformers = "^2.2.2"
|
||||
onnxruntime = "^1.15.0"
|
||||
insightface = "^0.7.3"
|
||||
opencv-python-headless = "^4.7.0.72"
|
||||
@@ -21,25 +22,13 @@ fastapi = "^0.95.2"
|
||||
uvicorn = {extras = ["standard"], version = "^0.22.0"}
|
||||
pydantic = "^1.10.8"
|
||||
aiocache = "^0.12.1"
|
||||
optimum = "^1.9.1"
|
||||
torchvision = [
|
||||
{markers = "platform_machine == 'arm64' or platform_machine == 'aarch64'", version = "=0.15.2", source = "pypi"},
|
||||
{markers = "platform_machine == 'amd64' or platform_machine == 'x86_64'", version = "=0.15.2", source = "pytorch-cpu"}
|
||||
]
|
||||
rich = "^13.4.2"
|
||||
ftfy = "^6.1.1"
|
||||
setuptools = "^68.0.0"
|
||||
open-clip-torch = "^2.20.0"
|
||||
python-multipart = "^0.0.6"
|
||||
orjson = "^3.9.5"
|
||||
safetensors = "0.3.2"
|
||||
gunicorn = "^21.1.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
mypy = "^1.3.0"
|
||||
black = "^23.3.0"
|
||||
pytest = "^7.3.1"
|
||||
locust = "^2.15.1"
|
||||
gunicorn = "^20.1.0"
|
||||
httpx = "^0.24.1"
|
||||
pytest-asyncio = "^0.21.0"
|
||||
pytest-cov = "^4.1.0"
|
||||
@@ -73,21 +62,13 @@ warn_untyped_fields = true
|
||||
[[tool.mypy.overrides]]
|
||||
module = [
|
||||
"huggingface_hub",
|
||||
"transformers",
|
||||
"gunicorn",
|
||||
"transformers.pipelines",
|
||||
"cv2",
|
||||
"insightface.model_zoo",
|
||||
"insightface.utils.face_align",
|
||||
"insightface.utils.storage",
|
||||
"onnxruntime",
|
||||
"optimum",
|
||||
"optimum.pipelines",
|
||||
"optimum.onnxruntime",
|
||||
"clip_server.model.clip",
|
||||
"clip_server.model.clip_onnx",
|
||||
"clip_server.model.pretrained_models",
|
||||
"clip_server.model.tokenization",
|
||||
"torchvision.transforms",
|
||||
"sentence_transformers",
|
||||
"sentence_transformers.util",
|
||||
"aiocache.backends.memory",
|
||||
"aiocache.lock",
|
||||
"aiocache.plugins"
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# requirements to be installed with `--no-deps` flag
|
||||
clip-server==0.8.*
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"flutterSdkVersion": "3.13.0",
|
||||
"flutterSdkVersion": "3.10.5",
|
||||
"flavors": {}
|
||||
}
|
||||
|
||||
Vendored
-11
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"dart.flutterSdkPath": ".fvm/flutter_sdk",
|
||||
// Remove .fvm files from search
|
||||
"search.exclude": {
|
||||
"**/.fvm": true
|
||||
},
|
||||
// Remove from file watching
|
||||
"files.watcherExclude": {
|
||||
"**/.fvm": true
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ android {
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "app.alextran.immich"
|
||||
minSdkVersion 26
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 33
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
@@ -96,8 +96,3 @@ dependencies {
|
||||
implementation "com.github.bumptech.glide:glide:$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,8 @@
|
||||
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.READ_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.READ_MEDIA_IMAGES" />
|
||||
@@ -63,7 +64,6 @@
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
|
||||
@@ -35,8 +35,8 @@ platform :android do
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 101,
|
||||
"android.injected.version.name" => "1.78.0",
|
||||
"android.injected.version.code" => 96,
|
||||
"android.injected.version.name" => "1.73.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')
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="63.585931">
|
||||
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="67.877631">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="24.755096">
|
||||
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="23.895222">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Přidáno do {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Je již v {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "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_title": "Preferovat vzdálené obrázky",
|
||||
"advanced_settings_tile_subtitle": "Pokročilé uživatelské nastavení",
|
||||
"advanced_settings_tile_title": "Pokročilé",
|
||||
"advanced_settings_troubleshooting_subtitle": "Zobrazit dodatečné vlastnosti pro řešení problémů",
|
||||
"advanced_settings_troubleshooting_subtitle": "Povolit dodatečné funkce pro ř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_included": "ZAHRNUTO",
|
||||
@@ -16,8 +16,8 @@
|
||||
"album_thumbnail_shared_by": "Sdílené od {}",
|
||||
"album_viewer_appbar_share_delete": "Odstranit album",
|
||||
"album_viewer_appbar_share_err_delete": "Nepodařilo se odstranit album",
|
||||
"album_viewer_appbar_share_err_leave": "Nepodařilo se opustit album",
|
||||
"album_viewer_appbar_share_err_remove": "Při odstraňování položek z alba se vyskytly problémy.",
|
||||
"album_viewer_appbar_share_err_leave": "Nepodařilo se ukončit album",
|
||||
"album_viewer_appbar_share_err_remove": "Při odstraňování souborů z alba se vyskytly problémy.",
|
||||
"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_remove": "Odstranit z alba",
|
||||
@@ -35,18 +35,18 @@
|
||||
"asset_list_settings_title": "Fotografická mřížka",
|
||||
"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_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_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_select_albums": "Vybraná alba",
|
||||
"backup_album_selection_page_selection_info": "Informace o výběru",
|
||||
"backup_album_selection_page_total_assets": "Celkový počet jedinečných položek",
|
||||
"backup_album_selection_page_total_assets": "Celkový počet jedinečných souborů",
|
||||
"backup_all": "Vše",
|
||||
"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_current_upload_notification": "Zálohování {}",
|
||||
"backup_background_service_default_notification": "Kontrola nových médií…",
|
||||
"backup_background_service_current_upload_notification": "Nahrávání {}",
|
||||
"backup_background_service_default_notification": "Kontrola nových médií {}",
|
||||
"backup_background_service_error_title": "Chyba zálohování",
|
||||
"backup_background_service_in_progress_notification": "Zálohování vašich médií...",
|
||||
"backup_background_service_upload_failure_notification": "Nepodařilo se zálohovat {}",
|
||||
"backup_background_service_in_progress_notification": "Vytvářím kopii vašich médií...",
|
||||
"backup_background_service_upload_failure_notification": "Nepodařilo se nahrát {}",
|
||||
"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_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_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_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_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_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_turn_off": "Vypnout zálohování na pozadí",
|
||||
"backup_controller_page_background_turn_on": "Povolit zálohování na pozadí",
|
||||
"backup_controller_page_background_wifi": "Jen na Wi-Fi",
|
||||
"backup_controller_page_background_wifi": "Jen na WiFi",
|
||||
"backup_controller_page_backup": "Zálohování",
|
||||
"backup_controller_page_backup_selected": "Vybrané: ",
|
||||
"backup_controller_page_backup_sub": "Zálohované fotografie a videa",
|
||||
@@ -76,7 +76,7 @@
|
||||
"backup_controller_page_id": "ID: {}",
|
||||
"backup_controller_page_info": "Informace o zálohování",
|
||||
"backup_controller_page_none_selected": "Žádné vybrané",
|
||||
"backup_controller_page_remainder": "Zbývá",
|
||||
"backup_controller_page_remainder": "Zůstává",
|
||||
"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_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_turn_off": "Vypnout zálohování na popředí",
|
||||
"backup_controller_page_turn_on": "Povolit zálohování na popředí",
|
||||
"backup_controller_page_uploading_file_info": "Informace o zálohovaném souboru",
|
||||
"backup_controller_page_uploading_file_info": "Nahrávaný soubor",
|
||||
"backup_err_only_album": "Nelze odstranit jediné vybrané album",
|
||||
"backup_info_card_assets": "položek",
|
||||
"backup_manual_cancelled": "Zrušeno",
|
||||
"backup_manual_cancelled": "Cancelled",
|
||||
"backup_manual_failed": "Selhalo",
|
||||
"backup_manual_in_progress": "Zálohování již probíhá. Zkuste to znovu později",
|
||||
"backup_manual_in_progress": "Zálohování již probíhá. Zkuste znovu později",
|
||||
"backup_manual_success": "Úspěch",
|
||||
"backup_manual_title": "Stav zálohování",
|
||||
"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_title": "Nastavení vyrovnávací paměti",
|
||||
"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. Níže zadejte 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. Zadejte níže, prosím, nové heslo.",
|
||||
"change_password_form_new_password": "Nové heslo",
|
||||
"change_password_form_password_mismatch": "Hesla se neshodují",
|
||||
"change_password_form_reenter_new_password": "Znovu zadejte nové heslo",
|
||||
@@ -122,7 +122,7 @@
|
||||
"common_shared": "Sdílené",
|
||||
"control_bottom_app_bar_add_to_album": "Přidat do alba",
|
||||
"control_bottom_app_bar_album_info": "{} položek",
|
||||
"control_bottom_app_bar_album_info_shared": "{} položky – sdílené",
|
||||
"control_bottom_app_bar_album_info_shared": "{} položky - sdílené",
|
||||
"control_bottom_app_bar_archive": "Archív",
|
||||
"control_bottom_app_bar_create_new_album": "Vytvořit nové album",
|
||||
"control_bottom_app_bar_delete": "Vymazat",
|
||||
@@ -132,14 +132,14 @@
|
||||
"create_album_page_untitled": "Bez názvu",
|
||||
"create_shared_album_page_create": "Vytvořit",
|
||||
"create_shared_album_page_share": "Sdílet",
|
||||
"create_shared_album_page_share_add_assets": "PŘIDAT POLOŽKY",
|
||||
"create_shared_album_page_share_add_assets": "PŘIDAT",
|
||||
"create_shared_album_page_share_select_photos": "Vybrat fotografie",
|
||||
"curated_location_page_title": "Místa",
|
||||
"curated_object_page_title": "Věci",
|
||||
"daily_title_text_date": "EEEE, d. MMMM",
|
||||
"daily_title_text_date_year": "EEEE, d. MMMM y",
|
||||
"date_format": "EEEE, d. MMMM y • H:mm",
|
||||
"delete_dialog_alert": "Tyto položky budou trvale odstraněny z Immich i z vašeho zařízení",
|
||||
"delete_dialog_alert": "Tyto položky budou trvale odstraněny z Immich a z vašeho zařízení.",
|
||||
"delete_dialog_cancel": "Zrušit",
|
||||
"delete_dialog_ok": "Vymazat",
|
||||
"delete_dialog_title": "Vymazat trvale",
|
||||
@@ -155,13 +155,13 @@
|
||||
"favorites_page_no_favorites": "Nebyla nalezena žádná oblíbená média",
|
||||
"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_err_local": "Zatím není možné přidat lokální média do alb, přeskakuji",
|
||||
"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_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_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ř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 alb.",
|
||||
"home_page_upload_err_limit": "Lze zálohovat nejvýše 30 položek najednou, přeskakuji",
|
||||
"home_page_favorite_err_local": "Zatím není možné zařadit lokální média mezi oblíbená, přeskakuje se",
|
||||
"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_upload_err_limit": "Lze nahrát nejvýše 30 položek najednou, přeskakuji",
|
||||
"image_viewer_page_state_provider_download_error": "Chyba stahování",
|
||||
"image_viewer_page_state_provider_download_success": "Stahování bylo úspěšné",
|
||||
"library_page_albums": "Alba",
|
||||
@@ -175,7 +175,7 @@
|
||||
"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_button_text": "Přihlásit se",
|
||||
"login_form_email_hint": "tvůje-mail@email.com",
|
||||
"login_form_email_hint": "tvůjmail@email.com",
|
||||
"login_form_endpoint_hint": "http://ip-tvého-serveru:port/api",
|
||||
"login_form_endpoint_url": "URL adresa serveru",
|
||||
"login_form_err_http": "Prosím, uveďte http:// nebo https://",
|
||||
@@ -185,7 +185,7 @@
|
||||
"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_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_password": "Heslo",
|
||||
"login_form_next_button": "Další",
|
||||
@@ -196,7 +196,7 @@
|
||||
"monthly_title_text_date_format": "LLLL y",
|
||||
"motion_photos_page_title": "Pohyblivé fotky",
|
||||
"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_list_tile_content": "Udělte oprávnění k aktivaci 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_grant_permission": "Povolit přístup",
|
||||
"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_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_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_request": "Immich potřebuje přístup k zobrazení vašich fotek a videí.",
|
||||
"profile_drawer_app_logs": "Logy",
|
||||
"profile_drawer_client_server_up_to_date": "Klient a server jsou aktuální",
|
||||
"profile_drawer_settings": "Nastavení",
|
||||
"profile_drawer_sign_out": "Odhlásit se",
|
||||
"recently_added_page_title": "Nedávno přidané",
|
||||
"search_bar_hint": "Prohledejte své fotky",
|
||||
"search_bar_hint": "Prohledejte své obrázky",
|
||||
"search_page_categories": "Kategorie",
|
||||
"search_page_favorites": "Oblíbené",
|
||||
"search_page_motion_photos": "Pohyblivé fotky",
|
||||
@@ -234,7 +234,7 @@
|
||||
"search_page_places": "Místa",
|
||||
"search_page_recently_added": "Nedávno přidané",
|
||||
"search_page_screenshots": "Snímky obrazovky",
|
||||
"search_page_selfies": "Autoportréty",
|
||||
"search_page_selfies": "Selfie",
|
||||
"search_page_things": "Věci",
|
||||
"search_page_videos": "Videa",
|
||||
"search_page_view_all_button": "Zobrazit vše",
|
||||
@@ -258,11 +258,11 @@
|
||||
"setting_notifications_notify_minutes": "{} minut",
|
||||
"setting_notifications_notify_never": "nikdy",
|
||||
"setting_notifications_notify_seconds": "{} sekundy",
|
||||
"setting_notifications_single_progress_subtitle": "Podrobné informace o průběhu zálohování položky",
|
||||
"setting_notifications_single_progress_subtitle": "Podrobné informace o průběhu nahrávání pro položku",
|
||||
"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_title": "Oznámení",
|
||||
"setting_notifications_total_progress_subtitle": "Celkový průběh zálohování (hotovo/celkově)",
|
||||
"setting_notifications_total_progress_subtitle": "Celkový průběh nahrávání (nahraných/celkově)",
|
||||
"setting_notifications_total_progress_title": "Zobrazit celkový průběh zálohování na pozadí",
|
||||
"setting_pages_app_bar_settings": "Nastavení",
|
||||
"settings_require_restart": "Pro použití tohoto nastavení restartujte Immich",
|
||||
@@ -297,7 +297,7 @@
|
||||
"upload_dialog_title": "Zálohovat položku",
|
||||
"version_announcement_overlay_ack": "Potvrdit",
|
||||
"version_announcement_overlay_release_notes": "poznámky k vydání",
|
||||
"version_announcement_overlay_text_1": "Ahoj, k dispozici je nová verze",
|
||||
"version_announcement_overlay_text_1": "Ahoj, je zde nová verze",
|
||||
"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_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_total_assets": "Samlede unikke elementer",
|
||||
"backup_all": "Alt",
|
||||
"backup_background_service_backup_failed_message": "Sikkerhedskopiering af elementer fejlede. Forsøger igen...",
|
||||
"backup_background_service_backup_failed_message": "Backup af elementer fejlede. 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_default_notification": "Søger efter nye elementer...",
|
||||
"backup_background_service_error_title": "Fejl med sikkerhedskopiering",
|
||||
"backup_background_service_in_progress_notification": "Tager sikkerhedskopi af dine elementer...",
|
||||
"backup_background_service_error_title": "Fejl med backup",
|
||||
"backup_background_service_in_progress_notification": "Tager backup af dine elementer...",
|
||||
"backup_background_service_upload_failure_notification": "Fejlede med uploade af {}",
|
||||
"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 sikkerhedskopi i baggrunden.",
|
||||
"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_title": "Baggrundsopdatering af app er slået fra",
|
||||
"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_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_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_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Batterioptimering",
|
||||
"backup_controller_page_background_charging": "Kun under opladning",
|
||||
"backup_controller_page_background_configure_error": "Fejlede konfigureringen af sikkerhedskopiering i baggrunden",
|
||||
"backup_controller_page_background_delay": "Udskyd sikkerhedskopi af nye elementer: {}",
|
||||
"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 sikkerhedskopiering i baggrunden er slået fra",
|
||||
"backup_controller_page_background_is_on": "Automatisk sikkerhedskopiering i baggrunden er slået til",
|
||||
"backup_controller_page_background_turn_off": "Slå sikkerhedskopiering i baggrunden fra",
|
||||
"backup_controller_page_background_turn_on": "Slå sikkerhedskopiering i baggrunden til",
|
||||
"backup_controller_page_background_configure_error": "Fejlede konfigureringen af baggrundsbackup",
|
||||
"backup_controller_page_background_delay": "Udskyd backup 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_is_off": "Automatisk baggrundsbackup er slået fra",
|
||||
"backup_controller_page_background_is_on": "Automatisk baggrundsbackup er slået til",
|
||||
"backup_controller_page_background_turn_off": "Slå baggrundsbackup fra",
|
||||
"backup_controller_page_background_turn_on": "Slå baggrundsbackup til",
|
||||
"backup_controller_page_background_wifi": "Kun med WiFi",
|
||||
"backup_controller_page_backup": "Sikkerhedskopier",
|
||||
"backup_controller_page_backup_selected": "Valgte: ",
|
||||
@@ -92,11 +92,11 @@
|
||||
"backup_controller_page_uploading_file_info": "Uploader filinformation",
|
||||
"backup_err_only_album": "Kan ikke slette det eneste album",
|
||||
"backup_info_card_assets": "elementer",
|
||||
"backup_manual_cancelled": "Annulleret",
|
||||
"backup_manual_failed": "Mislykkedes",
|
||||
"backup_manual_in_progress": "Upload er allerede undervejs. Prøv igen efter noget tid",
|
||||
"backup_manual_success": "Succes",
|
||||
"backup_manual_title": "Uploadstatus",
|
||||
"backup_manual_cancelled": "Cancelled",
|
||||
"backup_manual_failed": "Failed",
|
||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
||||
"backup_manual_success": "Success",
|
||||
"backup_manual_title": "Upload status",
|
||||
"cache_settings_album_thumbnails": "Biblioteksminiaturebilleder ({} elementer)",
|
||||
"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.",
|
||||
@@ -160,8 +160,8 @@
|
||||
"home_page_archive_err_local": "Kan ikke arkivere lokalt element endnu.. Springer over",
|
||||
"home_page_building_timeline": "Bygger tidslinjen",
|
||||
"home_page_favorite_err_local": "Kan endnu ikke gøre lokale elementer til favoritter. Springer over..",
|
||||
"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": "Det er kun muligt at lave sikkerhedskopi af 30 elementer ad gangen. 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_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Fejl ved download",
|
||||
"image_viewer_page_state_provider_download_success": "Download succesfuld",
|
||||
"library_page_albums": "Albummer",
|
||||
@@ -172,7 +172,7 @@
|
||||
"library_page_sharing": "Delte",
|
||||
"library_page_sort_created": "Senest oprettet",
|
||||
"library_page_sort_title": "Albumtitel",
|
||||
"login_disabled": "Login er blevet deaktiveret",
|
||||
"login_disabled": "Login has been disabled",
|
||||
"login_form_api_exception": "API-undtagelse. Tjek serverens URL og prøv igen. ",
|
||||
"login_form_button_text": "Log ind",
|
||||
"login_form_email_hint": "din-e-mail@e-mail.com",
|
||||
@@ -217,7 +217,7 @@
|
||||
"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_granted": "Tilladelse givet! Du er nu klar.",
|
||||
"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_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_request": "Immich kræver tilliadelse til at se dine billeder og videoer.",
|
||||
"profile_drawer_app_logs": "Log",
|
||||
"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_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_notifications_notify_failures_grace_period": "Giv besked om fejl med sikkerhedskopiering i baggrunden: {}",
|
||||
"setting_notifications_notify_failures_grace_period": "Giv besked om baggrundsbackupfejl: {}",
|
||||
"setting_notifications_notify_hours": "{} timer",
|
||||
"setting_notifications_notify_immediately": "med det samme",
|
||||
"setting_notifications_notify_minutes": "{} minutter",
|
||||
@@ -291,10 +291,10 @@
|
||||
"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_title": "Slå tre-trins indlæsning til",
|
||||
"upload_dialog_cancel": "Annuller",
|
||||
"upload_dialog_info": "Vil du sikkerhedskopiere de(t) valgte element(er) til serveren?",
|
||||
"upload_dialog_cancel": "Cancel",
|
||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
||||
"upload_dialog_ok": "Upload",
|
||||
"upload_dialog_title": "Upload element",
|
||||
"upload_dialog_title": "Upload Asset",
|
||||
"version_announcement_overlay_ack": "Accepter",
|
||||
"version_announcement_overlay_release_notes": "udgivelsesnoterne",
|
||||
"version_announcement_overlay_text_1": "Hej ven, der er en ny version af",
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
"advanced_settings_tile_title": "Advanced",
|
||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for 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_included": "INCLUDED",
|
||||
"album_thumbnail_card_item": "1 item",
|
||||
@@ -176,7 +174,6 @@
|
||||
"library_page_sort_title": "Album title",
|
||||
"login_disabled": "Login has been disabled",
|
||||
"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_email_hint": "youremail@email.com",
|
||||
"login_form_endpoint_hint": "http://your-server-ip:port/api",
|
||||
@@ -196,8 +193,6 @@
|
||||
"login_form_save_login": "Stay logged in",
|
||||
"login_form_server_empty": "Enter a server URL.",
|
||||
"login_form_server_error": "Could not connect to server.",
|
||||
"login_password_changed_success": "Password updated successfully",
|
||||
"login_password_changed_error": "There was an error updating your password",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"motion_photos_page_title": "Motion Photos",
|
||||
"notification_permission_dialog_cancel": "Cancel",
|
||||
@@ -305,21 +300,5 @@
|
||||
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",
|
||||
"version_announcement_overlay_text_2": "please take your time to visit the ",
|
||||
"version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
|
||||
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89",
|
||||
"translated_text_options": "Options",
|
||||
"map_no_assets_in_bounds": "No photos in this area",
|
||||
"map_zoom_to_see_photos": "Zoom out to see photos",
|
||||
"map_settings_dialog_title": "Map Settings",
|
||||
"map_settings_dark_mode": "Dark mode",
|
||||
"map_settings_only_show_favorites": "Show Favorite Only",
|
||||
"map_settings_only_relative_range": "Date range",
|
||||
"map_settings_dialog_cancel": "Cancel",
|
||||
"map_settings_dialog_save": "Save",
|
||||
"map_cannot_get_user_location": "Cannot get user's location",
|
||||
"map_location_service_disabled_title": "Location Service disabled",
|
||||
"map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?",
|
||||
"map_no_location_permission_title": "Location Permission denied",
|
||||
"map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?",
|
||||
"map_location_dialog_cancel": "Cancel",
|
||||
"map_location_dialog_yes": "Yes"
|
||||
}
|
||||
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89"
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Lisätty albumiin {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Kohde on jo albumissa {album}",
|
||||
"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": "Suosi etäkuvia",
|
||||
"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_tile_subtitle": "Edistyneen käyttäjän asetukset",
|
||||
"advanced_settings_tile_title": "Edistyneet",
|
||||
"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_remove": "Poista albumista",
|
||||
"album_viewer_page_share_add_users": "Lisää käyttäjiä",
|
||||
"all_people_page_title": "Ihmiset",
|
||||
"all_people_page_title": "People",
|
||||
"all_videos_page_title": "Videot",
|
||||
"archive_page_no_archived_assets": "Arkistoituja kohteita ei löytynyt",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Arkisto ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dynaaminen asetelma",
|
||||
"asset_list_layout_settings_group_automatically": "Automaattisesti",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Ryhmittele",
|
||||
"asset_list_layout_settings_group_by_month": "Kuukauden 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_cancel": "Peruuta",
|
||||
"backup_controller_page_created": "Luotu: {}",
|
||||
"backup_controller_page_desc_backup": "Kytke varmuuskopiointi päälle lähettääksesi uudet kohteet palvelimelle automaattisesti.",
|
||||
"backup_controller_page_desc_backup": "Kytke varmuuskopiointi päälle ladataksesi uudet kohteet palvelimelle automaattisesti.",
|
||||
"backup_controller_page_excluded": "Jätetty pois:",
|
||||
"backup_controller_page_failed": "Epäonnistui ({})",
|
||||
"backup_controller_page_filename": "Tiedoston nimi: {} [{}]",
|
||||
@@ -92,11 +92,11 @@
|
||||
"backup_controller_page_uploading_file_info": "Tiedostojen lähetystiedot",
|
||||
"backup_err_only_album": "Vähintään yhden albumin tulee olla valittuna",
|
||||
"backup_info_card_assets": "kohdetta",
|
||||
"backup_manual_cancelled": "Peruutettu",
|
||||
"backup_manual_failed": "Epäonnistui",
|
||||
"backup_manual_in_progress": "Lähetys palvelimelle on jo käynnissä. Kokeile uudelleen hetken kuluttua.",
|
||||
"backup_manual_success": "Onnistui",
|
||||
"backup_manual_title": "Lähetyksen tila",
|
||||
"backup_manual_cancelled": "Cancelled",
|
||||
"backup_manual_failed": "Failed",
|
||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
||||
"backup_manual_success": "Success",
|
||||
"backup_manual_title": "Upload status",
|
||||
"cache_settings_album_thumbnails": "Kirjastosivun esikatselukuvat ({} kohdetta)",
|
||||
"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.",
|
||||
@@ -128,7 +128,7 @@
|
||||
"control_bottom_app_bar_delete": "Poista",
|
||||
"control_bottom_app_bar_favorite": "Suosikki",
|
||||
"control_bottom_app_bar_share": "Jaa",
|
||||
"control_bottom_app_bar_unarchive": "Palauta arkistosta",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "Nimetön",
|
||||
"create_shared_album_page_create": "Luo",
|
||||
"create_shared_album_page_share": "Jaa",
|
||||
@@ -152,7 +152,7 @@
|
||||
"experimental_settings_new_asset_list_title": "Ota käyttöön kokeellinen kuvaruudukko",
|
||||
"experimental_settings_subtitle": "Käyttö omalla vastuulla!",
|
||||
"experimental_settings_title": "Kokeellinen",
|
||||
"favorites_page_no_favorites": "Suosikkikohteita ei löytynyt",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"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_err_local": "Paikallisten kohteiden lisääminen albumeihin ei ole mahdollista, ohitetaan",
|
||||
@@ -161,7 +161,7 @@
|
||||
"home_page_building_timeline": "Rakennetaan aikajanaa",
|
||||
"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_upload_err_limit": "Voit lähettää palvelimelle enintään 30 kohdetta kerrallaan, ohitetaan",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Lataus epäonnistui",
|
||||
"image_viewer_page_state_provider_download_success": "Lataus onnistui",
|
||||
"library_page_albums": "Albumit",
|
||||
@@ -172,7 +172,7 @@
|
||||
"library_page_sharing": "Jakaminen",
|
||||
"library_page_sort_created": "Viimeisin luotu",
|
||||
"library_page_sort_title": "Albumin otsikko",
|
||||
"login_disabled": "Kirjautuminen on poistettu käytöstä",
|
||||
"login_disabled": "Login has been disabled",
|
||||
"login_form_api_exception": "API-virhe. Tarkista palvelimen URL-osoite ja yritä uudelleen.",
|
||||
"login_form_button_text": "Kirjaudu",
|
||||
"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_enable_button": "Ota ilmoitukset käyttöön",
|
||||
"notification_permission_list_tile_title": "Ilmoitusten käyttöoikeus",
|
||||
"partner_page_add_partner": "Lisää kumppani",
|
||||
"partner_page_empty_message": "Kuviasi ei ole vielä jaettu kenenkään kumppanin kanssa.",
|
||||
"partner_page_no_more_users": "Ei enempää käyttäjiä lisättäväksi",
|
||||
"partner_page_partner_add_failed": "Kumppanin lisääminen epäonnistui",
|
||||
"partner_page_select_partner": "Valitse kumppani",
|
||||
"partner_page_shared_to_title": "Jaettu henkilöille",
|
||||
"partner_page_stop_sharing_content": "{} ei voi enää käyttää kuviasi.",
|
||||
"partner_page_stop_sharing_title": "Lopetetaanko kuvien jakaminen?",
|
||||
"partner_page_title": "Kumppani",
|
||||
"partner_page_add_partner": "Add partner",
|
||||
"partner_page_empty_message": "Your photos are not yet shared with any partner.",
|
||||
"partner_page_no_more_users": "No more users to add",
|
||||
"partner_page_partner_add_failed": "Failed to add partner",
|
||||
"partner_page_select_partner": "Select partner",
|
||||
"partner_page_shared_to_title": "Shared to",
|
||||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_continue_anyway": "Jatka silti",
|
||||
"permission_onboarding_get_started": "Aloittaminen",
|
||||
"permission_onboarding_go_to_settings": "Siirry asetuksiin",
|
||||
@@ -230,7 +230,7 @@
|
||||
"search_page_motion_photos": "Liikekuvat",
|
||||
"search_page_no_objects": "Objektitietoja ei ole saatavilla",
|
||||
"search_page_no_places": "Paikkatietoja ei ole saatavilla",
|
||||
"search_page_people": "Ihmiset",
|
||||
"search_page_people": "People",
|
||||
"search_page_places": "Paikat",
|
||||
"search_page_recently_added": "Viimeksi lisätyt",
|
||||
"search_page_screenshots": "Näyttökuvat",
|
||||
@@ -291,10 +291,10 @@
|
||||
"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_title": "Ota kolmivaiheinen lataus käyttöön",
|
||||
"upload_dialog_cancel": "Peruuta",
|
||||
"upload_dialog_info": "Haluatko varmuuskopioida valitut kohteet palvelimelle?",
|
||||
"upload_dialog_ok": "Lähetä",
|
||||
"upload_dialog_title": "Lähetä kohde",
|
||||
"upload_dialog_cancel": "Cancel",
|
||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
||||
"upload_dialog_ok": "Upload",
|
||||
"upload_dialog_title": "Upload Asset",
|
||||
"version_announcement_overlay_ack": "Tiedostan",
|
||||
"version_announcement_overlay_release_notes": "julkaisutiedoissa",
|
||||
"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_err_only_album": "Impossible de retirer le seul album",
|
||||
"backup_info_card_assets": "éléments",
|
||||
"backup_manual_cancelled": "Annulé",
|
||||
"backup_manual_failed": "Echec",
|
||||
"backup_manual_in_progress": "Téléchargement déjà en cours. Essayez après un instant",
|
||||
"backup_manual_success": "Succès ",
|
||||
"backup_manual_title": "Statut du téléchargement ",
|
||||
"backup_manual_cancelled": "Cancelled",
|
||||
"backup_manual_failed": "Failed",
|
||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
||||
"backup_manual_success": "Success",
|
||||
"backup_manual_title": "Upload status",
|
||||
"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_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_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_upload_err_limit": "Limite de téléchargement de 30 éléments en même temps, demande ignorée",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Erreur de téléchargement",
|
||||
"image_viewer_page_state_provider_download_success": "Téléchargement réussi",
|
||||
"library_page_albums": "Albums",
|
||||
@@ -172,7 +172,7 @@
|
||||
"library_page_sharing": "Partage",
|
||||
"library_page_sort_created": "Créations les plus récentes",
|
||||
"library_page_sort_title": "Titre de l'album",
|
||||
"login_disabled": "La connexion a été désactivée ",
|
||||
"login_disabled": "Login has been disabled",
|
||||
"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_email_hint": "votreemail@email.com",
|
||||
@@ -291,10 +291,10 @@
|
||||
"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_title": "Activer le chargement en trois étapes",
|
||||
"upload_dialog_cancel": "Annuler",
|
||||
"upload_dialog_info": "Voulez-vous sauvegarder la sélection vers le serveur ?",
|
||||
"upload_dialog_ok": "Télécharger ",
|
||||
"upload_dialog_title": "Télécharger cet élément ",
|
||||
"upload_dialog_cancel": "Cancel",
|
||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
||||
"upload_dialog_ok": "Upload",
|
||||
"upload_dialog_title": "Upload Asset",
|
||||
"version_announcement_overlay_ack": "Confirmer",
|
||||
"version_announcement_overlay_release_notes": "notes de mise à jour",
|
||||
"version_announcement_overlay_text_1": "Bonjour, une nouvelle version de",
|
||||
|
||||
+163
-163
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Hozzáadva a(z) {album} nevű albumhoz",
|
||||
"add_to_album_bottom_sheet_already_exists": "Már eleme a(z) {album} nevű albumnak",
|
||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Already 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_title": "Prefer remote images",
|
||||
"advanced_settings_tile_subtitle": "Haladó felhasználói beállítások",
|
||||
@@ -9,107 +9,107 @@
|
||||
"advanced_settings_troubleshooting_title": "Hibaelhárítás",
|
||||
"album_info_card_backup_album_excluded": "EXCLUDED",
|
||||
"album_info_card_backup_album_included": "INCLUDED",
|
||||
"album_thumbnail_card_item": "1 elem",
|
||||
"album_thumbnail_card_items": "{} elem",
|
||||
"album_thumbnail_card_shared": "· Megosztott",
|
||||
"album_thumbnail_card_item": "1 item",
|
||||
"album_thumbnail_card_items": "{} items",
|
||||
"album_thumbnail_card_shared": " · Shared",
|
||||
"album_thumbnail_owned": "Tulajdonos",
|
||||
"album_thumbnail_shared_by": "Megosztotta: {}",
|
||||
"album_viewer_appbar_share_delete": "Album törlése",
|
||||
"album_viewer_appbar_share_err_delete": "Hiba az album törlése közben",
|
||||
"album_viewer_appbar_share_err_leave": "Hiba az albumból való kilépés közben",
|
||||
"album_viewer_appbar_share_err_remove": "Hiba az elemek törlése közben",
|
||||
"album_viewer_appbar_share_err_title": "Hiba az album átnevezése közben",
|
||||
"album_viewer_appbar_share_leave": "Kilépés az albumból",
|
||||
"album_viewer_appbar_share_remove": "Törlés az albumból",
|
||||
"album_viewer_page_share_add_users": "Felhasználók hozzáadása",
|
||||
"all_people_page_title": "Emberek",
|
||||
"album_viewer_appbar_share_delete": "Delete album",
|
||||
"album_viewer_appbar_share_err_delete": "Failed to delete album",
|
||||
"album_viewer_appbar_share_err_leave": "Failed to leave album",
|
||||
"album_viewer_appbar_share_err_remove": "There are problems in removing assets from album",
|
||||
"album_viewer_appbar_share_err_title": "Failed to change album title",
|
||||
"album_viewer_appbar_share_leave": "Leave album",
|
||||
"album_viewer_appbar_share_remove": "Remove from album",
|
||||
"album_viewer_page_share_add_users": "Add users",
|
||||
"all_people_page_title": "People",
|
||||
"all_videos_page_title": "Videók",
|
||||
"archive_page_no_archived_assets": "Nem található archivált média",
|
||||
"archive_page_title": "Archívum ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dynamic layout",
|
||||
"asset_list_layout_settings_group_automatically": "Automatikus",
|
||||
"asset_list_layout_settings_group_by": "Group assets by",
|
||||
"asset_list_layout_settings_group_by_month": "Hónap",
|
||||
"asset_list_layout_settings_group_by_month_day": "Hónap + nap",
|
||||
"asset_list_layout_settings_group_by_month": "Month",
|
||||
"asset_list_layout_settings_group_by_month_day": "Month + day",
|
||||
"asset_list_settings_subtitle": "Photo grid layout settings",
|
||||
"asset_list_settings_title": "Photo Grid",
|
||||
"backup_album_selection_page_albums_device": "Az eszközön lévő albumok ({})",
|
||||
"backup_album_selection_page_albums_device": "Albums on device ({})",
|
||||
"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_select_albums": "Albumok kiválasztása",
|
||||
"backup_album_selection_page_select_albums": "Select albums",
|
||||
"backup_album_selection_page_selection_info": "Selection Info",
|
||||
"backup_album_selection_page_total_assets": "Összes egyedi elem",
|
||||
"backup_all": "Összes",
|
||||
"backup_background_service_backup_failed_message": "HIba a mentés közben. Újrapróbálkozás...",
|
||||
"backup_background_service_connection_failed_message": "HIba a szerverhez való csatlakozás közben. Újrapróbálkozás...",
|
||||
"backup_background_service_current_upload_notification": "Feltöltés {}",
|
||||
"backup_background_service_default_notification": "Keresés új elemek után...",
|
||||
"backup_background_service_error_title": "Hiba mentés közben",
|
||||
"backup_background_service_in_progress_notification": "Elemek mentés alatt..",
|
||||
"backup_background_service_upload_failure_notification": "Hiba feltöltés közben {}",
|
||||
"backup_controller_page_albums": "Albumok mentése",
|
||||
"backup_album_selection_page_total_assets": "Total unique assets",
|
||||
"backup_all": "All",
|
||||
"backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
|
||||
"backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…",
|
||||
"backup_background_service_current_upload_notification": "Uploading {}",
|
||||
"backup_background_service_default_notification": "Checking for new assets…",
|
||||
"backup_background_service_error_title": "Backup error",
|
||||
"backup_background_service_in_progress_notification": "Backing up your assets…",
|
||||
"backup_background_service_upload_failure_notification": "Failed to upload {}",
|
||||
"backup_controller_page_albums": "Backup 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_title": "Background app refresh disabled",
|
||||
"backup_controller_page_background_app_refresh_enable_button_text": "Beállítások megnyitása",
|
||||
"backup_controller_page_background_battery_info_link": "Mutasd meg hogyan",
|
||||
"backup_controller_page_background_battery_info_link": "Show me how",
|
||||
"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_title": "Akkumulátoroptimalizálás",
|
||||
"backup_controller_page_background_charging": "Csak töltés közben",
|
||||
"backup_controller_page_background_battery_info_title": "Battery optimizations",
|
||||
"backup_controller_page_background_charging": "Only while charging",
|
||||
"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_description": "Kapcsold be a háttérfolyamatot, hogy automatikusan mentsen elemeket az applikáció megnyitása nélkül",
|
||||
"backup_controller_page_background_is_off": "Automatikus mentés a háttérben ki van kapcsolva",
|
||||
"backup_controller_page_background_is_on": "Automatikus mentés a háttérben bekapcsolva",
|
||||
"backup_controller_page_background_turn_off": "Háttérfolyamat kikapcsolása",
|
||||
"backup_controller_page_background_turn_on": "Háttérfolyamat bekapcsolása",
|
||||
"backup_controller_page_background_wifi": "Csak WiFi-n",
|
||||
"backup_controller_page_backup": "Mentés",
|
||||
"backup_controller_page_backup_selected": "Kiválasztva:",
|
||||
"backup_controller_page_backup_sub": "Mentett fotók és videók",
|
||||
"backup_controller_page_cancel": "Megszakít",
|
||||
"backup_controller_page_created": "Létrehozva: {}",
|
||||
"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_is_off": "Automatic background backup is off",
|
||||
"backup_controller_page_background_is_on": "Automatic background backup is on",
|
||||
"backup_controller_page_background_turn_off": "Turn off background service",
|
||||
"backup_controller_page_background_turn_on": "Turn on background service",
|
||||
"backup_controller_page_background_wifi": "Only on WiFi",
|
||||
"backup_controller_page_backup": "Backup",
|
||||
"backup_controller_page_backup_selected": "Selected: ",
|
||||
"backup_controller_page_backup_sub": "Backed up photos and videos",
|
||||
"backup_controller_page_cancel": "Cancel",
|
||||
"backup_controller_page_created": "Created on: {}",
|
||||
"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": "Kivéve:",
|
||||
"backup_controller_page_failed": "Sikertelen ({})",
|
||||
"backup_controller_page_filename": "Fájlnév: {}[{}]",
|
||||
"backup_controller_page_id": "Azonosító: {}",
|
||||
"backup_controller_page_info": "Mentésinformációk",
|
||||
"backup_controller_page_none_selected": "Egy sincs kiválasztva",
|
||||
"backup_controller_page_remainder": "Maradék",
|
||||
"backup_controller_page_remainder_sub": "Hátralévő fotók és videók a kijelöltek közül",
|
||||
"backup_controller_page_select": "Kiválaszt",
|
||||
"backup_controller_page_server_storage": "Szerver tárhely",
|
||||
"backup_controller_page_start_backup": "Mentés elindítása",
|
||||
"backup_controller_page_status_off": "Autoatikus mentés az előtérben kikapcsolva",
|
||||
"backup_controller_page_status_on": "Autoatikus mentés az előtérben bekapcsolva",
|
||||
"backup_controller_page_storage_format": "{} / {} felhasználva",
|
||||
"backup_controller_page_to_backup": "Albumok amiket mentesz",
|
||||
"backup_controller_page_total": "Összes",
|
||||
"backup_controller_page_total_sub": "Minden egyedi fotó és videó a kijelölt albumokból",
|
||||
"backup_controller_page_excluded": "Excluded: ",
|
||||
"backup_controller_page_failed": "Failed ({})",
|
||||
"backup_controller_page_filename": "File name: {} [{}]",
|
||||
"backup_controller_page_id": "ID: {}",
|
||||
"backup_controller_page_info": "Backup Information",
|
||||
"backup_controller_page_none_selected": "None selected",
|
||||
"backup_controller_page_remainder": "Remainder",
|
||||
"backup_controller_page_remainder_sub": "Remaining photos and videos to back up from selection",
|
||||
"backup_controller_page_select": "Select",
|
||||
"backup_controller_page_server_storage": "Server Storage",
|
||||
"backup_controller_page_start_backup": "Start Backup",
|
||||
"backup_controller_page_status_off": "Automatic foreground backup is off",
|
||||
"backup_controller_page_status_on": "Automatic foreground backup is on",
|
||||
"backup_controller_page_storage_format": "{} of {} used",
|
||||
"backup_controller_page_to_backup": "Albums to be backup",
|
||||
"backup_controller_page_total": "Total",
|
||||
"backup_controller_page_total_sub": "All unique photos and videos from selected albums",
|
||||
"backup_controller_page_turn_off": "Turn off foreground backup",
|
||||
"backup_controller_page_turn_on": "Turn on foreground backup",
|
||||
"backup_controller_page_uploading_file_info": "Uploading file info",
|
||||
"backup_err_only_album": "Az utolsó albumot nem tudod törölni",
|
||||
"backup_info_card_assets": "elemek",
|
||||
"backup_manual_cancelled": "Megszakítva",
|
||||
"backup_err_only_album": "Cannot remove the only album",
|
||||
"backup_info_card_assets": "assets",
|
||||
"backup_manual_cancelled": "Cancelled",
|
||||
"backup_manual_failed": "Failed",
|
||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
||||
"backup_manual_success": "Sikeres",
|
||||
"backup_manual_success": "Success",
|
||||
"backup_manual_title": "Upload status",
|
||||
"cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
|
||||
"cache_settings_clear_cache_button": "Gyorsítótár törlése",
|
||||
"cache_settings_clear_cache_button": "Clear cache",
|
||||
"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_statistics_album": "Library thumbnails",
|
||||
"cache_settings_statistics_assets": "{} assets ({})",
|
||||
"cache_settings_statistics_full": "Teljes képek",
|
||||
"cache_settings_statistics_full": "Full images",
|
||||
"cache_settings_statistics_shared": "Shared album thumbnails",
|
||||
"cache_settings_statistics_thumbnail": "Előnézeti képek",
|
||||
"cache_settings_statistics_title": "Gyorsítótár által használt terület",
|
||||
"cache_settings_statistics_thumbnail": "Thumbnails",
|
||||
"cache_settings_statistics_title": "Cache usage",
|
||||
"cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application",
|
||||
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
|
||||
"cache_settings_title": "Gyorsítótár beállítások",
|
||||
"cache_settings_title": "Caching Settings",
|
||||
"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_new_password": "Új Jelszó",
|
||||
@@ -118,42 +118,42 @@
|
||||
"common_add_to_album": "Albumhoz ad",
|
||||
"common_change_password": "Jelszócsere",
|
||||
"common_create_new_album": "Új album létrehozása",
|
||||
"common_server_error": "Kérjük, ellenőrizd a hálózati kapcsolatot, gondoskodj róla, hogy a szerver elérhető legyen, valamint az app és a szerver kompatibilis verziójú legyen.",
|
||||
"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_shared": "Megosztva",
|
||||
"control_bottom_app_bar_add_to_album": "Hozzáadás az albumhoz",
|
||||
"control_bottom_app_bar_album_info": "{} elem",
|
||||
"control_bottom_app_bar_album_info_shared": "{} elemek· Megosztva",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_archive": "Archivál",
|
||||
"control_bottom_app_bar_create_new_album": "Album létrehozása",
|
||||
"control_bottom_app_bar_delete": "Törlés",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Delete",
|
||||
"control_bottom_app_bar_favorite": "Kedvenc",
|
||||
"control_bottom_app_bar_share": "Megosztás",
|
||||
"control_bottom_app_bar_share": "Share",
|
||||
"control_bottom_app_bar_unarchive": "Archiválás megszüntetése",
|
||||
"create_album_page_untitled": "Névtelen",
|
||||
"create_shared_album_page_create": "Létrehoz",
|
||||
"create_shared_album_page_share": "Megosztás",
|
||||
"create_shared_album_page_share_add_assets": "ELEMEK HOZZÁADÁSA",
|
||||
"create_shared_album_page_share_select_photos": "Fotók kiválasztása",
|
||||
"create_album_page_untitled": "Untitled",
|
||||
"create_shared_album_page_create": "Create",
|
||||
"create_shared_album_page_share": "Share",
|
||||
"create_shared_album_page_share_add_assets": "ADD ASSETS",
|
||||
"create_shared_album_page_share_select_photos": "Select Photos",
|
||||
"curated_location_page_title": "Helyek",
|
||||
"curated_object_page_title": "Dolgok",
|
||||
"daily_title_text_date": "E, MMM dd",
|
||||
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
||||
"date_format": "E, LLL d, y • h:mm a",
|
||||
"delete_dialog_alert": "Ezek az elemek véglegesen törölve lesznek Immich-ről és az eszközödről is",
|
||||
"delete_dialog_cancel": "Mégse",
|
||||
"delete_dialog_ok": "Törlés",
|
||||
"delete_dialog_title": "Törlés véglegesen",
|
||||
"delete_dialog_alert": "These items will be permanently deleted from Immich and from your device",
|
||||
"delete_dialog_cancel": "Cancel",
|
||||
"delete_dialog_ok": "Delete",
|
||||
"delete_dialog_title": "Delete Permanently",
|
||||
"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",
|
||||
"exif_bottom_sheet_description": "Leírás hozzáadása...",
|
||||
"exif_bottom_sheet_details": "RÉSZLETEK",
|
||||
"exif_bottom_sheet_location": "HELYSZÍN",
|
||||
"experimental_settings_new_asset_list_subtitle": "Fejlesztés alatt",
|
||||
"exif_bottom_sheet_description": "Add Description...",
|
||||
"exif_bottom_sheet_details": "DETAILS",
|
||||
"exif_bottom_sheet_location": "LOCATION",
|
||||
"experimental_settings_new_asset_list_subtitle": "Work in progress",
|
||||
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
||||
"experimental_settings_subtitle": "Csak saját felelősségre használd",
|
||||
"experimental_settings_title": "Kísérleti",
|
||||
"experimental_settings_subtitle": "Use at your own risk!",
|
||||
"experimental_settings_title": "Experimental",
|
||||
"favorites_page_no_favorites": "Nem található kedvencnek jelölt média",
|
||||
"favorites_page_title": "Kedvencek",
|
||||
"favorites_page_title": "Favorites",
|
||||
"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_success": "Added {added} assets to album {album}.",
|
||||
@@ -161,42 +161,42 @@
|
||||
"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_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": "Csak 30 elemet tudsz egyszerre feltölteni, átugrás",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Letöltési Hiba",
|
||||
"image_viewer_page_state_provider_download_success": "Letöltés Sikeres",
|
||||
"library_page_albums": "Albumok",
|
||||
"library_page_albums": "Albums",
|
||||
"library_page_archive": "Archívum",
|
||||
"library_page_device_albums": "Albumok az Eszközön",
|
||||
"library_page_favorites": "Kedvencek",
|
||||
"library_page_new_album": "Új album",
|
||||
"library_page_sharing": "Megosztás\n",
|
||||
"library_page_sort_created": "Legutoljára létrehozott",
|
||||
"library_page_sort_title": "Album címe",
|
||||
"login_disabled": "A bejelentkezés letiltva",
|
||||
"library_page_favorites": "Favorites",
|
||||
"library_page_new_album": "New album",
|
||||
"library_page_sharing": "Sharing",
|
||||
"library_page_sort_created": "Most recently created",
|
||||
"library_page_sort_title": "Album title",
|
||||
"login_disabled": "Login has been disabled",
|
||||
"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": "Belépés",
|
||||
"login_form_email_hint": "teemailed@email.com",
|
||||
"login_form_endpoint_hint": "http://szerver-címe:port/api",
|
||||
"login_form_endpoint_url": "Kiszolgáló végpont címe",
|
||||
"login_form_err_http": "Kérem, adjon meg egy http:// vagy https:// címet",
|
||||
"login_form_err_invalid_email": "Érvénytelen email cím",
|
||||
"login_form_err_invalid_url": "Érvénytelen cím",
|
||||
"login_form_err_leading_whitespace": "Az első karakter szóköz",
|
||||
"login_form_err_trailing_whitespace": "Az utolsó karakter szóköz",
|
||||
"login_form_button_text": "Login",
|
||||
"login_form_email_hint": "youremail@email.com",
|
||||
"login_form_endpoint_hint": "http://your-server-ip:port/api",
|
||||
"login_form_endpoint_url": "Server Endpoint URL",
|
||||
"login_form_err_http": "Please specify http:// or https://",
|
||||
"login_form_err_invalid_email": "Invalid Email",
|
||||
"login_form_err_invalid_url": "Invalid URL",
|
||||
"login_form_err_leading_whitespace": "Leading whitespace",
|
||||
"login_form_err_trailing_whitespace": "Trailing whitespace",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "Hiba bejelentkezés közben, ellenőrizd a címet, email-t és a jelszót",
|
||||
"login_form_failed_login": "Error logging you in, check server URL, email and password",
|
||||
"login_form_label_email": "Email",
|
||||
"login_form_label_password": "Jelszó",
|
||||
"login_form_label_password": "Password",
|
||||
"login_form_next_button": "Következő",
|
||||
"login_form_password_hint": "jelszó",
|
||||
"login_form_save_login": "Maradjon bejelentkezve",
|
||||
"login_form_password_hint": "password",
|
||||
"login_form_save_login": "Stay logged in",
|
||||
"login_form_server_empty": "Add meg a szerver címét.",
|
||||
"login_form_server_error": "Nem sikerült kapcsolódni a szerverhez.",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"motion_photos_page_title": "Mozgó Fotók",
|
||||
"notification_permission_dialog_cancel": "Mégsem",
|
||||
"notification_permission_dialog_content": "Az értesítések bekapcsolásához a Beállítások menüben válaszd ki az Engedélyezés-t.",
|
||||
"notification_permission_dialog_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_settings": "Beállítások",
|
||||
"notification_permission_list_tile_content": "Értesítések engedélyezése",
|
||||
"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_grant_permission": "Engedélyezés",
|
||||
"permission_onboarding_log_out": "Kijelentkezés",
|
||||
"permission_onboarding_permission_denied": "Hozzáférés megtagadva. Az Immich használatához engedélyezni kell a fotó és videó hozzáférést a Beállításokban.",
|
||||
"permission_onboarding_permission_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_granted": "Hozzáférés engedélyezve! Minden készen áll.",
|
||||
"permission_onboarding_permission_limited": "Korlátozott hozzáférés. Ha szeretnéd, hogy az Immich a teljes galéria gyűjteményedet mentse és kezelje, akkor a Beállításokban engedélyezd a fotó és videó jogosultságokat.",
|
||||
"permission_onboarding_request": "Engedélyezni kell, hogy az Immich hozzáférjen a képekhez és videókhoz",
|
||||
"profile_drawer_app_logs": "Naplók",
|
||||
"profile_drawer_client_server_up_to_date": "Kliens és a szerver is naprakész",
|
||||
"profile_drawer_settings": "Beállítások",
|
||||
"profile_drawer_sign_out": "Kijelentkezés",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
|
||||
"profile_drawer_settings": "Settings",
|
||||
"profile_drawer_sign_out": "Sign Out",
|
||||
"recently_added_page_title": "Nemrég Hozzáadott",
|
||||
"search_bar_hint": "Keress a fotóid között",
|
||||
"search_bar_hint": "Search your photos",
|
||||
"search_page_categories": "Kategóriák",
|
||||
"search_page_favorites": "Kedvencek",
|
||||
"search_page_motion_photos": "Mozgó Fotók",
|
||||
"search_page_no_objects": "No Objects Info Available",
|
||||
"search_page_no_places": "Helyinformáció nem érhető el",
|
||||
"search_page_people": "Emberek",
|
||||
"search_page_places": "Helyszínek",
|
||||
"search_page_no_places": "No Places Info Available",
|
||||
"search_page_people": "People",
|
||||
"search_page_places": "Places",
|
||||
"search_page_recently_added": "Nemrég hozzáadott",
|
||||
"search_page_screenshots": "Képernyőképek",
|
||||
"search_page_selfies": "Szelfik",
|
||||
"search_page_things": "Dolgok",
|
||||
"search_page_things": "Things",
|
||||
"search_page_videos": "Videók",
|
||||
"search_page_view_all_button": "Összes mutatása",
|
||||
"search_page_your_activity": "Tevékenységeid",
|
||||
"search_result_page_new_search_hint": "Új keresés",
|
||||
"search_result_page_new_search_hint": "New Search",
|
||||
"search_suggestion_list_smart_search_hint_1": "Az intelligens keresés alapértelmezetten be van kapcsolva, metaadatokat így kereshetsz",
|
||||
"search_suggestion_list_smart_search_hint_2": "m:keresési-kifejezés",
|
||||
"select_additional_user_for_sharing_page_suggestions": "Javaslatok",
|
||||
"select_user_for_sharing_page_err_album": "Hiba az album létrehozása közben",
|
||||
"select_user_for_sharing_page_share_suggestions": "Javaslatok",
|
||||
"select_additional_user_for_sharing_page_suggestions": "Suggestions",
|
||||
"select_user_for_sharing_page_err_album": "Failed to create album",
|
||||
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
||||
"server_info_box_app_version": "Alkalmazás 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).",
|
||||
@@ -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_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
"setting_notifications_notify_hours": "{} óra",
|
||||
"setting_notifications_notify_immediately": "azonnal",
|
||||
"setting_notifications_notify_minutes": "{} perc",
|
||||
"setting_notifications_notify_never": "soha",
|
||||
"setting_notifications_notify_seconds": "{} másodperc",
|
||||
"setting_notifications_notify_hours": "{} hours",
|
||||
"setting_notifications_notify_immediately": "immediately",
|
||||
"setting_notifications_notify_minutes": "{} minutes",
|
||||
"setting_notifications_notify_never": "never",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
|
||||
"setting_notifications_single_progress_title": "Show background backup detail progress",
|
||||
"setting_notifications_subtitle": "Adjust your notification preferences",
|
||||
"setting_notifications_title": "Értesítések",
|
||||
"setting_notifications_title": "Notifications",
|
||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Beállítások",
|
||||
"settings_require_restart": "Kérlek indítsd újra az Immich-et hogy alkalmazd ezt a beállítást",
|
||||
"share_add": "Hozzáadás",
|
||||
"share_add_photos": "Fotók hozzáadása",
|
||||
"share_add_title": "Cím hozzáadása",
|
||||
"share_create_album": "Album létrehozása",
|
||||
"share_dialog_preparing": "Előkészítés...",
|
||||
"share_invite": "Meghívás az albumba",
|
||||
"sharing_page_album": "Megosztott albumok",
|
||||
"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": "ÜRES LISTA",
|
||||
"sharing_silver_appbar_create_shared_album": "Megosztott album létrehozása",
|
||||
"sharing_silver_appbar_share_partner": "Megosztás másokkal",
|
||||
"tab_controller_nav_library": "Könyvtár",
|
||||
"tab_controller_nav_photos": "Képek",
|
||||
"tab_controller_nav_search": "Keresés",
|
||||
"tab_controller_nav_sharing": "Megosztás",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
"share_create_album": "Create album",
|
||||
"share_dialog_preparing": "Preparing...",
|
||||
"share_invite": "Invite to album",
|
||||
"sharing_page_album": "Shared albums",
|
||||
"sharing_page_description": "Create shared albums to share photos and videos with people in your network.",
|
||||
"sharing_page_empty_list": "EMPTY LIST",
|
||||
"sharing_silver_appbar_create_shared_album": "Create shared album",
|
||||
"sharing_silver_appbar_share_partner": "Share with partner",
|
||||
"tab_controller_nav_library": "Library",
|
||||
"tab_controller_nav_photos": "Photos",
|
||||
"tab_controller_nav_search": "Search",
|
||||
"tab_controller_nav_sharing": "Sharing",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
||||
"theme_setting_dark_mode_switch": "Sötét mód",
|
||||
"theme_setting_dark_mode_switch": "Dark mode",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
|
||||
"theme_setting_image_viewer_quality_title": "Image viewer quality",
|
||||
"theme_setting_system_theme_switch": "Automatikus (követi a rendszer témáját)",
|
||||
"theme_setting_system_theme_switch": "Automatic (Follow system setting)",
|
||||
"theme_setting_theme_subtitle": "Choose the app's theme setting",
|
||||
"theme_setting_theme_title": "Téma",
|
||||
"theme_setting_theme_title": "Theme",
|
||||
"theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load",
|
||||
"theme_setting_three_stage_loading_title": "Enable three-stage loading",
|
||||
"upload_dialog_cancel": "Mégse",
|
||||
"upload_dialog_info": "Akarod menteni a kiválasztott eleme(ke)t a szerverre?",
|
||||
"upload_dialog_ok": "Feltöltés",
|
||||
"upload_dialog_title": "Elem feltöltése",
|
||||
"version_announcement_overlay_ack": "Megértettem",
|
||||
"version_announcement_overlay_release_notes": "a változtatások listáját elolvasd",
|
||||
"version_announcement_overlay_text_1": "Szia, egy új verzió érhető el",
|
||||
"version_announcement_overlay_text_2": "kérlek szánj időt arra, hogy ",
|
||||
"version_announcement_overlay_text_3": "és gyöződj meg róla, hogy a docker-compose és .env beállításai naprakészek és pontosak, különösen akkor, ha használsz watchtower-t vagy bármi olyan megoldást ami automatikusan frissíti a szervert.",
|
||||
"version_announcement_overlay_title": "Új szerververzió érhető el \uD83C\uDF89"
|
||||
"upload_dialog_cancel": "Cancel",
|
||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
||||
"upload_dialog_ok": "Upload",
|
||||
"upload_dialog_title": "Upload Asset",
|
||||
"version_announcement_overlay_ack": "Acknowledge",
|
||||
"version_announcement_overlay_release_notes": "release notes",
|
||||
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",
|
||||
"version_announcement_overlay_text_2": "please take your time to visit the ",
|
||||
"version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
|
||||
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89"
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Aggiunto in {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Già presente in {album}",
|
||||
"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": "Preferisci immagini remote.",
|
||||
"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_tile_subtitle": "Impostazioni aggiuntive utenti",
|
||||
"advanced_settings_tile_title": "Avanzato",
|
||||
"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_remove": "Rimuovere dall'album ",
|
||||
"album_viewer_page_share_add_users": "Aggiungi utenti",
|
||||
"all_people_page_title": "Persone",
|
||||
"all_people_page_title": "People",
|
||||
"all_videos_page_title": "Video",
|
||||
"archive_page_no_archived_assets": "Nessuna oggetto archiviato",
|
||||
"archive_page_title": "Archivia ({})",
|
||||
@@ -33,7 +33,7 @@
|
||||
"asset_list_layout_settings_group_by_month_day": "Mese + giorno",
|
||||
"asset_list_settings_subtitle": "Impostazion del layout della griglia delle foto",
|
||||
"asset_list_settings_title": "Griglia foto",
|
||||
"backup_album_selection_page_albums_device": "Album sul dispositivo ({})",
|
||||
"backup_album_selection_page_albums_device": "Albums sul device ({})",
|
||||
"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_select_albums": "Seleziona gli album",
|
||||
@@ -44,8 +44,8 @@
|
||||
"backup_background_service_connection_failed_message": "Impossibile connettersi al server. Riprovo…",
|
||||
"backup_background_service_current_upload_notification": "Caricamento {}",
|
||||
"backup_background_service_default_notification": "Ricerca di nuovi contenuti…",
|
||||
"backup_background_service_error_title": "Errore di backup",
|
||||
"backup_background_service_in_progress_notification": "Backup dei tuoi contenuti…",
|
||||
"backup_background_service_error_title": "Errore di Backup",
|
||||
"backup_background_service_in_progress_notification": "Backing dei tuoi contenuti…",
|
||||
"backup_background_service_upload_failure_notification": "Impossibile caricare {}",
|
||||
"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.",
|
||||
@@ -69,7 +69,7 @@
|
||||
"backup_controller_page_backup_sub": "Foto e video caricati",
|
||||
"backup_controller_page_cancel": "Cancella ",
|
||||
"backup_controller_page_created": "Creato il: {}",
|
||||
"backup_controller_page_desc_backup": "Attiva il backup per eseguire il caricamento automatico sul server all'apertura dell'applicazione.",
|
||||
"backup_controller_page_desc_backup": "Attiva il backup per eseguire il caricamento automatico sul server",
|
||||
"backup_controller_page_excluded": "Esclusi:",
|
||||
"backup_controller_page_failed": "Falliti: ({})",
|
||||
"backup_controller_page_filename": "Nome del file: {} [{}]",
|
||||
@@ -77,7 +77,7 @@
|
||||
"backup_controller_page_info": "Informazioni sul backup",
|
||||
"backup_controller_page_none_selected": "Nessuna selezione",
|
||||
"backup_controller_page_remainder": "Promemoria ",
|
||||
"backup_controller_page_remainder_sub": "Foto e album selezionati che rimangono da caricare",
|
||||
"backup_controller_page_remainder_sub": "Photo e album selezionati che rimangono da caricare",
|
||||
"backup_controller_page_select": "Seleziona ",
|
||||
"backup_controller_page_server_storage": "Spazio sul server",
|
||||
"backup_controller_page_start_backup": "Inizia backup ",
|
||||
@@ -92,11 +92,11 @@
|
||||
"backup_controller_page_uploading_file_info": "Caricando informazioni sul file",
|
||||
"backup_err_only_album": "Non è possibile rimuovere l'unico album",
|
||||
"backup_info_card_assets": "oggetti ",
|
||||
"backup_manual_cancelled": "Annullato",
|
||||
"backup_manual_failed": "Fallito",
|
||||
"backup_manual_in_progress": "Caricamento già in corso. Riprova più tardi.",
|
||||
"backup_manual_success": "Successo",
|
||||
"backup_manual_title": "Stato del caricamento",
|
||||
"backup_manual_cancelled": "Cancelled",
|
||||
"backup_manual_failed": "Failed",
|
||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
||||
"backup_manual_success": "Success",
|
||||
"backup_manual_title": "Upload status",
|
||||
"cache_settings_album_thumbnails": "Anteprime pagine librerie ({} assets)",
|
||||
"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.",
|
||||
@@ -161,7 +161,7 @@
|
||||
"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_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": "Puoi caricare al massimo 30 file per volta, ignora quelli in eccesso",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Errore nel Download",
|
||||
"image_viewer_page_state_provider_download_success": "Download con successo",
|
||||
"library_page_albums": "Album",
|
||||
@@ -172,7 +172,7 @@
|
||||
"library_page_sharing": "Condividendo",
|
||||
"library_page_sort_created": "Creato il più recente",
|
||||
"library_page_sort_title": "Titolo album",
|
||||
"login_disabled": "L'accesso è stato disattivato",
|
||||
"login_disabled": "Login has been disabled",
|
||||
"login_form_api_exception": "API error, per favore ricontrolli URL del server e riprovi",
|
||||
"login_form_button_text": "Login",
|
||||
"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_enable_button": "Attiva notifiche",
|
||||
"notification_permission_list_tile_title": "Permessi delle Notifiche",
|
||||
"partner_page_add_partner": "Aggiungi partner.",
|
||||
"partner_page_empty_message": "Le tue foto non sono ancora condivise con alcun partner.",
|
||||
"partner_page_no_more_users": "Nessun altro utente da aggiungere.",
|
||||
"partner_page_partner_add_failed": "Aggiunta del partner non riuscita.",
|
||||
"partner_page_select_partner": "Seleziona partner.",
|
||||
"partner_page_shared_to_title": "Condividi con",
|
||||
"partner_page_stop_sharing_content": "{} non sarà più in grado di accedere alle tue foto.",
|
||||
"partner_page_stop_sharing_title": "Stoppare la condivisione delle tue foto?",
|
||||
"partner_page_add_partner": "Add partner",
|
||||
"partner_page_empty_message": "Your photos are not yet shared with any partner.",
|
||||
"partner_page_no_more_users": "No more users to add",
|
||||
"partner_page_partner_add_failed": "Failed to add partner",
|
||||
"partner_page_select_partner": "Select partner",
|
||||
"partner_page_shared_to_title": "Shared to",
|
||||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_continue_anyway": "Continua lo stesso",
|
||||
"permission_onboarding_get_started": "Inizia",
|
||||
@@ -230,7 +230,7 @@
|
||||
"search_page_motion_photos": "Motion Foto",
|
||||
"search_page_no_objects": "Nessuna informazione relativa all'oggetto disponibile",
|
||||
"search_page_no_places": "Nessun informazione sul luogo disponibile",
|
||||
"search_page_people": "Persone",
|
||||
"search_page_people": "People",
|
||||
"search_page_places": "Luoghi",
|
||||
"search_page_recently_added": "Aggiunte di recente",
|
||||
"search_page_screenshots": "Screenshot",
|
||||
@@ -291,10 +291,10 @@
|
||||
"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_title": "Abilita il caricamento a tre stage",
|
||||
"upload_dialog_cancel": "Cancella",
|
||||
"upload_dialog_info": "Vuoi fare il backup sul server di ciò che hai selezionato?",
|
||||
"upload_dialog_ok": "Carica",
|
||||
"upload_dialog_title": "Carica file",
|
||||
"upload_dialog_cancel": "Cancel",
|
||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
||||
"upload_dialog_ok": "Upload",
|
||||
"upload_dialog_title": "Upload Asset",
|
||||
"version_announcement_overlay_ack": "Presa visione",
|
||||
"version_announcement_overlay_release_notes": "note di rilascio ",
|
||||
"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_err_only_album": "Kan ikke fjerne det eneste albumet",
|
||||
"backup_info_card_assets": "objekter",
|
||||
"backup_manual_cancelled": "Avbrutt",
|
||||
"backup_manual_cancelled": "Cancelled",
|
||||
"backup_manual_failed": "Feilet",
|
||||
"backup_manual_in_progress": "Opplasting er allerede i gang. Prøv igjen om litt",
|
||||
"backup_manual_success": "Vellykket",
|
||||
|
||||
@@ -92,11 +92,11 @@
|
||||
"backup_controller_page_uploading_file_info": "Bestandsgegevens uploaden",
|
||||
"backup_err_only_album": "Kan het enige album niet verwijderen",
|
||||
"backup_info_card_assets": "items",
|
||||
"backup_manual_cancelled": "Geannuleerd",
|
||||
"backup_manual_failed": "Gefaald",
|
||||
"backup_manual_in_progress": "Het uploaden is al bezig. Probeer het na een tijdje",
|
||||
"backup_manual_success": "Succes",
|
||||
"backup_manual_title": "Uploadstatus",
|
||||
"backup_manual_cancelled": "Cancelled",
|
||||
"backup_manual_failed": "Failed",
|
||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
||||
"backup_manual_success": "Success",
|
||||
"backup_manual_title": "Upload status",
|
||||
"cache_settings_album_thumbnails": "Thumbnails bibliotheekpagina ({} items)",
|
||||
"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.",
|
||||
@@ -161,7 +161,7 @@
|
||||
"home_page_building_timeline": "Tijdlijn opbouwen",
|
||||
"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_upload_err_limit": "Kan maximaal 30 assets tegelijk uploaden, overslaan",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Download mislukt",
|
||||
"image_viewer_page_state_provider_download_success": "Download succesvol",
|
||||
"library_page_albums": "Albums",
|
||||
@@ -172,7 +172,7 @@
|
||||
"library_page_sharing": "Gedeeld",
|
||||
"library_page_sort_created": "Meest recent gemaakt",
|
||||
"library_page_sort_title": "Albumtitel",
|
||||
"login_disabled": "Aanmelding uitgeschakeld",
|
||||
"login_disabled": "Login has been disabled",
|
||||
"login_form_api_exception": "API fout. Controleer de server URL en probeer opnieuw.",
|
||||
"login_form_button_text": "Inloggen",
|
||||
"login_form_email_hint": "jouwemail@email.com",
|
||||
@@ -291,10 +291,10 @@
|
||||
"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_title": "Laden in drie fasen inschakelen",
|
||||
"upload_dialog_cancel": "Annuleren",
|
||||
"upload_dialog_info": "Wilt u een backup maken van de geselecteerde Asset(s) op de server?",
|
||||
"upload_dialog_cancel": "Cancel",
|
||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
||||
"upload_dialog_ok": "Upload",
|
||||
"upload_dialog_title": "Asset uploaden",
|
||||
"upload_dialog_title": "Upload Asset",
|
||||
"version_announcement_overlay_ack": "Bevestig",
|
||||
"version_announcement_overlay_release_notes": "releaseopmerkingen",
|
||||
"version_announcement_overlay_text_1": "Hoi, er is een nieuwe versie beschikbaar van",
|
||||
|
||||
+134
-134
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Dodano do {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Już w {album}",
|
||||
"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": "Preferuj obrazy zdalne",
|
||||
"advanced_settings_tile_subtitle": "Zaawansowane ustawienia użytkownika",
|
||||
"advanced_settings_tile_title": "Zaawansowane",
|
||||
"advanced_settings_troubleshooting_subtitle": "Włącz dodatkowe funkcje rozwiązywania problemów",
|
||||
"advanced_settings_troubleshooting_title": "Rozwiązywanie problemów",
|
||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Already 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_title": "Prefer remote images",
|
||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
||||
"advanced_settings_tile_title": "Advanced",
|
||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
||||
"album_info_card_backup_album_excluded": "WYKLUCZONE",
|
||||
"album_info_card_backup_album_included": "WŁĄCZONE",
|
||||
"album_thumbnail_card_item": "1 pozycja",
|
||||
"album_thumbnail_card_items": "{} pozycje",
|
||||
"album_thumbnail_card_shared": "Udostępniony",
|
||||
"album_thumbnail_owned": "Posiadany",
|
||||
"album_thumbnail_shared_by": "Udostępnione przez {}",
|
||||
"album_thumbnail_owned": "Owned",
|
||||
"album_thumbnail_shared_by": "Shared by {}",
|
||||
"album_viewer_appbar_share_delete": "Usuń album",
|
||||
"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",
|
||||
@@ -22,15 +22,15 @@
|
||||
"album_viewer_appbar_share_leave": "Opuść album",
|
||||
"album_viewer_appbar_share_remove": "Usuń z albumu",
|
||||
"album_viewer_page_share_add_users": "Dodaj użytkowników",
|
||||
"all_people_page_title": "Ludzie",
|
||||
"all_videos_page_title": "Filmy",
|
||||
"archive_page_no_archived_assets": "Nie znaleziono zarchiwizowanych zasobów",
|
||||
"archive_page_title": "Archiwum ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Układ dynamiczny",
|
||||
"asset_list_layout_settings_group_automatically": "Automatyczny",
|
||||
"asset_list_layout_settings_group_by": "Grupuj zasoby według",
|
||||
"asset_list_layout_settings_group_by_month": "Miesiąc",
|
||||
"asset_list_layout_settings_group_by_month_day": "Miesiąc + dzień",
|
||||
"all_people_page_title": "People",
|
||||
"all_videos_page_title": "Videos",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Archive ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dynamic layout",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Group assets by",
|
||||
"asset_list_layout_settings_group_by_month": "Month",
|
||||
"asset_list_layout_settings_group_by_month_day": "Month + day",
|
||||
"asset_list_settings_subtitle": "Ustawienia układu siatki zdjęć",
|
||||
"asset_list_settings_title": "Siatka Zdjęć",
|
||||
"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_upload_failure_notification": "Nie udało się przesłać {}",
|
||||
"backup_controller_page_albums": "Backup Albumów",
|
||||
"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": "Odświeżanie aplikacji w tle wyłączone",
|
||||
"backup_controller_page_background_app_refresh_enable_button_text": "Przejdź do ustawień",
|
||||
"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_enable_button_text": "Go to settings",
|
||||
"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_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Optymalizacja Baterii",
|
||||
"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_delay": "Opóźnij tworzenie kopii zapasowych nowych zasobów: {}",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"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_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_err_only_album": "Nie można usunąć tylko i wyłącznie albumu",
|
||||
"backup_info_card_assets": "zasoby",
|
||||
"backup_manual_cancelled": "Anulowano",
|
||||
"backup_manual_failed": "Niepowodzenie",
|
||||
"backup_manual_in_progress": "Przesyłanie już trwa. Spróbuj po pewnym czasie",
|
||||
"backup_manual_success": "Sukces",
|
||||
"backup_manual_title": "Stan przesyłania",
|
||||
"backup_manual_cancelled": "Cancelled",
|
||||
"backup_manual_failed": "Failed",
|
||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
||||
"backup_manual_success": "Success",
|
||||
"backup_manual_title": "Upload status",
|
||||
"cache_settings_album_thumbnails": "Miniatury stron bibliotek ({} zasobów)",
|
||||
"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.",
|
||||
@@ -110,32 +110,32 @@
|
||||
"cache_settings_subtitle": "Kontrolowanie zachowania buforowania aplikacji mobilnej Immich",
|
||||
"cache_settings_thumbnail_size": "Rozmiar pamięci podręcznej miniatur ({} zasobów)",
|
||||
"cache_settings_title": "Ustawienia Buforowania",
|
||||
"change_password_form_confirm_password": "Potwierdź Hasło",
|
||||
"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": "Nowe Hasło",
|
||||
"change_password_form_password_mismatch": "Hasła nie są zgodne",
|
||||
"change_password_form_reenter_new_password": "Wprowadź ponownie Nowe Hasło",
|
||||
"common_add_to_album": "Dodaj do albumu",
|
||||
"common_change_password": "Zmień Hasło",
|
||||
"common_create_new_album": "Utwórz nowy album",
|
||||
"common_server_error": "Sprawdź połączenie sieciowe, upewnij się, że serwer jest osiągalny i wersje aplikacji/serwera są kompatybilne.",
|
||||
"common_shared": "Udostępnione",
|
||||
"control_bottom_app_bar_add_to_album": "Dodaj do albumu",
|
||||
"control_bottom_app_bar_album_info": "{} pozycji",
|
||||
"control_bottom_app_bar_album_info_shared": "{} pozycji · Udostępnionych",
|
||||
"control_bottom_app_bar_archive": "Archiwum",
|
||||
"control_bottom_app_bar_create_new_album": "Utwórz nowy album",
|
||||
"change_password_form_confirm_password": "Confirm Password",
|
||||
"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_new_password": "New Password",
|
||||
"change_password_form_password_mismatch": "Passwords do not match",
|
||||
"change_password_form_reenter_new_password": "Re-enter New Password",
|
||||
"common_add_to_album": "Add to album",
|
||||
"common_change_password": "Change Password",
|
||||
"common_create_new_album": "Create new album",
|
||||
"common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.",
|
||||
"common_shared": "Shared",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_archive": "Archive",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Usuń",
|
||||
"control_bottom_app_bar_favorite": "Ulubione",
|
||||
"control_bottom_app_bar_favorite": "Favorite",
|
||||
"control_bottom_app_bar_share": "Udostępnij",
|
||||
"control_bottom_app_bar_unarchive": "Cofnij archiwizację",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "Bez tytułu",
|
||||
"create_shared_album_page_create": "Utwórz",
|
||||
"create_shared_album_page_share": "Udostępnij",
|
||||
"create_shared_album_page_share_add_assets": "DODAJ ZASOBY",
|
||||
"create_shared_album_page_share_select_photos": "Zaznacz Zdjęcia",
|
||||
"curated_location_page_title": "Miejsca",
|
||||
"curated_object_page_title": "Rzeczy",
|
||||
"curated_location_page_title": "Places",
|
||||
"curated_object_page_title": "Things",
|
||||
"daily_title_text_date": "E, MMM dd",
|
||||
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
||||
"date_format": "E, LLL d, y • h:mm a",
|
||||
@@ -143,129 +143,129 @@
|
||||
"delete_dialog_cancel": "Anuluj",
|
||||
"delete_dialog_ok": "Usuń",
|
||||
"delete_dialog_title": "Usuń trwale",
|
||||
"description_input_hint_text": "Dodaj opis...",
|
||||
"description_input_submit_error": "Błąd aktualizacji opisu, sprawdź dziennik, aby uzyskać więcej szczegółów",
|
||||
"description_input_hint_text": "Add description...",
|
||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
||||
"exif_bottom_sheet_description": "Dodaj Opis...",
|
||||
"exif_bottom_sheet_details": "SZCZEGÓŁY",
|
||||
"exif_bottom_sheet_location": "LOKALIZACJA",
|
||||
"experimental_settings_new_asset_list_subtitle": "Praca w toku",
|
||||
"experimental_settings_new_asset_list_title": "Włącz eksperymentalną układ zdjęć",
|
||||
"experimental_settings_subtitle": "Używaj na własne ryzyko!",
|
||||
"experimental_settings_title": "Eksperymentalny",
|
||||
"favorites_page_no_favorites": "Nie znaleziono ulubionych zasobów",
|
||||
"favorites_page_title": "Ulubione",
|
||||
"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": "Nie można dodawać zasobów lokalnych do albumów, pomijam",
|
||||
"home_page_add_to_album_success": "Dodano {added} zasoby do albumu {album}.",
|
||||
"home_page_archive_err_local": "Nie można jeszcze zarchiwizować zasobów lokalnych, pomijanie",
|
||||
"home_page_building_timeline": "Budowanie osi czasu",
|
||||
"home_page_favorite_err_local": "Nie można dodać do ulubionych lokalnych zasobów, pomijam",
|
||||
"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": "Można przesłać maksymalnie 30 zasobów jednocześnie, pomijanie",
|
||||
"image_viewer_page_state_provider_download_error": "Błąd pobierania",
|
||||
"image_viewer_page_state_provider_download_success": "Pobieranie zakończone",
|
||||
"experimental_settings_new_asset_list_subtitle": "Work in progress",
|
||||
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
||||
"experimental_settings_subtitle": "Use at your own risk!",
|
||||
"experimental_settings_title": "Experimental",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Favorites",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_building_timeline": "Building the timeline",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
"library_page_albums": "Albumy",
|
||||
"library_page_archive": "Archiwum",
|
||||
"library_page_device_albums": "Albumy na Urządzeniu",
|
||||
"library_page_favorites": "Ulubione",
|
||||
"library_page_archive": "Archive",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_favorites": "Favorites",
|
||||
"library_page_new_album": "Nowy album",
|
||||
"library_page_sharing": "Udostępnianie",
|
||||
"library_page_sort_created": "Ostatnio utworzone",
|
||||
"library_page_sort_title": "Tytuł albumu",
|
||||
"login_disabled": "Logowanie zostało wyłączone",
|
||||
"login_form_api_exception": "Wyjątek API. Sprawdź adres URL serwera i spróbuj ponownie.",
|
||||
"library_page_sharing": "Sharing",
|
||||
"library_page_sort_created": "Most recently created",
|
||||
"library_page_sort_title": "Album title",
|
||||
"login_disabled": "Login has been disabled",
|
||||
"login_form_api_exception": "API exception. Please check the server URL and try again.",
|
||||
"login_form_button_text": "Login",
|
||||
"login_form_email_hint": "twojmail@email.com",
|
||||
"login_form_endpoint_hint": "http://ip-twojego-serwera:port/api",
|
||||
"login_form_endpoint_url": "URL Serwera",
|
||||
"login_form_err_http": "Proszę określić http:// lub https://",
|
||||
"login_form_err_invalid_email": "Niepoprawny Email",
|
||||
"login_form_err_invalid_url": "Nieprawidłowy URL",
|
||||
"login_form_err_invalid_url": "Invalid URL",
|
||||
"login_form_err_leading_whitespace": "Białe znaki",
|
||||
"login_form_err_trailing_whitespace": "Białe znaki po przecinku",
|
||||
"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": "Funkcja OAuth nie jest dostępna na tym serwerze",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "Błąd logowania, sprawdź adres url serwera, email i hasło.",
|
||||
"login_form_label_email": "Email",
|
||||
"login_form_label_password": "Hasło",
|
||||
"login_form_next_button": "Dalej",
|
||||
"login_form_next_button": "Next",
|
||||
"login_form_password_hint": "hasło",
|
||||
"login_form_save_login": "Pozostań zalogowany",
|
||||
"login_form_server_empty": "Wprowadź adres URL serwera.",
|
||||
"login_form_server_error": "Nie można połączyć się z serwerem.",
|
||||
"login_form_server_empty": "Enter a server URL.",
|
||||
"login_form_server_error": "Could not connect to server.",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"motion_photos_page_title": "Zdjęcia ruchome",
|
||||
"notification_permission_dialog_cancel": "Anuluj",
|
||||
"notification_permission_dialog_content": "Aby włączyć powiadomienia, przejdź do Ustawień i wybierz opcję Zezwalaj.",
|
||||
"notification_permission_dialog_settings": "Ustawienia",
|
||||
"notification_permission_list_tile_content": "Przyznaj uprawnienia, aby włączyć powiadomienia.",
|
||||
"notification_permission_list_tile_enable_button": "Włącz Powiadomienia",
|
||||
"notification_permission_list_tile_title": "Pozwolenie na powiadomienia",
|
||||
"partner_page_add_partner": "Dodaj partnera",
|
||||
"partner_page_empty_message": "Twoje zdjęcia nie są udostępnione żadnemu partnerowi",
|
||||
"partner_page_no_more_users": "Brak użytkowników do dodania",
|
||||
"partner_page_partner_add_failed": "Nie udało się dodać partnera",
|
||||
"partner_page_select_partner": "Wybierz partnera",
|
||||
"partner_page_shared_to_title": "Udostępniono",
|
||||
"partner_page_stop_sharing_content": "{} nie będziesz już mieć dostępu do swoich zdjęć.",
|
||||
"partner_page_stop_sharing_title": "Przestać udostępniać swoje zdjęcia?",
|
||||
"motion_photos_page_title": "Motion Photos",
|
||||
"notification_permission_dialog_cancel": "Cancel",
|
||||
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
|
||||
"notification_permission_dialog_settings": "Settings",
|
||||
"notification_permission_list_tile_content": "Grant permission to enable notifications.",
|
||||
"notification_permission_list_tile_enable_button": "Enable Notifications",
|
||||
"notification_permission_list_tile_title": "Notification Permission",
|
||||
"partner_page_add_partner": "Add partner",
|
||||
"partner_page_empty_message": "Your photos are not yet shared with any partner.",
|
||||
"partner_page_no_more_users": "No more users to add",
|
||||
"partner_page_partner_add_failed": "Failed to add partner",
|
||||
"partner_page_select_partner": "Select partner",
|
||||
"partner_page_shared_to_title": "Shared to",
|
||||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_continue_anyway": "Kontynuuj mimo to",
|
||||
"permission_onboarding_get_started": "Rozpocznij",
|
||||
"permission_onboarding_go_to_settings": "Przejdź do ustawień",
|
||||
"permission_onboarding_grant_permission": "Wydaj pozwolenie",
|
||||
"permission_onboarding_log_out": "Wyloguj",
|
||||
"permission_onboarding_permission_denied": "Odmowa pozwolenia. Aby korzystać z Immich, przyznaj uprawnienia do zdjęć i filmów w Ustawieniach.",
|
||||
"permission_onboarding_permission_granted": "Pozwolenie udzielone! Wszystko gotowe.",
|
||||
"permission_onboarding_permission_limited": "Pozwolenie ograniczone. Aby umożliwić Immichowi tworzenie kopii zapasowych całej kolekcji galerii i zarządzanie nią, przyznaj uprawnienia do zdjęć i filmów w Ustawieniach.",
|
||||
"permission_onboarding_request": "Immich potrzebuje pozwolenia na przeglądanie Twoich zdjęć i filmów.",
|
||||
"profile_drawer_app_logs": "Logi",
|
||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
||||
"permission_onboarding_get_started": "Get started",
|
||||
"permission_onboarding_go_to_settings": "Go to settings",
|
||||
"permission_onboarding_grant_permission": "Grant permission",
|
||||
"permission_onboarding_log_out": "Log out",
|
||||
"permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.",
|
||||
"permission_onboarding_permission_granted": "Permission granted! You are all set.",
|
||||
"permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.",
|
||||
"permission_onboarding_request": "Immich requires permission to view your photos and videos.",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "Klient i serwer są aktualne",
|
||||
"profile_drawer_settings": "Ustawienia",
|
||||
"profile_drawer_sign_out": "Wyloguj się",
|
||||
"recently_added_page_title": "Ostatnio Dodane",
|
||||
"recently_added_page_title": "Recently Added",
|
||||
"search_bar_hint": "Szukaj swoich zdjęć",
|
||||
"search_page_categories": "Kategorie",
|
||||
"search_page_favorites": "Ulubione",
|
||||
"search_page_motion_photos": "Zdjęcia ruchome",
|
||||
"search_page_categories": "Categories",
|
||||
"search_page_favorites": "Favorites",
|
||||
"search_page_motion_photos": "Motion Photos",
|
||||
"search_page_no_objects": "Brak informacji o obiektach",
|
||||
"search_page_no_places": "Brak informacji o miejscu",
|
||||
"search_page_people": "Ludzie",
|
||||
"search_page_people": "People",
|
||||
"search_page_places": "Miejsca",
|
||||
"search_page_recently_added": "Ostatnio dodane",
|
||||
"search_page_screenshots": "Zrzuty ekranu",
|
||||
"search_page_selfies": "Selfi",
|
||||
"search_page_recently_added": "Recently added",
|
||||
"search_page_screenshots": "Screenshots",
|
||||
"search_page_selfies": "Selfies",
|
||||
"search_page_things": "Rzeczy",
|
||||
"search_page_videos": "Filmy",
|
||||
"search_page_view_all_button": "Pokaż wszystkie",
|
||||
"search_page_your_activity": "Twoja aktywność",
|
||||
"search_page_videos": "Videos",
|
||||
"search_page_view_all_button": "View all",
|
||||
"search_page_your_activity": "Your activity",
|
||||
"search_result_page_new_search_hint": "Nowe wyszukiwanie",
|
||||
"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_1": "Smart search is enabled by default, to search for metadata use the syntax ",
|
||||
"search_suggestion_list_smart_search_hint_2": "m:your-search-term",
|
||||
"select_additional_user_for_sharing_page_suggestions": "Propozycje",
|
||||
"select_user_for_sharing_page_err_album": "Nie udało się utworzyć albumu",
|
||||
"select_user_for_sharing_page_share_suggestions": "Propozycje",
|
||||
"server_info_box_app_version": "Wersja Aplikacji",
|
||||
"server_info_box_server_version": "Wersja Serwera",
|
||||
"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": "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": "Załaduj oryginalny obraz",
|
||||
"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": "Załaduj obraz podglądu",
|
||||
"server_info_box_app_version": "App Version",
|
||||
"server_info_box_server_version": "Server Version",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "Powiadomienie o awariach kopii zapasowych w tle: {}",
|
||||
"setting_notifications_notify_hours": "{} godzin",
|
||||
"setting_notifications_notify_immediately": "natychmiast",
|
||||
"setting_notifications_notify_minutes": "{} minut",
|
||||
"setting_notifications_notify_never": "nigdy",
|
||||
"setting_notifications_notify_seconds": "{} sekund",
|
||||
"setting_notifications_single_progress_subtitle": "Szczegółowe informacje o postępie przesyłania dla każdego zasobu",
|
||||
"setting_notifications_single_progress_title": "Pokaż postęp szczegółów kopii zapasowej w tle",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
|
||||
"setting_notifications_single_progress_title": "Show background backup detail progress",
|
||||
"setting_notifications_subtitle": "Dostosuj preferencje powiadomień",
|
||||
"setting_notifications_title": "Powiadomienia",
|
||||
"setting_notifications_total_progress_subtitle": "Ogólny postęp przesyłania (gotowe/całkowite zasoby)",
|
||||
"setting_notifications_total_progress_title": "Pokaż całkowity postęp tworzenia kopii zapasowej w tle",
|
||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Ustawienia",
|
||||
"settings_require_restart": "Aby zastosować to ustawienie, uruchom ponownie Immich",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"share_add": "Dodaj",
|
||||
"share_add_photos": "Dodaj zdjęcia",
|
||||
"share_add_title": "Dodaj tytuł",
|
||||
@@ -282,7 +282,7 @@
|
||||
"tab_controller_nav_search": "Szukaj",
|
||||
"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_tiles_per_row_title": "Liczba zasobów w wierszu ({})",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
||||
"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_title": "Jakość przeglądania obrazów",
|
||||
@@ -291,10 +291,10 @@
|
||||
"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_title": "Włączenie trójstopniowego ładowania",
|
||||
"upload_dialog_cancel": "Anuluj",
|
||||
"upload_dialog_info": "Czy chcesz wykonać kopię zapasową wybranych zasobów na serwerze?",
|
||||
"upload_dialog_ok": "Prześlij",
|
||||
"upload_dialog_title": "Prześlij Zasób",
|
||||
"upload_dialog_cancel": "Cancel",
|
||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
||||
"upload_dialog_ok": "Upload",
|
||||
"upload_dialog_title": "Upload Asset",
|
||||
"version_announcement_overlay_ack": "Potwierdzam",
|
||||
"version_announcement_overlay_release_notes": "informacje o wydaniu",
|
||||
"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_already_exists": "Уже в {album}",
|
||||
"advanced_settings_prefer_remote_subtitle": "Некоторые устройства очень медленно загружают предпросмотр объектов, находящихся на устройстве. Активируйте эту настройку, чтобы вместо них загружались изображени с сервера.",
|
||||
"advanced_settings_prefer_remote_title": "Предпочитать фото на сервере",
|
||||
"advanced_settings_tile_subtitle": "Расширенные настройки пользователя",
|
||||
"advanced_settings_tile_title": "Расширенные",
|
||||
"advanced_settings_troubleshooting_subtitle": "Включить расширенные возможности для решения проблем",
|
||||
"advanced_settings_troubleshooting_title": "Решение проблем",
|
||||
"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_tile_subtitle": "Advanced user's settings",
|
||||
"advanced_settings_tile_title": "Advanced",
|
||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
||||
"album_info_card_backup_album_excluded": "ИСКЛЮЧЕН",
|
||||
"album_info_card_backup_album_included": "ВКЛЮЧЕН",
|
||||
"album_thumbnail_card_item": "1 объект",
|
||||
"album_thumbnail_card_items": "{} объектов",
|
||||
"album_thumbnail_card_shared": "· Общий",
|
||||
"album_thumbnail_owned": "Автор",
|
||||
"album_thumbnail_shared_by": "Поделился {}",
|
||||
"album_thumbnail_owned": "Owned",
|
||||
"album_thumbnail_shared_by": "Shared by {}",
|
||||
"album_viewer_appbar_share_delete": "Удалить альбом",
|
||||
"album_viewer_appbar_share_err_delete": "Невозможно удалить альбом",
|
||||
"album_viewer_appbar_share_err_leave": "Невозможно покинуть альбом",
|
||||
@@ -22,15 +22,15 @@
|
||||
"album_viewer_appbar_share_leave": "Покинуть альбом",
|
||||
"album_viewer_appbar_share_remove": "Удалить из альбома",
|
||||
"album_viewer_page_share_add_users": "Добавить пользователей",
|
||||
"all_people_page_title": "Люди",
|
||||
"all_videos_page_title": "Видео",
|
||||
"archive_page_no_archived_assets": "В архиве сейчас пусто",
|
||||
"archive_page_title": "Архив ({})",
|
||||
"all_people_page_title": "People",
|
||||
"all_videos_page_title": "Videos",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Archive ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Динамическое расположение",
|
||||
"asset_list_layout_settings_group_automatically": "Автоматически",
|
||||
"asset_list_layout_settings_group_by": "Группировать объекты по:",
|
||||
"asset_list_layout_settings_group_by_month": "Месяцу",
|
||||
"asset_list_layout_settings_group_by_month_day": "Месяцу и дню",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Группировать объекты по",
|
||||
"asset_list_layout_settings_group_by_month": "месяцу",
|
||||
"asset_list_layout_settings_group_by_month_day": "месяцу и дню",
|
||||
"asset_list_settings_subtitle": "Настройки макета сетки фотографий",
|
||||
"asset_list_settings_title": "Сетка фотографий",
|
||||
"backup_album_selection_page_albums_device": "Альбомов на устройстве ({})",
|
||||
@@ -48,9 +48,9 @@
|
||||
"backup_background_service_in_progress_notification": "Резервное копирование ваших объектов…",
|
||||
"backup_background_service_upload_failure_notification": "Ошибка загрузки {}",
|
||||
"backup_controller_page_albums": "Резервное копирование альбомов",
|
||||
"backup_controller_page_background_app_refresh_disabled_content": "Включите фоновое обновление приложений в меню Настройки > Общие > Фоновое обновление приложений, чтобы использовать фоновое резервное копирование.",
|
||||
"backup_controller_page_background_app_refresh_disabled_title": "Фоновое обновление отключено",
|
||||
"backup_controller_page_background_app_refresh_enable_button_text": "Перейти в настройки",
|
||||
"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_enable_button_text": "Go to settings",
|
||||
"backup_controller_page_background_battery_info_link": "Показать как",
|
||||
"backup_controller_page_background_battery_info_message": "Для наилучшего фонового резервного копирования отключите любые настройки оптимизации батареи, ограничивающие фоновую активность для Immich.\n\nПоскольку это зависит от устройства, найдите необходимую информацию для производителя вашего устройства.",
|
||||
"backup_controller_page_background_battery_info_ok": "ОК",
|
||||
@@ -68,8 +68,8 @@
|
||||
"backup_controller_page_backup_selected": "Выбрано: ",
|
||||
"backup_controller_page_backup_sub": "Загруженные фото и видео",
|
||||
"backup_controller_page_cancel": "Отмена",
|
||||
"backup_controller_page_created": "Создано: {}",
|
||||
"backup_controller_page_desc_backup": "Включите резервное копирование в активном режиме, чтобы автоматически загружать новые объекты на сервер при открытии приложения.",
|
||||
"backup_controller_page_created": "Создано на: {}",
|
||||
"backup_controller_page_desc_backup": "Включите резервное копирование в активном режиме, чтобы автоматически загружать новые активы на сервер при открытии приложения.",
|
||||
"backup_controller_page_excluded": "Исключены:",
|
||||
"backup_controller_page_failed": "Неудачных ({})",
|
||||
"backup_controller_page_filename": "Имя файла: {} [{}]",
|
||||
@@ -89,14 +89,14 @@
|
||||
"backup_controller_page_total_sub": "Все уникальные фото и видео из выбранных альбомов",
|
||||
"backup_controller_page_turn_off": "Выключить резервное копирование в активном режиме",
|
||||
"backup_controller_page_turn_on": "Включить резервное копирование в активном режиме",
|
||||
"backup_controller_page_uploading_file_info": "Информация о загружаемом файле",
|
||||
"backup_controller_page_uploading_file_info": "Загрузка информации о файле",
|
||||
"backup_err_only_album": "Невозможно удалить единственный альбом",
|
||||
"backup_info_card_assets": "объектов",
|
||||
"backup_manual_cancelled": "Отменено",
|
||||
"backup_manual_failed": "Неудачно",
|
||||
"backup_manual_in_progress": "Загрузка уже в процессе, попробуйте позже",
|
||||
"backup_manual_success": "Успешно",
|
||||
"backup_manual_title": "Статус загрузки",
|
||||
"backup_info_card_assets": "объекты",
|
||||
"backup_manual_cancelled": "Cancelled",
|
||||
"backup_manual_failed": "Failed",
|
||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
||||
"backup_manual_success": "Success",
|
||||
"backup_manual_title": "Upload status",
|
||||
"cache_settings_album_thumbnails": "Миниатюры страниц библиотеки ({} объектов)",
|
||||
"cache_settings_clear_cache_button": "Очистить кэш",
|
||||
"cache_settings_clear_cache_button_title": "Очищает кэш приложения. Это значительно повлияет на производительность приложения, до тех пор, пока кэш не будет перестроен заново.",
|
||||
@@ -118,66 +118,66 @@
|
||||
"common_add_to_album": "Добавить в альбом",
|
||||
"common_change_password": "Изменить пароль",
|
||||
"common_create_new_album": "Создать новый альбом",
|
||||
"common_server_error": "Пожалуйста, проверьте подключение к сети и убедитесь, что ваш сервер доступен, а версии приложения и сервера — совместимы.",
|
||||
"common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.",
|
||||
"common_shared": "Общие",
|
||||
"control_bottom_app_bar_add_to_album": "Добавить в альбом",
|
||||
"control_bottom_app_bar_album_info": "{} файлов",
|
||||
"control_bottom_app_bar_album_info_shared": "{} файлов · Общий",
|
||||
"control_bottom_app_bar_archive": "Архив",
|
||||
"control_bottom_app_bar_archive": "Archive",
|
||||
"control_bottom_app_bar_create_new_album": "\nСоздать новый альбом",
|
||||
"control_bottom_app_bar_delete": "Удалить",
|
||||
"control_bottom_app_bar_favorite": "Избранное",
|
||||
"control_bottom_app_bar_share": "Поделиться",
|
||||
"control_bottom_app_bar_unarchive": "Восстановить",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "Без названия",
|
||||
"create_shared_album_page_create": "Создать",
|
||||
"create_shared_album_page_share": "Поделиться",
|
||||
"create_shared_album_page_share_add_assets": "ДОБАВИТЬ ОБЪЕКТЫ",
|
||||
"create_shared_album_page_share_select_photos": "Выберите фотографии",
|
||||
"curated_location_page_title": "Места",
|
||||
"curated_object_page_title": "Предметы",
|
||||
"curated_location_page_title": "Places",
|
||||
"curated_object_page_title": "Things",
|
||||
"daily_title_text_date": "E, MMM dd",
|
||||
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
||||
"date_format": "E, LLL d, y • h:mm a",
|
||||
"delete_dialog_alert": "Эти элементы будут безвозвратно удалены из приложения, а также с вашего устройства",
|
||||
"delete_dialog_alert": "Эти объекты будут безвозвратно удалены из приложения, а также с вашего устройства",
|
||||
"delete_dialog_cancel": "Отменить",
|
||||
"delete_dialog_ok": "Удалить",
|
||||
"delete_dialog_title": "Удалить навсегда",
|
||||
"description_input_hint_text": "Добавить описание...",
|
||||
"description_input_submit_error": "Не удалось обновить описание, проверьте логи, чтобы узнать причину",
|
||||
"description_input_hint_text": "Add description...",
|
||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
||||
"exif_bottom_sheet_description": "Добавить описание...",
|
||||
"exif_bottom_sheet_details": "ПОДРОБНОСТИ",
|
||||
"exif_bottom_sheet_location": "МЕСТОПОЛОЖЕНИЕ",
|
||||
"experimental_settings_new_asset_list_subtitle": "Ведутся работы",
|
||||
"experimental_settings_new_asset_list_subtitle": "Работа ведётся",
|
||||
"experimental_settings_new_asset_list_title": "Включить экспериментальную сетку фотографий",
|
||||
"experimental_settings_subtitle": "Используйте на свой страх и риск!",
|
||||
"experimental_settings_title": "Экспериментальные функции",
|
||||
"favorites_page_no_favorites": "В избранном сейчас пусто",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Избранное",
|
||||
"home_page_add_to_album_conflicts": "Добавлено {added} объектов в альбом {album}. Объекты {failed} уже есть в альбоме.",
|
||||
"home_page_add_to_album_err_local": "Пока нельзя добавлять локальные объекты в альбомы, пропускаем",
|
||||
"home_page_add_to_album_success": "Добавлено {added} объектов в альбом {album}.",
|
||||
"home_page_archive_err_local": "Пока невозможно добавить локальные объекты в архив, пропускаем",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_building_timeline": "Построение временной шкалы",
|
||||
"home_page_favorite_err_local": "Пока не удается добавить в избранное локальные объекты, пропускаем",
|
||||
"home_page_first_time_notice": "Если вы используете приложение впервые, убедитесь, что вы выбрали резервный(е) альбом(ы), чтобы временная шкала могла заполнить фотографии и видео в альбоме(ах).",
|
||||
"home_page_upload_err_limit": "Вы можете выгрузить максимум 30 файлов за раз",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Ошибка загрузки",
|
||||
"image_viewer_page_state_provider_download_success": "Успешно загружено",
|
||||
"library_page_albums": "Альбомы",
|
||||
"library_page_archive": "Архив",
|
||||
"library_page_device_albums": "Альбомы на устройстве",
|
||||
"library_page_archive": "Archive",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_favorites": "Избранное",
|
||||
"library_page_new_album": "Новый альбом",
|
||||
"library_page_sharing": "Общие",
|
||||
"library_page_sort_created": "По новизне",
|
||||
"library_page_sort_title": "По названию альбома",
|
||||
"login_disabled": "Вход отключен",
|
||||
"login_form_api_exception": "Ошибка при попытке взаимодействия с сервером. Проверьте URL-адрес до него и попробуйте еще раз.",
|
||||
"login_disabled": "Login has been disabled",
|
||||
"login_form_api_exception": "API exception. Please check the server URL and try again.",
|
||||
"login_form_button_text": "Войти",
|
||||
"login_form_email_hint": "youremail@email.com",
|
||||
"login_form_endpoint_hint": "http://your-server-ip:port/api",
|
||||
"login_form_endpoint_url": "URL-aдрес сервера",
|
||||
"login_form_endpoint_url": "URL Адрес сервера",
|
||||
"login_form_err_http": "Пожалуйста, укажите http:// или https://",
|
||||
"login_form_err_invalid_email": "Неверный адрес Email",
|
||||
"login_form_err_invalid_url": "Неверная ссылка",
|
||||
@@ -188,60 +188,60 @@
|
||||
"login_form_failed_login": "Ошибка при входе в систему, проверьте URL-адрес сервера, адрес электронной почты и пароль",
|
||||
"login_form_label_email": "Email",
|
||||
"login_form_label_password": "Пароль",
|
||||
"login_form_next_button": "Далее",
|
||||
"login_form_next_button": "Next",
|
||||
"login_form_password_hint": "пароль",
|
||||
"login_form_save_login": "Оставаться в системе",
|
||||
"login_form_server_empty": "Введите URL-адрес вашего сервера.",
|
||||
"login_form_server_error": "Нет соединения с сервером.",
|
||||
"login_form_server_empty": "Enter a server URL.",
|
||||
"login_form_server_error": "Could not connect to server.",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"motion_photos_page_title": "Динамические фото",
|
||||
"motion_photos_page_title": "Motion Photos",
|
||||
"notification_permission_dialog_cancel": "Отмена",
|
||||
"notification_permission_dialog_content": "Чтобы включить уведомления, перейдите в «Настройки» и выберите «Разрешить».",
|
||||
"notification_permission_dialog_settings": "Настройки",
|
||||
"notification_permission_list_tile_content": "Предоставьте разрешение на включение уведомлений",
|
||||
"notification_permission_list_tile_enable_button": "Включить уведомления",
|
||||
"notification_permission_list_tile_title": "Разрешение на уведомление",
|
||||
"partner_page_add_partner": "Добавить партнёра",
|
||||
"partner_page_empty_message": "У вашего партнёра еще пока нет доступа к вашим фото",
|
||||
"partner_page_no_more_users": "Выбраны все доступные пользователи",
|
||||
"partner_page_partner_add_failed": "Не удалось добавить партнёра",
|
||||
"partner_page_select_partner": "Выбрать партнёра",
|
||||
"partner_page_shared_to_title": "Поделиться с...",
|
||||
"partner_page_stop_sharing_content": "{} больше не сможет получить доступ к вашим фотографиям",
|
||||
"partner_page_stop_sharing_title": "Закрыть доступ партнёра к вашим фото?",
|
||||
"partner_page_title": "Партнёр",
|
||||
"permission_onboarding_continue_anyway": "Все равно продолжить",
|
||||
"permission_onboarding_get_started": "Давайте начнём",
|
||||
"permission_onboarding_go_to_settings": "Перейти в настройки",
|
||||
"permission_onboarding_grant_permission": "Предоставить разрешение",
|
||||
"permission_onboarding_log_out": "Выйти",
|
||||
"permission_onboarding_permission_denied": "Не удалось получить доступ.",
|
||||
"permission_onboarding_permission_granted": "Доступ получен! Всё готово.",
|
||||
"permission_onboarding_permission_limited": "Доступ к файлам ограничен. Чтобы Immich мог создавать резервные копии и управлять вашей галереей, пожалуйста, предоставьте приложению разрешение на доступ к \"Фото и видео\" в Настройках.",
|
||||
"permission_onboarding_request": "Immich просит вас предоставить разрешение на доступ к вашим фото и видео",
|
||||
"profile_drawer_app_logs": "Журнал",
|
||||
"partner_page_add_partner": "Add partner",
|
||||
"partner_page_empty_message": "Your photos are not yet shared with any partner.",
|
||||
"partner_page_no_more_users": "No more users to add",
|
||||
"partner_page_partner_add_failed": "Failed to add partner",
|
||||
"partner_page_select_partner": "Select partner",
|
||||
"partner_page_shared_to_title": "Shared to",
|
||||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
||||
"permission_onboarding_get_started": "Get started",
|
||||
"permission_onboarding_go_to_settings": "Go to settings",
|
||||
"permission_onboarding_grant_permission": "Grant permission",
|
||||
"permission_onboarding_log_out": "Log out",
|
||||
"permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.",
|
||||
"permission_onboarding_permission_granted": "Permission granted! You are all set.",
|
||||
"permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.",
|
||||
"permission_onboarding_request": "Immich requires permission to view your photos and videos.",
|
||||
"profile_drawer_app_logs": "Журналы",
|
||||
"profile_drawer_client_server_up_to_date": "Клиент и сервер обновлены",
|
||||
"profile_drawer_settings": "Настройки",
|
||||
"profile_drawer_sign_out": "Выйти",
|
||||
"recently_added_page_title": "Недавно добавленные",
|
||||
"recently_added_page_title": "Recently Added",
|
||||
"search_bar_hint": "Поиск фотографий",
|
||||
"search_page_categories": "Категории",
|
||||
"search_page_favorites": "Избранное",
|
||||
"search_page_motion_photos": "Динамические фото",
|
||||
"search_page_categories": "Categories",
|
||||
"search_page_favorites": "Favorites",
|
||||
"search_page_motion_photos": "Motion Photos",
|
||||
"search_page_no_objects": "Нет доступной информации об объектах",
|
||||
"search_page_no_places": "Информация о местах отсутствует",
|
||||
"search_page_people": "Люди",
|
||||
"search_page_people": "People",
|
||||
"search_page_places": "Места",
|
||||
"search_page_recently_added": "Недавно добавленные",
|
||||
"search_page_screenshots": "Скриншоты",
|
||||
"search_page_selfies": "Селфи",
|
||||
"search_page_recently_added": "Recently added",
|
||||
"search_page_screenshots": "Screenshots",
|
||||
"search_page_selfies": "Selfies",
|
||||
"search_page_things": "Предметы",
|
||||
"search_page_videos": "Видео",
|
||||
"search_page_view_all_button": "Посмотреть все",
|
||||
"search_page_your_activity": "Ваша активность",
|
||||
"search_page_videos": "Videos",
|
||||
"search_page_view_all_button": "View all",
|
||||
"search_page_your_activity": "Your activity",
|
||||
"search_result_page_new_search_hint": "Новый поиск",
|
||||
"search_suggestion_list_smart_search_hint_1": "Интеллектуальный поиск включен по умолчанию, для поиска метаданных используйте специальный синтаксис",
|
||||
"search_suggestion_list_smart_search_hint_2": "m:ваш-запрос",
|
||||
"search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ",
|
||||
"search_suggestion_list_smart_search_hint_2": "m:your-search-term",
|
||||
"select_additional_user_for_sharing_page_suggestions": "Предложения",
|
||||
"select_user_for_sharing_page_err_album": "\nНе удалось создать альбом",
|
||||
"select_user_for_sharing_page_share_suggestions": "Предложения",
|
||||
@@ -276,7 +276,7 @@
|
||||
"sharing_page_description": "Создавайте общие альбомы, чтобы делиться фотографиями и видео с людьми в вашей сети.",
|
||||
"sharing_page_empty_list": "ПУСТОЙ СПИСОК",
|
||||
"sharing_silver_appbar_create_shared_album": "Создать общий альбом",
|
||||
"sharing_silver_appbar_share_partner": "Поделиться с партнёром",
|
||||
"sharing_silver_appbar_share_partner": "Поделиться с партнером",
|
||||
"tab_controller_nav_library": "Библиотека",
|
||||
"tab_controller_nav_photos": "Фото",
|
||||
"tab_controller_nav_search": "Поиск",
|
||||
@@ -284,17 +284,17 @@
|
||||
"theme_setting_asset_list_storage_indicator_title": "Показать индикатор хранилища на плитках объектов",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Количество объектов в строке ({})",
|
||||
"theme_setting_dark_mode_switch": "Тёмная тема",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Настройка качества детального просмотра изображения",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Настройте качество просмотра подробного изображения",
|
||||
"theme_setting_image_viewer_quality_title": "Качество просмотра изображений",
|
||||
"theme_setting_system_theme_switch": "Автоматически (Как в системе)",
|
||||
"theme_setting_theme_subtitle": "Выберите настройки темы приложения",
|
||||
"theme_setting_theme_title": "Тема",
|
||||
"theme_setting_three_stage_loading_subtitle": "Трехэтапная загрузка может повысить производительность загрузки, но вызывает значительно более высокую нагрузку на сеть",
|
||||
"theme_setting_three_stage_loading_title": "Включить трехэтапную загрузку",
|
||||
"upload_dialog_cancel": "Отмена",
|
||||
"upload_dialog_info": "Вы хотите загрузить выбранный объект(ы) на ваш сервер?",
|
||||
"upload_dialog_ok": "Загрузить",
|
||||
"upload_dialog_title": "Загрузить объект",
|
||||
"upload_dialog_cancel": "Cancel",
|
||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
||||
"upload_dialog_ok": "Upload",
|
||||
"upload_dialog_title": "Upload Asset",
|
||||
"version_announcement_overlay_ack": "Подтверждение",
|
||||
"version_announcement_overlay_release_notes": "примечания к выпуску",
|
||||
"version_announcement_overlay_text_1": "Привет друг, вышел новый релиз",
|
||||
|
||||
@@ -269,7 +269,7 @@
|
||||
"share_add": "Pridať",
|
||||
"share_add_photos": "Pridať fotografie",
|
||||
"share_add_title": "Pridať názov",
|
||||
"share_create_album": "Vytvoriť album",
|
||||
"share_create_album": "Tvorba albumu",
|
||||
"share_dialog_preparing": "Pripravujem...",
|
||||
"share_invite": "Pozvať do albumu",
|
||||
"sharing_page_album": "Zdieľané albumy",
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
"backup_controller_page_uploading_file_info": "Uploading file info",
|
||||
"backup_err_only_album": "ไม่สามารถนำอัลบั้มสุดท้ายออกได้",
|
||||
"backup_info_card_assets": "ทรัพยากร",
|
||||
"backup_manual_cancelled": "ถูกยกเลิก",
|
||||
"backup_manual_cancelled": "Cancelled",
|
||||
"backup_manual_failed": "ล้มเหลว",
|
||||
"backup_manual_in_progress": "อัปโหลดกำลังดำเนินการอยู่ โปรดลองใหม่ในสักพัก",
|
||||
"backup_manual_success": "สำเร็จ",
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
"backup_controller_page_info": "备份信息",
|
||||
"backup_controller_page_none_selected": "未选择",
|
||||
"backup_controller_page_remainder": "剩余",
|
||||
"backup_controller_page_remainder_sub": "所选数据中尚未备份的数据",
|
||||
"backup_controller_page_remainder_sub": "选中的数据中尚未备份的数据",
|
||||
"backup_controller_page_select": "选择",
|
||||
"backup_controller_page_server_storage": "服务器存储",
|
||||
"backup_controller_page_start_backup": "开始备份",
|
||||
@@ -89,26 +89,26 @@
|
||||
"backup_controller_page_total_sub": "选中相册中的所有不重复的视频和图像",
|
||||
"backup_controller_page_turn_off": "关闭前台备份",
|
||||
"backup_controller_page_turn_on": "开启前台备份",
|
||||
"backup_controller_page_uploading_file_info": "正在上传中的文件信息",
|
||||
"backup_controller_page_uploading_file_info": "正在上传文件信息",
|
||||
"backup_err_only_album": "不能移除唯一的一个相册",
|
||||
"backup_info_card_assets": "项",
|
||||
"backup_manual_cancelled": "已取消",
|
||||
"backup_info_card_assets": "张",
|
||||
"backup_manual_cancelled": "Cancelled",
|
||||
"backup_manual_failed": "失败",
|
||||
"backup_manual_in_progress": "上传正在进行中,请稍后再试",
|
||||
"backup_manual_success": "成功",
|
||||
"backup_manual_title": "上传状态",
|
||||
"cache_settings_album_thumbnails": "图库缩略图({} 项)",
|
||||
"cache_settings_album_thumbnails": "图库缩略图({} 张)",
|
||||
"cache_settings_clear_cache_button": "清除缓存",
|
||||
"cache_settings_clear_cache_button_title": "清除应用缓存。在重新生成缓存之前,将显著影响应用的性能。",
|
||||
"cache_settings_image_cache_size": "图像缓存大小({} 项)",
|
||||
"cache_settings_image_cache_size": "图像缓存大小({} 张)",
|
||||
"cache_settings_statistics_album": "图库缩略图",
|
||||
"cache_settings_statistics_assets": "{} 项({})",
|
||||
"cache_settings_statistics_assets": "{} 张({})",
|
||||
"cache_settings_statistics_full": "完整图像",
|
||||
"cache_settings_statistics_shared": "共享相册缩略图",
|
||||
"cache_settings_statistics_thumbnail": "缩略图",
|
||||
"cache_settings_statistics_title": "缓存使用情况",
|
||||
"cache_settings_subtitle": "控制 Immich 的缓存行为",
|
||||
"cache_settings_thumbnail_size": "缩略图缓存大小({} 项)",
|
||||
"cache_settings_thumbnail_size": "缩略图缓存大小({} 张)",
|
||||
"cache_settings_title": "缓存设置",
|
||||
"change_password_form_confirm_password": "确认密码",
|
||||
"change_password_form_description": "{firstName} {lastName} 您好,\n\n这是您首次登录系统,或被管理员要求更改密码。\n请在下方输入新密码。",
|
||||
@@ -139,7 +139,7 @@
|
||||
"daily_title_text_date": "E, MMM dd",
|
||||
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
||||
"date_format": "E, LLL d, y • h:mm a",
|
||||
"delete_dialog_alert": "这些项目将从 Immich 和 您的设备 中永久删除",
|
||||
"delete_dialog_alert": "这些项目将从 Immich 和您的设备中永久删除",
|
||||
"delete_dialog_cancel": "取消",
|
||||
"delete_dialog_ok": "删除",
|
||||
"delete_dialog_title": "永久删除",
|
||||
@@ -177,7 +177,7 @@
|
||||
"login_form_button_text": "登录",
|
||||
"login_form_email_hint": "youremail@email.com",
|
||||
"login_form_endpoint_hint": "http(s)://你的服务器地址:端口/api",
|
||||
"login_form_endpoint_url": "服务器链接地址",
|
||||
"login_form_endpoint_url": "服务器终结点地址",
|
||||
"login_form_err_http": "请注明 http:// 或 https://",
|
||||
"login_form_err_invalid_email": "无效的电子邮件",
|
||||
"login_form_err_invalid_url": "无效的地址",
|
||||
@@ -270,7 +270,7 @@
|
||||
"share_add_photos": "添加项目",
|
||||
"share_add_title": "添加标题",
|
||||
"share_create_album": "创建相册",
|
||||
"share_dialog_preparing": "正在准备...",
|
||||
"share_dialog_preparing": "这种准备...",
|
||||
"share_invite": "邀请相册共享",
|
||||
"sharing_page_album": "共享相册",
|
||||
"sharing_page_description": "创建共享相册以与网络中的人共享照片和视频。",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "添加到 {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "已在 {album} 中",
|
||||
"advanced_settings_prefer_remote_subtitle": "在某些设备上,从本地的项目加载缩略图的速度非常慢。\n启用此选项以加载远程。",
|
||||
"advanced_settings_prefer_remote_subtitle": "在某些设备上,从本地的项目加载缩略图的速度非常慢。\n启用此选项以加载远程\n项目。",
|
||||
"advanced_settings_prefer_remote_title": "优先远程项目",
|
||||
"advanced_settings_tile_subtitle": "高级用户设置",
|
||||
"advanced_settings_tile_title": "高级",
|
||||
@@ -77,7 +77,7 @@
|
||||
"backup_controller_page_info": "备份信息",
|
||||
"backup_controller_page_none_selected": "未选择",
|
||||
"backup_controller_page_remainder": "剩余",
|
||||
"backup_controller_page_remainder_sub": "所选数据中尚未备份的数据",
|
||||
"backup_controller_page_remainder_sub": "要从所选内容备份的剩余照片和视频",
|
||||
"backup_controller_page_select": "选择",
|
||||
"backup_controller_page_server_storage": "服务器存储",
|
||||
"backup_controller_page_start_backup": "开始备份",
|
||||
@@ -89,26 +89,26 @@
|
||||
"backup_controller_page_total_sub": "选中相册中的所有不重复的视频和图像",
|
||||
"backup_controller_page_turn_off": "关闭前台备份",
|
||||
"backup_controller_page_turn_on": "开启前台备份",
|
||||
"backup_controller_page_uploading_file_info": "正在上传中的文件信息",
|
||||
"backup_controller_page_uploading_file_info": "正在上传文件信息",
|
||||
"backup_err_only_album": "不能移除唯一的一个相册",
|
||||
"backup_info_card_assets": "项",
|
||||
"backup_manual_cancelled": "已取消",
|
||||
"backup_info_card_assets": "张",
|
||||
"backup_manual_cancelled": "Cancelled",
|
||||
"backup_manual_failed": "失败",
|
||||
"backup_manual_in_progress": "上传正在进行中,请稍后再试",
|
||||
"backup_manual_success": "成功",
|
||||
"backup_manual_title": "上传状态",
|
||||
"cache_settings_album_thumbnails": "图库缩略图({} 项)",
|
||||
"cache_settings_album_thumbnails": "图库缩略图({} 张)",
|
||||
"cache_settings_clear_cache_button": "清除缓存",
|
||||
"cache_settings_clear_cache_button_title": "清除应用缓存。在重新生成缓存之前,将显著影响应用的性能。",
|
||||
"cache_settings_image_cache_size": "图像缓存大小({} 项)",
|
||||
"cache_settings_image_cache_size": "图像缓存大小({} 张)",
|
||||
"cache_settings_statistics_album": "图库缩略图",
|
||||
"cache_settings_statistics_assets": "{} 项({})",
|
||||
"cache_settings_statistics_assets": "{} 张({})",
|
||||
"cache_settings_statistics_full": "完整图像",
|
||||
"cache_settings_statistics_shared": "共享相册缩略图",
|
||||
"cache_settings_statistics_thumbnail": "缩略图",
|
||||
"cache_settings_statistics_title": "缓存使用情况",
|
||||
"cache_settings_subtitle": "控制 Immich 的缓存行为",
|
||||
"cache_settings_thumbnail_size": "缩略图缓存大小({} 项)",
|
||||
"cache_settings_thumbnail_size": "缩略图缓存大小({} 张)",
|
||||
"cache_settings_title": "缓存设置",
|
||||
"change_password_form_confirm_password": "确认密码",
|
||||
"change_password_form_description": "{firstName} {lastName} 您好,\n\n这是您首次登录系统,或被管理员要求更改密码。\n请在下方输入新密码。",
|
||||
@@ -139,7 +139,7 @@
|
||||
"daily_title_text_date": "E, MMM dd",
|
||||
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
||||
"date_format": "E, LLL d, y • h:mm a",
|
||||
"delete_dialog_alert": "这些项目将从 Immich 和 您的设备 中永久删除",
|
||||
"delete_dialog_alert": "这些项目将从 Immich 和您的设备中永久删除",
|
||||
"delete_dialog_cancel": "取消",
|
||||
"delete_dialog_ok": "删除",
|
||||
"delete_dialog_title": "永久删除",
|
||||
@@ -177,7 +177,7 @@
|
||||
"login_form_button_text": "登录",
|
||||
"login_form_email_hint": "youremail@email.com",
|
||||
"login_form_endpoint_hint": "http(s)://你的服务器地址:端口/api",
|
||||
"login_form_endpoint_url": "服务器链接地址",
|
||||
"login_form_endpoint_url": "服务器终结点地址",
|
||||
"login_form_err_http": "请注明 http:// 或 https://",
|
||||
"login_form_err_invalid_email": "无效的电子邮件",
|
||||
"login_form_err_invalid_url": "无效的地址",
|
||||
@@ -270,7 +270,7 @@
|
||||
"share_add_photos": "添加项目",
|
||||
"share_add_title": "添加标题",
|
||||
"share_create_album": "创建相册",
|
||||
"share_dialog_preparing": "正在准备...",
|
||||
"share_dialog_preparing": "这种准备...",
|
||||
"share_invite": "邀请相册共享",
|
||||
"sharing_page_album": "共享相册",
|
||||
"sharing_page_description": "创建共享相册以与网络中的人共享照片和视频。",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 45 KiB |
+12
-18
@@ -20,8 +20,6 @@ PODS:
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- geolocator_apple (1.2.0):
|
||||
- Flutter
|
||||
- image_picker_ios (0.0.1):
|
||||
- Flutter
|
||||
- integration_test (0.0.1):
|
||||
@@ -35,7 +33,7 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- path_provider_ios (0.0.1):
|
||||
- Flutter
|
||||
- permission_handler_apple (9.1.1):
|
||||
- permission_handler_apple (9.0.4):
|
||||
- Flutter
|
||||
- photo_manager (2.0.0):
|
||||
- Flutter
|
||||
@@ -55,7 +53,7 @@ PODS:
|
||||
- Flutter
|
||||
- video_player_avfoundation (0.0.1):
|
||||
- Flutter
|
||||
- wakelock_plus (0.0.1):
|
||||
- wakelock (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
@@ -67,7 +65,6 @@ DEPENDENCIES:
|
||||
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
|
||||
- flutter_web_auth (from `.symlinks/plugins/flutter_web_auth/ios`)
|
||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
|
||||
@@ -81,7 +78,7 @@ DEPENDENCIES:
|
||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
|
||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||
- wakelock (from `.symlinks/plugins/wakelock/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
@@ -107,8 +104,6 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/flutter_web_auth/ios"
|
||||
fluttertoast:
|
||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
geolocator_apple:
|
||||
:path: ".symlinks/plugins/geolocator_apple/ios"
|
||||
image_picker_ios:
|
||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||
integration_test:
|
||||
@@ -135,8 +130,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
video_player_avfoundation:
|
||||
:path: ".symlinks/plugins/video_player_avfoundation/ios"
|
||||
wakelock_plus:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
wakelock:
|
||||
:path: ".symlinks/plugins/wakelock/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
||||
@@ -146,26 +141,25 @@ SPEC CHECKSUMS:
|
||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||
flutter_udid: 0848809dbed4c055175747ae6a45a8b4f6771e1c
|
||||
flutter_web_auth: c25208760459cec375a3c39f6a8759165ca0fa4d
|
||||
fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c
|
||||
fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
geolocator_apple: cc556e6844d508c95df1e87e3ea6fa4e58c50401
|
||||
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
|
||||
integration_test: 13825b8a9334a850581300559b8839134b124670
|
||||
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
|
||||
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
||||
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
|
||||
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
||||
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
||||
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
|
||||
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
|
||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
||||
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
|
||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||
video_player_avfoundation: 81e49bb3d9fb63dccf9fa0f6d877dc3ddbeac126
|
||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
|
||||
|
||||
PODFILE CHECKSUM: 599d8aeb73728400c15364e734525722250a5382
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1430;
|
||||
LastUpgradeCheck = 1300;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
97C146ED1CF9000F007C117D = {
|
||||
@@ -379,7 +379,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 116;
|
||||
CURRENT_PROJECT_VERSION = 113;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -515,7 +515,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 116;
|
||||
CURRENT_PROJECT_VERSION = 113;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -543,7 +543,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 116;
|
||||
CURRENT_PROJECT_VERSION = 113;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1430"
|
||||
LastUpgradeVersion = "1300"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -59,11 +59,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.76.0</string>
|
||||
<string>1.73.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>116</string>
|
||||
<string>113</string>
|
||||
<key>FLTEnableImpeller</key>
|
||||
<true />
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
@@ -83,6 +83,8 @@
|
||||
</dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>We need to access the camera to let you take beautiful video using this app</string>
|
||||
<key>NSLocationAlwaysUsageDescription</key>
|
||||
<string>Enable location setting to show position of assets on map</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>Enable location setting to show position of assets on map</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
# The default execution directory of this script is the ci_scripts directory.
|
||||
cd $CI_WORKSPACE/mobile
|
||||
|
||||
@@ -19,7 +19,7 @@ platform :ios do
|
||||
desc "iOS Beta"
|
||||
lane :beta do
|
||||
increment_version_number(
|
||||
version_number: "1.78.0"
|
||||
version_number: "1.73.0"
|
||||
)
|
||||
increment_build_number(
|
||||
build_number: latest_testflight_build_number + 1,
|
||||
|
||||
@@ -5,32 +5,32 @@
|
||||
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000243">
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000187">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="2.611762">
|
||||
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="2.403882">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="6.937008">
|
||||
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="5.068392">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="2.740416">
|
||||
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="1.988079">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="4: build_app" time="93.625943">
|
||||
<testcase classname="fastlane.lanes" name="4: build_app" time="96.47923">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="62.107671">
|
||||
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="57.517755">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
+2
-12
@@ -29,7 +29,6 @@ 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/views/immich_loading_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/migration.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
@@ -42,7 +41,6 @@ void main() async {
|
||||
final db = await loadDb();
|
||||
await initApp();
|
||||
await migrateDatabaseIfNeeded(db);
|
||||
HttpOverrides.global = HttpSSLCertOverride();
|
||||
runApp(getMainWidget(db));
|
||||
}
|
||||
|
||||
@@ -65,15 +63,11 @@ Future<void> initApp() async {
|
||||
|
||||
FlutterError.onError = (details) {
|
||||
FlutterError.presentError(details);
|
||||
log.severe(
|
||||
'Catch all error: ${details.toString()} - ${details.exception} - ${details.library} - ${details.context} - ${details.stack}',
|
||||
details,
|
||||
details.stack,
|
||||
);
|
||||
log.severe(details.toString(), details, details.stack);
|
||||
};
|
||||
|
||||
PlatformDispatcher.instance.onError = (error, stack) {
|
||||
log.severe('Catch all error: ${error.toString()} - $error', error, stack);
|
||||
log.severe(error.toString(), error, stack);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
@@ -145,10 +139,6 @@ class ImmichAppState extends ConsumerState<ImmichApp>
|
||||
debugPrint("[APP STATE] detached");
|
||||
ref.read(appStateProvider.notifier).handleAppDetached();
|
||||
break;
|
||||
case AppLifecycleState.hidden:
|
||||
debugPrint("[APP STATE] hidden");
|
||||
ref.read(appStateProvider.notifier).handleAppHidden();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,16 +56,6 @@ class SharedAlbumNotifier extends StateNotifier<List<Album>> {
|
||||
return _albumService.removeAssetFromAlbum(album, assets);
|
||||
}
|
||||
|
||||
Future<bool> removeUserFromAlbum(Album album, User user) async {
|
||||
final result = await _albumService.removeUserFromAlbum(album, user);
|
||||
|
||||
if (result && album.sharedUsers.isEmpty) {
|
||||
state = state.where((element) => element.id != album.id).toList();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_streamSub.cancel();
|
||||
|
||||
@@ -348,26 +348,6 @@ class AlbumService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> removeUserFromAlbum(
|
||||
Album album,
|
||||
User user,
|
||||
) async {
|
||||
try {
|
||||
await _apiService.albumApi.removeUserFromAlbum(
|
||||
album.remoteId!,
|
||||
user.id,
|
||||
);
|
||||
|
||||
album.sharedUsers.remove(user);
|
||||
await _db.writeTxn(() => album.sharedUsers.update(unlink: [user]));
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint("Error removeUserFromAlbum ${e.toString()}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> changeTitleAlbum(
|
||||
Album album,
|
||||
String newAlbumTitle,
|
||||
|
||||
@@ -49,7 +49,7 @@ class AlbumThumbnailListTile extends StatelessWidget {
|
||||
type: ThumbnailFormat.JPEG,
|
||||
),
|
||||
httpHeaders: {
|
||||
"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}",
|
||||
"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}"
|
||||
},
|
||||
cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG),
|
||||
errorWidget: (context, url, error) =>
|
||||
@@ -105,9 +105,9 @@ class AlbumThumbnailListTile extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
),
|
||||
).tr(),
|
||||
).tr()
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -69,11 +69,6 @@ class AlbumTitleTextField extends ConsumerWidget {
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
hintText: 'share_add_title'.tr(),
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 28,
|
||||
color: isDarkTheme ? Colors.grey[300] : Colors.grey[700],
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
focusColor: Colors.grey[300],
|
||||
fillColor: isDarkTheme
|
||||
? const Color.fromARGB(255, 32, 33, 35)
|
||||
|
||||
@@ -39,7 +39,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
||||
final newAlbumTitle = ref.watch(albumViewerProvider).editTitleText;
|
||||
final isEditAlbum = ref.watch(albumViewerProvider).isEditAlbum;
|
||||
|
||||
deleteAlbum() async {
|
||||
void onDeleteAlbumPressed() async {
|
||||
ImmichLoadingOverlayController.appLoader.show();
|
||||
|
||||
final bool success;
|
||||
@@ -65,52 +65,6 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
||||
ImmichLoadingOverlayController.appLoader.hide();
|
||||
}
|
||||
|
||||
Future<void> showConfirmationDialog() async {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false, // user must tap button!
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Delete album'),
|
||||
content: const Text(
|
||||
'Are you sure you want to delete this album from your account?',
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, 'Cancel'),
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, 'Confirm');
|
||||
deleteAlbum();
|
||||
},
|
||||
child: Text(
|
||||
'Confirm',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).brightness == Brightness.light
|
||||
? Colors.red
|
||||
: Colors.red[300],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void onDeleteAlbumPressed() async {
|
||||
showConfirmationDialog();
|
||||
}
|
||||
|
||||
void onLeaveAlbumPressed() async {
|
||||
ImmichLoadingOverlayController.appLoader.show();
|
||||
|
||||
@@ -198,61 +152,43 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
||||
}
|
||||
|
||||
void buildBottomSheet() {
|
||||
final ownerActions = [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.person_add_alt_rounded),
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
onAddUsers!(album);
|
||||
},
|
||||
title: const Text(
|
||||
"album_viewer_page_share_add_users",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings_rounded),
|
||||
onTap: () =>
|
||||
AutoRouter.of(context).navigate(AlbumOptionsRoute(album: album)),
|
||||
title: const Text(
|
||||
"translated_text_options",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
];
|
||||
|
||||
final commonActions = [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.add_photo_alternate_outlined),
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
onAddPhotos!(album);
|
||||
},
|
||||
title: const Text(
|
||||
"share_add_photos",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
];
|
||||
showModalBottomSheet(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
isScrollControlled: false,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
buildBottomSheetActionButton(),
|
||||
if (selected.isEmpty && onAddPhotos != null) ...commonActions,
|
||||
if (selected.isEmpty &&
|
||||
onAddPhotos != null &&
|
||||
userId == album.ownerId)
|
||||
...ownerActions,
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
buildBottomSheetActionButton(),
|
||||
if (selected.isEmpty && onAddPhotos != null)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.add_photo_alternate_outlined),
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
onAddPhotos!(album);
|
||||
},
|
||||
title: const Text(
|
||||
"share_add_photos",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
if (selected.isEmpty &&
|
||||
onAddPhotos != null &&
|
||||
userId == album.ownerId)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.person_add_alt_rounded),
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
onAddUsers!(album);
|
||||
},
|
||||
title: const Text(
|
||||
"album_viewer_page_share_add_users",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -281,8 +217,6 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
||||
toastType: ToastType.error,
|
||||
);
|
||||
}
|
||||
|
||||
titleFocusNode.unfocus();
|
||||
},
|
||||
icon: const Icon(Icons.check_rounded),
|
||||
splashRadius: 25,
|
||||
|
||||
@@ -84,11 +84,6 @@ class AlbumViewerEditableTitle extends HookConsumerWidget {
|
||||
: Colors.grey[200],
|
||||
filled: titleFocusNode.hasFocus,
|
||||
hintText: 'share_add_title'.tr(),
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 28,
|
||||
color: isDarkTheme ? Colors.grey[300] : Colors.grey[700],
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
|
||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/shared/models/album.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
||||
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
|
||||
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
|
||||
|
||||
class AlbumOptionsPage extends HookConsumerWidget {
|
||||
final Album album;
|
||||
|
||||
const AlbumOptionsPage({super.key, required this.album});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final sharedUsers = useState(album.sharedUsers.toList());
|
||||
final owner = album.owner.value;
|
||||
final userId = ref.watch(authenticationProvider).userId;
|
||||
final isOwner = owner?.id == userId;
|
||||
|
||||
void showErrorMessage() {
|
||||
Navigator.pop(context);
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "Error leaving/removing from album",
|
||||
toastType: ToastType.error,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
|
||||
void leaveAlbum() async {
|
||||
ImmichLoadingOverlayController.appLoader.show();
|
||||
|
||||
try {
|
||||
final isSuccess =
|
||||
await ref.read(sharedAlbumProvider.notifier).leaveAlbum(album);
|
||||
|
||||
if (isSuccess) {
|
||||
AutoRouter.of(context)
|
||||
.navigate(const TabControllerRoute(children: [SharingRoute()]));
|
||||
} else {
|
||||
showErrorMessage();
|
||||
}
|
||||
} catch (_) {
|
||||
showErrorMessage();
|
||||
}
|
||||
|
||||
ImmichLoadingOverlayController.appLoader.hide();
|
||||
}
|
||||
|
||||
void removeUserFromAlbum(User user) async {
|
||||
ImmichLoadingOverlayController.appLoader.show();
|
||||
|
||||
try {
|
||||
await ref
|
||||
.read(sharedAlbumProvider.notifier)
|
||||
.removeUserFromAlbum(album, user);
|
||||
album.sharedUsers.remove(user);
|
||||
sharedUsers.value = album.sharedUsers.toList();
|
||||
} catch (error) {
|
||||
showErrorMessage();
|
||||
}
|
||||
|
||||
Navigator.pop(context);
|
||||
ImmichLoadingOverlayController.appLoader.hide();
|
||||
}
|
||||
|
||||
void handleUserClick(User user) {
|
||||
var actions = [];
|
||||
|
||||
if (user.id == userId) {
|
||||
actions = [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.exit_to_app_rounded),
|
||||
title: const Text("Leave album"),
|
||||
onTap: leaveAlbum,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
if (isOwner) {
|
||||
actions = [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.person_remove_rounded),
|
||||
title: const Text("Remove user from album"),
|
||||
onTap: () => removeUserFromAlbum(user),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
showModalBottomSheet(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
isScrollControlled: false,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [...actions],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
buildOwnerInfo() {
|
||||
return ListTile(
|
||||
leading: owner != null
|
||||
? UserCircleAvatar(
|
||||
user: owner,
|
||||
useRandomBackgroundColor: true,
|
||||
)
|
||||
: const SizedBox(),
|
||||
title: Text(
|
||||
album.owner.value?.firstName ?? "",
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
album.owner.value?.email ?? "",
|
||||
style: TextStyle(color: Colors.grey[500]),
|
||||
),
|
||||
trailing: const Text(
|
||||
"Owner",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
buildSharedUsersList() {
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: sharedUsers.value.length,
|
||||
itemBuilder: (context, index) {
|
||||
final user = sharedUsers.value[index];
|
||||
return ListTile(
|
||||
leading: UserCircleAvatar(
|
||||
user: user,
|
||||
useRandomBackgroundColor: true,
|
||||
radius: 22,
|
||||
),
|
||||
title: Text(
|
||||
user.firstName,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
user.email,
|
||||
style: TextStyle(color: Colors.grey[500]),
|
||||
),
|
||||
trailing: userId == user.id || isOwner
|
||||
? const Icon(Icons.more_horiz_rounded)
|
||||
: const SizedBox(),
|
||||
onTap: userId == user.id || isOwner
|
||||
? () => handleUserClick(user)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
buildSectionTitle(String text) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(text, style: Theme.of(context).textTheme.bodySmall),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
||||
onPressed: () {
|
||||
AutoRouter.of(context).pop(null);
|
||||
},
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Text("translated_text_options".tr()),
|
||||
),
|
||||
body: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
buildSectionTitle("PEOPLE"),
|
||||
buildOwnerInfo(),
|
||||
buildSharedUsersList(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/shared/models/album.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
|
||||
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
|
||||
|
||||
class AlbumViewerPage extends HookConsumerWidget {
|
||||
@@ -117,7 +116,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
|
||||
Widget buildControlButton(Album album) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, top: 8, bottom: 16),
|
||||
padding: const EdgeInsets.only(left: 16.0, top: 8, bottom: 8),
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: ListView(
|
||||
@@ -142,7 +141,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
|
||||
Widget buildTitle(Album album) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 8, right: 8, top: 24),
|
||||
padding: const EdgeInsets.only(left: 8, right: 8, top: 16),
|
||||
child: userId == album.ownerId && album.isRemote
|
||||
? AlbumViewerEditableTitle(
|
||||
album: album,
|
||||
@@ -173,6 +172,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 16.0,
|
||||
top: 8.0,
|
||||
bottom: album.shared ? 0.0 : 8.0,
|
||||
),
|
||||
child: Text(
|
||||
@@ -180,34 +180,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildSharedUserIconsRow(Album album) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
await AutoRouter.of(context).push(AlbumOptionsRoute(album: album));
|
||||
ref.invalidate(albumDetailProvider(album.id));
|
||||
},
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: ((context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: UserCircleAvatar(
|
||||
user: album.sharedUsers.toList()[index],
|
||||
radius: 18,
|
||||
size: 36,
|
||||
useRandomBackgroundColor: true,
|
||||
),
|
||||
);
|
||||
}),
|
||||
itemCount: album.sharedUsers.length,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -220,7 +193,33 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
children: [
|
||||
buildTitle(album),
|
||||
if (album.assets.isNotEmpty == true) buildAlbumDateRange(album),
|
||||
if (album.shared) buildSharedUserIconsRow(album),
|
||||
if (album.shared)
|
||||
SizedBox(
|
||||
height: 50,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: ((context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: CircleAvatar(
|
||||
backgroundColor: Colors.grey[300],
|
||||
radius: 18,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(2.0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(50.0),
|
||||
child: Image.asset(
|
||||
'assets/immich-logo-no-outline.png',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
itemCount: album.sharedUsers.length,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -73,12 +73,9 @@ class AssetSelectionPage extends HookConsumerWidget {
|
||||
AutoRouter.of(context)
|
||||
.popForced<AssetSelectionPageResult>(payload);
|
||||
},
|
||||
child: Text(
|
||||
child: const Text(
|
||||
"share_add",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -30,8 +30,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
final albumTitleTextFieldFocusNode = useFocusNode();
|
||||
final isAlbumTitleTextFieldFocus = useState(false);
|
||||
final isAlbumTitleEmpty = useState(true);
|
||||
final selectedAssets = useState<Set<Asset>>(
|
||||
initialAssets != null ? Set.from(initialAssets!) : const {},);
|
||||
final selectedAssets = useState<Set<Asset>>(initialAssets != null ? Set.from(initialAssets!) : const {});
|
||||
final isDarkTheme = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
showSelectUserPage() async {
|
||||
@@ -249,9 +248,8 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
: null,
|
||||
child: Text(
|
||||
'create_shared_album_page_create'.tr(),
|
||||
style: TextStyle(
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -60,7 +60,7 @@ class LibraryPage extends HookConsumerWidget {
|
||||
Widget buildSortButton() {
|
||||
final options = [
|
||||
"library_page_sort_created".tr(),
|
||||
"library_page_sort_title".tr(),
|
||||
"library_page_sort_title".tr()
|
||||
];
|
||||
|
||||
return PopupMenuButton(
|
||||
@@ -87,7 +87,7 @@ class LibraryPage extends HookConsumerWidget {
|
||||
color: selected ? Theme.of(context).primaryColor : null,
|
||||
fontSize: 12.0,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -7,7 +7,6 @@ import 'package:immich_mobile/modules/album/providers/suggested_shared_users.pro
|
||||
import 'package:immich_mobile/shared/models/album.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
|
||||
|
||||
class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
||||
final Album album;
|
||||
@@ -36,8 +35,10 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return UserCircleAvatar(
|
||||
user: user,
|
||||
return CircleAvatar(
|
||||
backgroundImage:
|
||||
const AssetImage('assets/immich-logo-no-outline.png'),
|
||||
backgroundColor: Theme.of(context).primaryColor.withAlpha(50),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -102,7 +103,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
||||
} else {
|
||||
sharedUsersList.value = {
|
||||
...sharedUsersList.value,
|
||||
users[index],
|
||||
users[index]
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -135,7 +136,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
||||
"share_add",
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: suggestedShareUsers.when(
|
||||
|
||||
@@ -10,7 +10,6 @@ import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
|
||||
|
||||
class SelectUserForSharingPage extends HookConsumerWidget {
|
||||
const SelectUserForSharingPage({Key? key, required this.assets})
|
||||
@@ -57,8 +56,10 @@ class SelectUserForSharingPage extends HookConsumerWidget {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return UserCircleAvatar(
|
||||
user: user,
|
||||
return CircleAvatar(
|
||||
backgroundImage:
|
||||
const AssetImage('assets/immich-logo-no-outline.png'),
|
||||
backgroundColor: Theme.of(context).primaryColor.withAlpha(50),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -123,7 +124,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
|
||||
} else {
|
||||
sharedUsersList.value = {
|
||||
...sharedUsersList.value,
|
||||
users[index],
|
||||
users[index]
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -163,7 +164,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
|
||||
// color: Theme.of(context).primaryColor,
|
||||
),
|
||||
).tr(),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: suggestedShareUsers.when(
|
||||
|
||||
@@ -160,7 +160,7 @@ class SharingPage extends HookConsumerWidget {
|
||||
maxLines: 1,
|
||||
).tr(),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,12 +2,14 @@ import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/archive/providers/archive_asset_provider.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||
import 'package:immich_mobile/utils/selection_handlers.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
||||
|
||||
class ArchivePage extends HookConsumerWidget {
|
||||
const ArchivePage({super.key});
|
||||
@@ -66,18 +68,30 @@ class ArchivePage extends HookConsumerWidget {
|
||||
: () async {
|
||||
processing.value = true;
|
||||
try {
|
||||
await handleArchiveAssets(
|
||||
ref,
|
||||
context,
|
||||
selection.value.toList(),
|
||||
shouldArchive: false,
|
||||
);
|
||||
if (selection.value.isNotEmpty) {
|
||||
await ref
|
||||
.watch(assetProvider.notifier)
|
||||
.toggleArchive(
|
||||
selection.value.toList(),
|
||||
false,
|
||||
);
|
||||
|
||||
final assetOrAssets = selection.value.length > 1
|
||||
? 'assets'
|
||||
: 'asset';
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg:
|
||||
'Moved ${selection.value.length} $assetOrAssets to library',
|
||||
gravity: ToastGravity.CENTER,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
processing.value = false;
|
||||
selectionEnabledHook.value = false;
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -110,7 +124,7 @@ class ArchivePage extends HookConsumerWidget {
|
||||
),
|
||||
if (selectionEnabledHook.value) buildBottomBar(),
|
||||
if (processing.value)
|
||||
const Center(child: ImmichLoadingIndicator()),
|
||||
const Center(child: ImmichLoadingIndicator())
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -5,10 +5,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/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:latlong2/latlong.dart';
|
||||
import 'package:immich_mobile/utils/bytes_units.dart';
|
||||
@@ -19,39 +16,16 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||
|
||||
const ExifBottomSheet({Key? key, required this.asset}) : super(key: key);
|
||||
|
||||
bool hasCoordinates(ExifInfo? exifInfo) =>
|
||||
exifInfo != null &&
|
||||
exifInfo.latitude != null &&
|
||||
exifInfo.longitude != null &&
|
||||
exifInfo.latitude != 0 &&
|
||||
exifInfo.longitude != 0;
|
||||
|
||||
String get formattedDateTime {
|
||||
final fileCreatedAt = asset.fileCreatedAt.toLocal();
|
||||
final date = DateFormat.yMMMEd().format(fileCreatedAt);
|
||||
final time = DateFormat.jm().format(fileCreatedAt);
|
||||
|
||||
return '$date • $time';
|
||||
}
|
||||
|
||||
Future<Uri?> _createCoordinatesUri(ExifInfo? exifInfo) async {
|
||||
if (!hasCoordinates(exifInfo)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final double latitude = exifInfo!.latitude!;
|
||||
final double longitude = exifInfo.longitude!;
|
||||
|
||||
const zoomLevel = 16;
|
||||
bool get showMap =>
|
||||
asset.exifInfo?.latitude != null && asset.exifInfo?.longitude != null;
|
||||
|
||||
Future<Uri> _createCoordinatesUri(double latitude, double longitude) async {
|
||||
const zoomLevel = 5;
|
||||
if (Platform.isAndroid) {
|
||||
Uri uri = Uri(
|
||||
scheme: 'geo',
|
||||
host: '$latitude,$longitude',
|
||||
queryParameters: {
|
||||
'z': '$zoomLevel',
|
||||
'q': '$latitude,$longitude($formattedDateTime)',
|
||||
},
|
||||
queryParameters: {'z': '$zoomLevel', 'q': '$latitude,$longitude'},
|
||||
);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
return uri;
|
||||
@@ -59,27 +33,22 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||
} else if (Platform.isIOS) {
|
||||
var params = {
|
||||
'll': '$latitude,$longitude',
|
||||
'q': formattedDateTime,
|
||||
'z': '$zoomLevel',
|
||||
'q': '$latitude, $longitude',
|
||||
};
|
||||
Uri uri = Uri.https('maps.apple.com', '/', params);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
if (!await canLaunchUrl(uri)) {
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
|
||||
return Uri(
|
||||
scheme: 'https',
|
||||
host: 'openstreetmap.org',
|
||||
queryParameters: {'mlat': '$latitude', 'mlon': '$longitude'},
|
||||
fragment: 'map=$zoomLevel/$latitude/$longitude',
|
||||
return Uri.https(
|
||||
'www.google.com',
|
||||
'/maps/place/$latitude,$longitude/@$latitude,$longitude,${zoomLevel}z',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final assetWithExif = ref.watch(assetDetailProvider(asset));
|
||||
final exifInfo = (assetWithExif.value ?? asset).exifInfo;
|
||||
final exifInfo = asset.exifInfo;
|
||||
var isDarkTheme = Theme.of(context).brightness == Brightness.dark;
|
||||
var textColor = isDarkTheme ? Colors.white : Colors.black;
|
||||
|
||||
@@ -88,35 +57,67 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return MapThumbnail(
|
||||
coords: LatLng(
|
||||
exifInfo?.latitude ?? 0,
|
||||
exifInfo?.longitude ?? 0,
|
||||
),
|
||||
return Container(
|
||||
height: 150,
|
||||
zoom: 16.0,
|
||||
markers: [
|
||||
Marker(
|
||||
anchorPos: AnchorPos.align(AnchorAlign.top),
|
||||
point: LatLng(
|
||||
width: constraints.maxWidth,
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15)),
|
||||
),
|
||||
child: FlutterMap(
|
||||
options: MapOptions(
|
||||
interactiveFlags: InteractiveFlag.none,
|
||||
center: LatLng(
|
||||
exifInfo?.latitude ?? 0,
|
||||
exifInfo?.longitude ?? 0,
|
||||
),
|
||||
builder: (ctx) => const Image(
|
||||
image: AssetImage('assets/location-pin.png'),
|
||||
),
|
||||
zoom: 16.0,
|
||||
onTap: (tapPosition, latLong) async {
|
||||
if (exifInfo != null &&
|
||||
exifInfo.latitude != null &&
|
||||
exifInfo.longitude != null) {
|
||||
launchUrl(
|
||||
await _createCoordinatesUri(
|
||||
exifInfo.latitude!,
|
||||
exifInfo.longitude!,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
onTap: (tapPosition, latLong) async {
|
||||
Uri? uri = await _createCoordinatesUri(exifInfo);
|
||||
|
||||
if (uri == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
debugPrint('Opening Map Uri: $uri');
|
||||
launchUrl(uri);
|
||||
},
|
||||
nonRotatedChildren: [
|
||||
RichAttributionWidget(
|
||||
attributions: [
|
||||
TextSourceAttribution(
|
||||
'OpenStreetMap contributors',
|
||||
onTap: () => launchUrl(
|
||||
Uri.parse('https://openstreetmap.org/copyright'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
children: [
|
||||
TileLayer(
|
||||
urlTemplate:
|
||||
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
subdomains: const ['a', 'b', 'c'],
|
||||
),
|
||||
MarkerLayer(
|
||||
markers: [
|
||||
Marker(
|
||||
anchorPos: AnchorPos.align(AnchorAlign.top),
|
||||
point: LatLng(
|
||||
exifInfo?.latitude ?? 0,
|
||||
exifInfo?.longitude ?? 0,
|
||||
),
|
||||
builder: (ctx) => const Image(
|
||||
image: AssetImage('assets/location-pin.png'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -131,7 +132,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||
? formatBytes(a.exifInfo!.fileSize!)
|
||||
: "";
|
||||
String text = resolution + fileSize;
|
||||
return text.isNotEmpty ? text : null;
|
||||
return text.isEmpty ? null : Text(text);
|
||||
}
|
||||
|
||||
buildDragHeader() {
|
||||
@@ -150,7 +151,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||
|
||||
buildLocation() {
|
||||
// Guard no lat/lng
|
||||
if (!hasCoordinates(exifInfo)) {
|
||||
if (!showMap) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
@@ -198,7 +199,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||
Text(
|
||||
"${exifInfo!.latitude!.toStringAsFixed(4)}, ${exifInfo.longitude!.toStringAsFixed(4)}",
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -206,8 +207,12 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
buildDate() {
|
||||
final fileCreatedAt = asset.fileCreatedAt.toLocal();
|
||||
final date = DateFormat.yMMMEd().format(fileCreatedAt);
|
||||
final time = DateFormat.jm().format(fileCreatedAt);
|
||||
|
||||
return Text(
|
||||
formattedDateTime,
|
||||
'$date • $time',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
@@ -215,58 +220,7 @@ 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() {
|
||||
final imgProperties = buildImageProperties();
|
||||
|
||||
// There are no details
|
||||
if (imgProperties == null &&
|
||||
(exifInfo == null || exifInfo.make == null)) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -281,7 +235,22 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||
),
|
||||
).tr(),
|
||||
),
|
||||
if (imgProperties != null) imgProperties,
|
||||
ListTile(
|
||||
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)
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
@@ -298,7 +267,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
"ƒ/${exifInfo.fNumber} ${exifInfo.exposureTime} ${exifInfo.focalLength} mm ISO ${exifInfo.iso ?? ''} ",
|
||||
"ƒ/${exifInfo.fNumber} ${exifInfo.exposureTime} ${exifInfo.focalLength} mm ISO${exifInfo.iso} ",
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -337,7 +306,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
flex: hasCoordinates(exifInfo) ? 5 : 0,
|
||||
flex: showMap ? 5 : 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: buildLocation(),
|
||||
@@ -367,7 +336,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||
if (asset.isRemote) DescriptionInput(asset: asset),
|
||||
const SizedBox(height: 8.0),
|
||||
buildLocation(),
|
||||
SizedBox(height: hasCoordinates(exifInfo) ? 16.0 : 0.0),
|
||||
SizedBox(height: showMap ? 16.0 : 0.0),
|
||||
buildDetail(),
|
||||
const SizedBox(height: 50),
|
||||
],
|
||||
|
||||
@@ -128,7 +128,7 @@ class TopControlAppBar extends HookConsumerWidget {
|
||||
if (asset.isLocal && !asset.isRemote) buildUploadButton(),
|
||||
if (asset.isRemote && !asset.isLocal) buildDownloadButton(),
|
||||
if (asset.isRemote) buildAddToAlbumButtom(),
|
||||
buildMoreInfoButton(),
|
||||
buildMoreInfoButton()
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,8 +64,9 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
final authToken = 'Bearer ${Store.get(StoreKey.accessToken)}';
|
||||
final currentIndex = useState(initialIndex);
|
||||
final currentAsset = loadAsset(currentIndex.value);
|
||||
final watchedAsset = ref.watch(assetDetailProvider(currentAsset));
|
||||
|
||||
Asset asset() => currentAsset;
|
||||
Asset asset() => watchedAsset.value ?? currentAsset;
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
@@ -193,7 +194,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
),
|
||||
child: ExifBottomSheet(asset: currentAsset),
|
||||
child: ExifBottomSheet(asset: asset()),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user