Compare commits
1 Commits
v1.106.0
...
dev/paymen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad3e92fff0 |
@@ -4,7 +4,6 @@
|
||||
|
||||
design/
|
||||
docker/
|
||||
!docker/scripts
|
||||
docs/
|
||||
e2e/
|
||||
fastlane/
|
||||
|
||||
23
.github/labeler.yml
vendored
@@ -1,23 +0,0 @@
|
||||
cli:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: cli/**
|
||||
|
||||
documentation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: docs/**
|
||||
|
||||
🖥️web:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: web/**
|
||||
|
||||
📱mobile:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: mobile/**
|
||||
|
||||
🗄️server:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: server/**
|
||||
|
||||
🧠machine-learning:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: machine-learning/**
|
||||
2
.github/workflows/cli.yml
vendored
@@ -87,7 +87,7 @@ jobs:
|
||||
type=raw,value=latest,enable=${{ github.event_name == 'workflow_dispatch' }}
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v5.4.0
|
||||
uses: docker/build-push-action@v5.3.0
|
||||
with:
|
||||
file: cli/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
2
.github/workflows/docker.yml
vendored
@@ -115,7 +115,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v5.4.0
|
||||
uses: docker/build-push-action@v5.3.0
|
||||
with:
|
||||
context: ${{ matrix.context }}
|
||||
file: ${{ matrix.file }}
|
||||
|
||||
12
.github/workflows/pr-labeler.yml
vendored
@@ -1,12 +0,0 @@
|
||||
name: "Pull Request Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
13
.github/workflows/pr-require-label.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: Enforce PR labels
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, unlabeled, opened, edited, synchronize]
|
||||
jobs:
|
||||
enforce-label:
|
||||
name: Enforce label
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- if: toJson(github.event.pull_request.labels) == '[]'
|
||||
run: exit 1
|
||||
|
||||
6
Makefile
@@ -10,6 +10,12 @@ dev-update:
|
||||
dev-scale:
|
||||
docker compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans
|
||||
|
||||
stage:
|
||||
docker compose -f ./docker/docker-compose.staging.yml up --build -V --remove-orphans
|
||||
|
||||
pull-stage:
|
||||
docker compose -f ./docker/docker-compose.staging.yml pull
|
||||
|
||||
.PHONY: e2e
|
||||
e2e:
|
||||
docker compose -f ./e2e/docker-compose.yml up --build -V --remove-orphans
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
<a href="readme_i18n/README_zh_CN.md">中文</a>
|
||||
<a href="readme_i18n/README_ru_RU.md">Русский</a>
|
||||
<a href="readme_i18n/README_pt_BR.md">Português Brasileiro</a>
|
||||
<a href="readme_i18n/README_sv_SE.md">Svenska</a>
|
||||
<a href="readme_i18n/README_ar_JO.md">العربية</a>
|
||||
</p>
|
||||
|
||||
|
||||
4
cli/package-lock.json
generated
@@ -47,14 +47,14 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.106.0",
|
||||
"version": "1.105.1",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@oazapfts/runtime": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/node": "^20.12.13",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -84,7 +84,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: redis:6.2-alpine@sha256:d6c2911ac51b289db208767581a5d154544f2b2fe4914ea5056443f62dc6e900
|
||||
image: redis:6.2-alpine@sha256:e31ca60b18f7e9b78b573d156702471d4eda038803c0b8e6f01559f350031e93
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: redis:6.2-alpine@sha256:d6c2911ac51b289db208767581a5d154544f2b2fe4914ea5056443f62dc6e900
|
||||
image: redis:6.2-alpine@sha256:e31ca60b18f7e9b78b573d156702471d4eda038803c0b8e6f01559f350031e93
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
restart: always
|
||||
@@ -85,7 +85,7 @@ services:
|
||||
command: ['./run.sh', '-disable-reporting']
|
||||
ports:
|
||||
- 3000:3000
|
||||
image: grafana/grafana:11.0.0-ubuntu@sha256:dcd3ae78713958a862732c3608d32c03f0c279c35a2032d74b80b12c5cdc47b8
|
||||
image: grafana/grafana:11.0.0-ubuntu@sha256:02e99d1ee0b52dc9d3000c7b5314e7a07e0dfd69cc49bb3f8ce323491ed3406b
|
||||
volumes:
|
||||
- grafana-data:/var/lib/grafana
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: docker.io/redis:6.2-alpine@sha256:d6c2911ac51b289db208767581a5d154544f2b2fe4914ea5056443f62dc6e900
|
||||
image: docker.io/redis:6.2-alpine@sha256:e31ca60b18f7e9b78b573d156702471d4eda038803c0b8e6f01559f350031e93
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
restart: always
|
||||
|
||||
@@ -3,10 +3,10 @@ global:
|
||||
evaluation_interval: 15s
|
||||
|
||||
scrape_configs:
|
||||
- job_name: immich_api
|
||||
- job_name: immich_server
|
||||
static_configs:
|
||||
- targets: ['immich-server:8081']
|
||||
|
||||
|
||||
- job_name: immich_microservices
|
||||
static_configs:
|
||||
- targets: ['immich-server:8082']
|
||||
- targets: ['immich-microservices:8081']
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
LOG_LEVEL="${IMMICH_LOG_LEVEL:='info'}"
|
||||
|
||||
logDebug() {
|
||||
if [ "$LOG_LEVEL" = "debug" ] || [ "$LOG_LEVEL" = "verbose" ]; then
|
||||
echo "DEBUG: $1" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
|
||||
logDebug "cgroup v2 detected."
|
||||
if [ -f /sys/fs/cgroup/cpu.max ]; then
|
||||
read -r quota period </sys/fs/cgroup/cpu.max
|
||||
if [ "$quota" = "max" ]; then
|
||||
logDebug "No CPU limits set."
|
||||
unset quota period
|
||||
fi
|
||||
else
|
||||
logDebug "/sys/fs/cgroup/cpu.max not found."
|
||||
fi
|
||||
else
|
||||
logDebug "cgroup v1 detected."
|
||||
|
||||
if [ -f /sys/fs/cgroup/cpu/cpu.cfs_quota_us ] && [ -f /sys/fs/cgroup/cpu/cpu.cfs_period_us ]; then
|
||||
quota=$(cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us)
|
||||
period=$(cat /sys/fs/cgroup/cpu/cpu.cfs_period_us)
|
||||
|
||||
if [ "$quota" = "-1" ]; then
|
||||
logDebug "No CPU limits set."
|
||||
unset quota period
|
||||
fi
|
||||
else
|
||||
logDebug "/sys/fs/cgroup/cpu/cpu.cfs_quota_us or /sys/fs/cgroup/cpu/cpu.cfs_period_us not found."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "${quota:-}" ] && [ -n "${period:-}" ]; then
|
||||
cpus=$((quota / period))
|
||||
if [ "$cpus" -eq 0 ]; then
|
||||
cpus=1
|
||||
fi
|
||||
else
|
||||
cpus=$(grep -c processor /proc/cpuinfo)
|
||||
fi
|
||||
|
||||
echo "$cpus"
|
||||
@@ -1,23 +0,0 @@
|
||||
# Email Notifications
|
||||
|
||||
Immich supports the option to send notifications via Email for the following events:
|
||||
|
||||
- Creating a new user
|
||||
- Notifying a user when they get added to a shared album
|
||||
- Informing other users about the addition of new assets to a shared album
|
||||
|
||||
## SMTP settings
|
||||
|
||||
You can access the settings panel from the web at `Administration -> Settings -> Notification settings`
|
||||
|
||||
Under Email, enter the following details to connect with SMTP servers.
|
||||
|
||||
You can use the following [guide](/docs/guides/smtp-gmail) to use Gmail's SMTP server.
|
||||
|
||||
<img src={require('./img/email-settings.png').default} width="80%" title="SMTP settings" />
|
||||
|
||||
## User's notifications settings
|
||||
|
||||
Users can manage their email notification settings from their account settings page on the web. They can choose to turn email notifications on or off for the following events:
|
||||
|
||||
<img src={require('./img/user-notifications-settings.png').default} width="80%" title="User notification settings" />
|
||||
|
Before Width: | Height: | Size: 218 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 109 KiB |
@@ -19,11 +19,6 @@ this advice improves throughput, not latency, for example, it will make Smart Se
|
||||
|
||||
It is important to remember that jobs like Smart Search, Face Detection, Facial Recognition, and Transcode Videos require a **lot** of processing power and therefore do not exaggerate the amount of jobs because you're probably thoroughly overloading the server.
|
||||
|
||||
:::danger IMPORTANT
|
||||
If you increase the concurrency from the defaults we set, especially for thumbnail generation, make sure you do not increase them past the amount of CPU cores you have available.
|
||||
Doing so can impact API responsiveness with no gain in thumbnail generation speed.
|
||||
:::
|
||||
|
||||
:::info Facial Recognition Concurrency
|
||||
The Facial Recognition Concurrency value cannot be changed because
|
||||
[DBSCAN](https://www.youtube.com/watch?v=RDZUdRSDOok) is traditionally sequential, but there are parallel implementations of it out there. Our implementation isn't parallel.
|
||||
|
||||
@@ -13,20 +13,6 @@ Immich supports multiple users, each with their own library.
|
||||
|
||||
<UserCreate />
|
||||
|
||||
## Send new user email notification
|
||||
|
||||
:::note
|
||||
This option is only available if an SMTP server has been configured in the administrator settings.
|
||||
:::
|
||||
|
||||
Admin can send a welcome email if the Email option is set, you can learn here how to set up the SMTP server in Immich.
|
||||
|
||||
<img
|
||||
src={require('./img/send-user-email-notification.webp').default}
|
||||
width="40%"
|
||||
title="Send user email notification"
|
||||
/>
|
||||
|
||||
## Set Storage Quota For User
|
||||
|
||||
Admin can specify the storage quota for the user as the instance's admin; once the limit is reached, the user won't be able to upload to the instance anymore.
|
||||
|
||||
|
Before Width: | Height: | Size: 218 KiB |
|
Before Width: | Height: | Size: 17 KiB |
@@ -1,20 +0,0 @@
|
||||
# SMTP settings using Gmail
|
||||
|
||||
This guide walks you through how to get the information you need to set up your Immich instance to send emails using Gmail's SMTP server.
|
||||
|
||||
## Create an app password
|
||||
|
||||
From your Google account settings
|
||||
|
||||
- Add [2-Step Verification](https://support.google.com/accounts/answer/185839) to your Google account (Required)
|
||||
- [Create an app password](https://myaccount.google.com/apppasswords).
|
||||
|
||||
At the end of creating your app passwords, a password will be displayed; save it, it will be used for the password field when setting up the SMTP server in Immich.
|
||||
|
||||
<img src={require('./img/google-app-password.webp').default} title="Authorised redirect URIs" />
|
||||
|
||||
## Entering the SMTP credential in Immich
|
||||
|
||||
Entering your credential in Immich's email notification settings at `Administration -> Settings -> Notification Settings`
|
||||
|
||||
<img src={require('./img/email-settings.png').default} width="80%" title="SMTP settings" />
|
||||
@@ -38,19 +38,16 @@ Regardless of filesystem, it is not recommended to use a network share for your
|
||||
|
||||
## General
|
||||
|
||||
| Variable | Description | Default | Containers | Workers |
|
||||
| :---------------------------------- | :---------------------------------------------- | :--------------------------: | :----------------------- | :----------------- |
|
||||
| `TZ` | Timezone | | server | microservices |
|
||||
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
|
||||
| `IMMICH_LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
|
||||
| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload`<sup>\*1</sup> | server | api, microservices |
|
||||
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
|
||||
| `IMMICH_WEB_ROOT` | Path of root index.html | `/usr/src/app/www` | server | api |
|
||||
| `IMMICH_REVERSE_GEOCODING_ROOT` | Path of reverse geocoding dump directory | `/usr/src/resources` | server | microservices |
|
||||
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
|
||||
| `CPU_CORES` | Amount of cores available to the immich server | auto-detected cpu core count | server | |
|
||||
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
|
||||
| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices |
|
||||
| Variable | Description | Default | Containers | Workers |
|
||||
| :------------------------------ | :---------------------------------------------- | :----------------------: | :----------------------- | :----------------- |
|
||||
| `TZ` | Timezone | | server | microservices |
|
||||
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
|
||||
| `IMMICH_LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
|
||||
| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload`<sup>\*1</sup> | server | api, microservices |
|
||||
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
|
||||
| `IMMICH_WEB_ROOT` | Path of root index.html | `/usr/src/app/www` | server | api |
|
||||
| `IMMICH_REVERSE_GEOCODING_ROOT` | Path of reverse geocoding dump directory | `/usr/src/resources` | server | microservices |
|
||||
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
|
||||
|
||||
\*1: With the default `WORKDIR` of `/usr/src/app`, this path will resolve to `/usr/src/app/upload`.
|
||||
It only need to be set if the Immich deployment method is changing.
|
||||
|
||||
BIN
docs/static/img/ios-app-store-badge.png
vendored
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 15 KiB |
@@ -27,7 +27,7 @@ services:
|
||||
- 2283:3001
|
||||
|
||||
redis:
|
||||
image: redis:6.2-alpine@sha256:d6c2911ac51b289db208767581a5d154544f2b2fe4914ea5056443f62dc6e900
|
||||
image: redis:6.2-alpine@sha256:e31ca60b18f7e9b78b573d156702471d4eda038803c0b8e6f01559f350031e93
|
||||
|
||||
database:
|
||||
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
|
||||
|
||||
8
e2e/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "1.106.0",
|
||||
"version": "1.105.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich-e2e",
|
||||
"version": "1.106.0",
|
||||
"version": "1.105.1",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"devDependencies": {
|
||||
"@immich/cli": "file:../cli",
|
||||
@@ -81,14 +81,14 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.106.0",
|
||||
"version": "1.105.1",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@oazapfts/runtime": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/node": "^20.12.13",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "1.106.0",
|
||||
"version": "1.105.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
LibraryResponseDto,
|
||||
LoginResponseDto,
|
||||
ScanLibraryDto,
|
||||
getAllLibraries,
|
||||
removeOfflineFiles,
|
||||
scanLibrary,
|
||||
} from '@immich/sdk';
|
||||
import { LibraryResponseDto, LoginResponseDto, ScanLibraryDto, getAllLibraries, scanLibrary } from '@immich/sdk';
|
||||
import { cpSync, existsSync } from 'node:fs';
|
||||
import { Socket } from 'socket.io-client';
|
||||
import { userDto, uuidDto } from 'src/fixtures';
|
||||
@@ -391,51 +384,6 @@ describe('/libraries', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should not try to delete offline files', async () => {
|
||||
utils.createImageFile(`${testAssetDir}/temp/offline1/assetA.png`);
|
||||
|
||||
const library = await utils.createLibrary(admin.accessToken, {
|
||||
ownerId: admin.userId,
|
||||
importPaths: [`${testAssetDirInternal}/temp/offline1`],
|
||||
});
|
||||
|
||||
await scan(admin.accessToken, library.id);
|
||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||
|
||||
const { assets: initialAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
|
||||
expect(initialAssets).toEqual({
|
||||
count: 1,
|
||||
total: 1,
|
||||
facets: [],
|
||||
items: [expect.objectContaining({ originalFileName: 'assetA.png' })],
|
||||
nextPage: null,
|
||||
});
|
||||
|
||||
utils.removeImageFile(`${testAssetDir}/temp/offline1/assetA.png`);
|
||||
|
||||
await scan(admin.accessToken, library.id);
|
||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||
|
||||
const { assets: offlineAssets } = await utils.metadataSearch(admin.accessToken, {
|
||||
libraryId: library.id,
|
||||
isOffline: true,
|
||||
});
|
||||
expect(offlineAssets).toEqual({
|
||||
count: 1,
|
||||
total: 1,
|
||||
facets: [],
|
||||
items: [expect.objectContaining({ originalFileName: 'assetA.png' })],
|
||||
nextPage: null,
|
||||
});
|
||||
|
||||
utils.createImageFile(`${testAssetDir}/temp/offline1/assetA.png`);
|
||||
await removeOfflineFiles({ id: library.id }, { headers: asBearerAuth(admin.accessToken) });
|
||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||
await utils.waitForWebsocketEvent({ event: 'assetDelete', total: 1 });
|
||||
|
||||
expect(existsSync(`${testAssetDir}/temp/offline1/assetA.png`)).toBe(true);
|
||||
});
|
||||
|
||||
it('should scan new files', async () => {
|
||||
const library = await utils.createLibrary(admin.accessToken, {
|
||||
ownerId: admin.userId,
|
||||
@@ -559,10 +507,10 @@ describe('/libraries', () => {
|
||||
it('should remove offline files', async () => {
|
||||
const library = await utils.createLibrary(admin.accessToken, {
|
||||
ownerId: admin.userId,
|
||||
importPaths: [`${testAssetDirInternal}/temp/offline2`],
|
||||
importPaths: [`${testAssetDirInternal}/temp`],
|
||||
});
|
||||
|
||||
utils.createImageFile(`${testAssetDir}/temp/offline2/assetA.png`);
|
||||
utils.createImageFile(`${testAssetDir}/temp/directoryA/assetB.png`);
|
||||
|
||||
await scan(admin.accessToken, library.id);
|
||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||
@@ -570,9 +518,9 @@ describe('/libraries', () => {
|
||||
const { assets: initialAssets } = await utils.metadataSearch(admin.accessToken, {
|
||||
libraryId: library.id,
|
||||
});
|
||||
expect(initialAssets.count).toBe(1);
|
||||
expect(initialAssets.count).toBe(3);
|
||||
|
||||
utils.removeImageFile(`${testAssetDir}/temp/offline2/assetA.png`);
|
||||
utils.removeImageFile(`${testAssetDir}/temp/directoryA/assetB.png`);
|
||||
|
||||
await scan(admin.accessToken, library.id);
|
||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||
@@ -593,7 +541,7 @@ describe('/libraries', () => {
|
||||
|
||||
const { assets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
|
||||
|
||||
expect(assets.count).toBe(0);
|
||||
expect(assets.count).toBe(2);
|
||||
});
|
||||
|
||||
it('should not remove online files', async () => {
|
||||
|
||||
@@ -5,61 +5,51 @@ export const errorDto = {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Authentication required',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
forbidden: {
|
||||
error: 'Forbidden',
|
||||
statusCode: 403,
|
||||
message: expect.any(String),
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
wrongPassword: {
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: 'Wrong password',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
invalidToken: {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Invalid user token',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
invalidShareKey: {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Invalid share key',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
invalidSharePassword: {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Invalid password',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
badRequest: (message: any = null) => ({
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: message ?? expect.anything(),
|
||||
correlationId: expect.any(String),
|
||||
}),
|
||||
noPermission: {
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: expect.stringContaining('Not found or no'),
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
incorrectLogin: {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Incorrect email or password',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
alreadyHasAdmin: {
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: 'The server already has an admin',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -323,40 +323,6 @@ export const utils = {
|
||||
return body as AssetMediaResponseDto;
|
||||
},
|
||||
|
||||
replaceAsset: async (
|
||||
accessToken: string,
|
||||
assetId: string,
|
||||
dto?: Partial<Omit<AssetMediaCreateDto, 'assetData'>> & { assetData?: AssetData },
|
||||
) => {
|
||||
const _dto = {
|
||||
deviceAssetId: 'test-1',
|
||||
deviceId: 'test',
|
||||
fileCreatedAt: new Date().toISOString(),
|
||||
fileModifiedAt: new Date().toISOString(),
|
||||
...dto,
|
||||
};
|
||||
|
||||
const assetData = dto?.assetData?.bytes || makeRandomImage();
|
||||
const filename = dto?.assetData?.filename || 'example.png';
|
||||
|
||||
if (dto?.assetData?.bytes) {
|
||||
console.log(`Uploading ${filename}`);
|
||||
}
|
||||
|
||||
const builder = request(app)
|
||||
.put(`/assets/${assetId}/original`)
|
||||
.attach('assetData', assetData, filename)
|
||||
.set('Authorization', `Bearer ${accessToken}`);
|
||||
|
||||
for (const [key, value] of Object.entries(_dto)) {
|
||||
void builder.field(key, String(value));
|
||||
}
|
||||
|
||||
const { body } = await builder;
|
||||
|
||||
return body as AssetMediaResponseDto;
|
||||
},
|
||||
|
||||
createImageFile: (path: string) => {
|
||||
if (!existsSync(dirname(path))) {
|
||||
mkdirSync(dirname(path), { recursive: true });
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import { AssetMediaResponseDto, LoginResponseDto } from '@immich/sdk';
|
||||
import { Page, expect, test } from '@playwright/test';
|
||||
import { utils } from 'src/utils';
|
||||
|
||||
function imageLocator(page: Page) {
|
||||
return page.getByAltText('Image taken on').locator('visible=true');
|
||||
}
|
||||
test.describe('Photo Viewer', () => {
|
||||
let admin: LoginResponseDto;
|
||||
let asset: AssetMediaResponseDto;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
utils.initSdk();
|
||||
await utils.resetDatabase();
|
||||
admin = await utils.adminSetup();
|
||||
asset = await utils.createAsset(admin.accessToken);
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
// before each test, login as user
|
||||
await utils.setAuthCookies(context, admin.accessToken);
|
||||
await page.goto('/photos');
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
test('initially shows a loading spinner', async ({ page }) => {
|
||||
await page.route(`/api/assets/${asset.id}/thumbnail**`, async (route) => {
|
||||
// slow down the request for thumbnail, so spiner has chance to show up
|
||||
await new Promise((f) => setTimeout(f, 2000));
|
||||
await route.continue();
|
||||
});
|
||||
await page.goto(`/photos/${asset.id}`);
|
||||
await page.waitForLoadState('load');
|
||||
// this is the spinner
|
||||
await page.waitForSelector('svg[role=status]');
|
||||
await expect(page.getByRole('status')).toBeVisible();
|
||||
});
|
||||
|
||||
test('loads high resolution photo when zoomed', async ({ page }) => {
|
||||
await page.goto(`/photos/${asset.id}`);
|
||||
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail');
|
||||
const box = await imageLocator(page).boundingBox();
|
||||
expect(box).toBeTruthy;
|
||||
const { x, y, width, height } = box!;
|
||||
await page.mouse.move(x + width / 2, y + height / 2);
|
||||
await page.mouse.wheel(0, -1);
|
||||
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('original');
|
||||
});
|
||||
|
||||
test('reloads photo when checksum changes', async ({ page }) => {
|
||||
await page.goto(`/photos/${asset.id}`);
|
||||
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail');
|
||||
const initialSrc = await imageLocator(page).getAttribute('src');
|
||||
await utils.replaceAsset(admin.accessToken, asset.id);
|
||||
await expect.poll(async () => await imageLocator(page).getAttribute('src')).not.toBe(initialSrc);
|
||||
});
|
||||
});
|
||||
@@ -12,6 +12,8 @@ from rich.logging import RichHandler
|
||||
from uvicorn import Server
|
||||
from uvicorn.workers import UvicornWorker
|
||||
|
||||
from .schemas import ModelType
|
||||
|
||||
|
||||
class PreloadModelData(BaseModel):
|
||||
clip: str | None
|
||||
@@ -19,7 +21,7 @@ class PreloadModelData(BaseModel):
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
cache_folder: Path = Path("/cache")
|
||||
cache_folder: str = "/cache"
|
||||
model_ttl: int = 300
|
||||
model_ttl_poll_s: int = 10
|
||||
host: str = "0.0.0.0"
|
||||
@@ -53,6 +55,14 @@ def clean_name(model_name: str) -> str:
|
||||
return model_name.split("/")[-1].translate(_clean_name)
|
||||
|
||||
|
||||
def get_cache_dir(model_name: str, model_type: ModelType) -> Path:
|
||||
return Path(settings.cache_folder) / model_type.value / clean_name(model_name)
|
||||
|
||||
|
||||
def get_hf_model_name(model_name: str) -> str:
|
||||
return f"immich-app/{clean_name(model_name)}"
|
||||
|
||||
|
||||
LOG_LEVELS: dict[str, int] = {
|
||||
"critical": logging.ERROR,
|
||||
"error": logging.ERROR,
|
||||
|
||||
@@ -6,34 +6,22 @@ import threading
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from contextlib import asynccontextmanager
|
||||
from functools import partial
|
||||
from typing import Any, AsyncGenerator, Callable, Iterator
|
||||
from zipfile import BadZipFile
|
||||
|
||||
import orjson
|
||||
from fastapi import Depends, FastAPI, File, Form, HTTPException
|
||||
from fastapi import Depends, FastAPI, Form, HTTPException, UploadFile
|
||||
from fastapi.responses import ORJSONResponse
|
||||
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf, NoSuchFile
|
||||
from PIL.Image import Image
|
||||
from pydantic import ValidationError
|
||||
from starlette.formparsers import MultiPartParser
|
||||
|
||||
from app.models import get_model_deps
|
||||
from app.models.base import InferenceModel
|
||||
from app.models.transforms import decode_pil
|
||||
|
||||
from .config import PreloadModelData, log, settings
|
||||
from .models.cache import ModelCache
|
||||
from .schemas import (
|
||||
InferenceEntries,
|
||||
InferenceEntry,
|
||||
InferenceResponse,
|
||||
MessageResponse,
|
||||
ModelIdentity,
|
||||
ModelTask,
|
||||
ModelType,
|
||||
PipelineRequest,
|
||||
T,
|
||||
TextResponse,
|
||||
)
|
||||
|
||||
@@ -75,21 +63,12 @@ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
|
||||
gc.collect()
|
||||
|
||||
|
||||
async def preload_models(preload: PreloadModelData) -> None:
|
||||
log.info(f"Preloading models: {preload}")
|
||||
if preload.clip is not None:
|
||||
model = await model_cache.get(preload.clip, ModelType.TEXTUAL, ModelTask.SEARCH)
|
||||
await load(model)
|
||||
|
||||
model = await model_cache.get(preload.clip, ModelType.VISUAL, ModelTask.SEARCH)
|
||||
await load(model)
|
||||
|
||||
if preload.facial_recognition is not None:
|
||||
model = await model_cache.get(preload.facial_recognition, ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)
|
||||
await load(model)
|
||||
|
||||
model = await model_cache.get(preload.facial_recognition, ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION)
|
||||
await load(model)
|
||||
async def preload_models(preload_models: PreloadModelData) -> None:
|
||||
log.info(f"Preloading models: {preload_models}")
|
||||
if preload_models.clip is not None:
|
||||
await load(await model_cache.get(preload_models.clip, ModelType.CLIP))
|
||||
if preload_models.facial_recognition is not None:
|
||||
await load(await model_cache.get(preload_models.facial_recognition, ModelType.FACIAL_RECOGNITION))
|
||||
|
||||
|
||||
def update_state() -> Iterator[None]:
|
||||
@@ -102,27 +81,6 @@ def update_state() -> Iterator[None]:
|
||||
active_requests -= 1
|
||||
|
||||
|
||||
def get_entries(entries: str = Form()) -> InferenceEntries:
|
||||
try:
|
||||
request: PipelineRequest = orjson.loads(entries)
|
||||
without_deps: list[InferenceEntry] = []
|
||||
with_deps: list[InferenceEntry] = []
|
||||
for task, types in request.items():
|
||||
for type, entry in types.items():
|
||||
parsed: InferenceEntry = {
|
||||
"name": entry["modelName"],
|
||||
"task": task,
|
||||
"type": type,
|
||||
"options": entry.get("options", {}),
|
||||
}
|
||||
dep = get_model_deps(parsed["name"], type, task)
|
||||
(with_deps if dep else without_deps).append(parsed)
|
||||
return without_deps, with_deps
|
||||
except (orjson.JSONDecodeError, ValidationError, KeyError, AttributeError) as e:
|
||||
log.error(f"Invalid request format: {e}")
|
||||
raise HTTPException(422, "Invalid request format.")
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
|
||||
@@ -138,63 +96,42 @@ def ping() -> str:
|
||||
|
||||
@app.post("/predict", dependencies=[Depends(update_state)])
|
||||
async def predict(
|
||||
entries: InferenceEntries = Depends(get_entries),
|
||||
image: bytes | None = File(default=None),
|
||||
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: Image | str = await run(lambda: decode_pil(image))
|
||||
inputs: str | bytes = await image.read()
|
||||
elif text is not None:
|
||||
inputs = text
|
||||
else:
|
||||
raise HTTPException(400, "Either image or text must be provided")
|
||||
response = await run_inference(inputs, entries)
|
||||
return ORJSONResponse(response)
|
||||
try:
|
||||
kwargs = orjson.loads(options)
|
||||
except orjson.JSONDecodeError:
|
||||
raise HTTPException(400, f"Invalid options JSON: {options}")
|
||||
|
||||
model = await load(await model_cache.get(model_name, model_type, ttl=settings.model_ttl, **kwargs))
|
||||
model.configure(**kwargs)
|
||||
outputs = await run(model.predict, inputs)
|
||||
return ORJSONResponse(outputs)
|
||||
|
||||
|
||||
async def run_inference(payload: Image | str, entries: InferenceEntries) -> InferenceResponse:
|
||||
outputs: dict[ModelIdentity, Any] = {}
|
||||
response: InferenceResponse = {}
|
||||
|
||||
async def _run_inference(entry: InferenceEntry) -> None:
|
||||
model = await model_cache.get(entry["name"], entry["type"], entry["task"], ttl=settings.model_ttl)
|
||||
inputs = [payload]
|
||||
for dep in model.depends:
|
||||
try:
|
||||
inputs.append(outputs[dep])
|
||||
except KeyError:
|
||||
message = f"Task {entry['task']} of type {entry['type']} depends on output of {dep}"
|
||||
raise HTTPException(400, message)
|
||||
model = await load(model)
|
||||
output = await run(model.predict, *inputs, **entry["options"])
|
||||
outputs[model.identity] = output
|
||||
response[entry["task"]] = output
|
||||
|
||||
without_deps, with_deps = entries
|
||||
await asyncio.gather(*[_run_inference(entry) for entry in without_deps])
|
||||
if with_deps:
|
||||
await asyncio.gather(*[_run_inference(entry) for entry in with_deps])
|
||||
if isinstance(payload, Image):
|
||||
response["imageHeight"], response["imageWidth"] = payload.height, payload.width
|
||||
|
||||
return response
|
||||
|
||||
|
||||
async def run(func: Callable[..., T], *args: Any, **kwargs: Any) -> T:
|
||||
async def run(func: Callable[..., Any], inputs: Any) -> Any:
|
||||
if thread_pool is None:
|
||||
return func(*args, **kwargs)
|
||||
partial_func = partial(func, *args, **kwargs)
|
||||
return await asyncio.get_running_loop().run_in_executor(thread_pool, partial_func)
|
||||
return func(inputs)
|
||||
return await asyncio.get_running_loop().run_in_executor(thread_pool, func, inputs)
|
||||
|
||||
|
||||
async def load(model: InferenceModel) -> InferenceModel:
|
||||
if model.loaded:
|
||||
return model
|
||||
|
||||
def _load(model: InferenceModel) -> InferenceModel:
|
||||
def _load(model: InferenceModel) -> None:
|
||||
with lock:
|
||||
model.load()
|
||||
return model
|
||||
|
||||
try:
|
||||
await run(_load, model)
|
||||
|
||||
@@ -1,40 +1,24 @@
|
||||
from typing import Any
|
||||
|
||||
from app.models.base import InferenceModel
|
||||
from app.models.clip.textual import MClipTextualEncoder, OpenClipTextualEncoder
|
||||
from app.models.clip.visual import OpenClipVisualEncoder
|
||||
from app.schemas import ModelSource, ModelTask, ModelType
|
||||
from app.schemas import ModelType
|
||||
|
||||
from .constants import get_model_source
|
||||
from .facial_recognition.detection import FaceDetector
|
||||
from .facial_recognition.recognition import FaceRecognizer
|
||||
from .base import InferenceModel
|
||||
from .clip import MCLIPEncoder, OpenCLIPEncoder
|
||||
from .constants import is_insightface, is_mclip, is_openclip
|
||||
from .facial_recognition import FaceRecognizer
|
||||
|
||||
|
||||
def get_model_class(model_name: str, model_type: ModelType, model_task: ModelTask) -> type[InferenceModel]:
|
||||
source = get_model_source(model_name)
|
||||
match source, model_type, model_task:
|
||||
case ModelSource.OPENCLIP | ModelSource.MCLIP, ModelType.VISUAL, ModelTask.SEARCH:
|
||||
return OpenClipVisualEncoder
|
||||
|
||||
case ModelSource.OPENCLIP, ModelType.TEXTUAL, ModelTask.SEARCH:
|
||||
return OpenClipTextualEncoder
|
||||
|
||||
case ModelSource.MCLIP, ModelType.TEXTUAL, ModelTask.SEARCH:
|
||||
return MClipTextualEncoder
|
||||
|
||||
case ModelSource.INSIGHTFACE, ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION:
|
||||
return FaceDetector
|
||||
|
||||
case ModelSource.INSIGHTFACE, ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION:
|
||||
return FaceRecognizer
|
||||
|
||||
def from_model_type(model_type: ModelType, model_name: str, **model_kwargs: Any) -> InferenceModel:
|
||||
match model_type:
|
||||
case ModelType.CLIP:
|
||||
if is_openclip(model_name):
|
||||
return OpenCLIPEncoder(model_name, **model_kwargs)
|
||||
elif is_mclip(model_name):
|
||||
return MCLIPEncoder(model_name, **model_kwargs)
|
||||
case ModelType.FACIAL_RECOGNITION:
|
||||
if is_insightface(model_name):
|
||||
return FaceRecognizer(model_name, **model_kwargs)
|
||||
case _:
|
||||
raise ValueError(f"Unknown model combination: {source}, {model_type}, {model_task}")
|
||||
raise ValueError(f"Unknown model type {model_type}")
|
||||
|
||||
|
||||
def from_model_type(model_name: str, model_type: ModelType, model_task: ModelTask, **kwargs: Any) -> InferenceModel:
|
||||
return get_model_class(model_name, model_type, model_task)(model_name, **kwargs)
|
||||
|
||||
|
||||
def get_model_deps(model_name: str, model_type: ModelType, model_task: ModelTask) -> list[tuple[ModelType, ModelTask]]:
|
||||
return get_model_class(model_name, model_type, model_task).depends
|
||||
raise ValueError(f"Unknown {model_type} model {model_name}")
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
from shutil import rmtree
|
||||
from typing import Any, ClassVar
|
||||
from typing import Any
|
||||
|
||||
import onnxruntime as ort
|
||||
from huggingface_hub import snapshot_download
|
||||
@@ -11,14 +11,13 @@ from huggingface_hub import snapshot_download
|
||||
import ann.ann
|
||||
from app.models.constants import SUPPORTED_PROVIDERS
|
||||
|
||||
from ..config import clean_name, log, settings
|
||||
from ..schemas import ModelFormat, ModelIdentity, ModelSession, ModelTask, ModelType
|
||||
from ..config import get_cache_dir, get_hf_model_name, log, settings
|
||||
from ..schemas import ModelRuntime, ModelType
|
||||
from .ann import AnnSession
|
||||
|
||||
|
||||
class InferenceModel(ABC):
|
||||
depends: ClassVar[list[ModelIdentity]]
|
||||
identity: ClassVar[ModelIdentity]
|
||||
_model_type: ModelType
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -27,16 +26,16 @@ class InferenceModel(ABC):
|
||||
providers: list[str] | None = None,
|
||||
provider_options: list[dict[str, Any]] | None = None,
|
||||
sess_options: ort.SessionOptions | None = None,
|
||||
preferred_format: ModelFormat | None = None,
|
||||
preferred_runtime: ModelRuntime | None = None,
|
||||
**model_kwargs: Any,
|
||||
) -> None:
|
||||
self.loaded = False
|
||||
self.model_name = clean_name(model_name)
|
||||
self.model_name = model_name
|
||||
self.cache_dir = Path(cache_dir) if cache_dir is not None else self.cache_dir_default
|
||||
self.providers = providers if providers is not None else self.providers_default
|
||||
self.provider_options = provider_options if provider_options is not None else self.provider_options_default
|
||||
self.sess_options = sess_options if sess_options is not None else self.sess_options_default
|
||||
self.preferred_format = preferred_format if preferred_format is not None else self.preferred_format_default
|
||||
self.preferred_runtime = preferred_runtime if preferred_runtime is not None else self.preferred_runtime_default
|
||||
|
||||
def download(self) -> None:
|
||||
if not self.cached:
|
||||
@@ -48,36 +47,35 @@ class InferenceModel(ABC):
|
||||
def load(self) -> None:
|
||||
if self.loaded:
|
||||
return
|
||||
|
||||
self.download()
|
||||
log.info(f"Loading {self.model_type.replace('-', ' ')} model '{self.model_name}' to memory")
|
||||
self.session = self._load()
|
||||
self._load()
|
||||
self.loaded = True
|
||||
|
||||
def predict(self, *inputs: Any, **model_kwargs: Any) -> Any:
|
||||
def predict(self, inputs: Any, **model_kwargs: Any) -> Any:
|
||||
self.load()
|
||||
if model_kwargs:
|
||||
self.configure(**model_kwargs)
|
||||
return self._predict(*inputs, **model_kwargs)
|
||||
return self._predict(inputs)
|
||||
|
||||
@abstractmethod
|
||||
def _predict(self, *inputs: Any, **model_kwargs: Any) -> Any: ...
|
||||
def _predict(self, inputs: Any) -> Any: ...
|
||||
|
||||
def configure(self, **kwargs: Any) -> None:
|
||||
def configure(self, **model_kwargs: Any) -> None:
|
||||
pass
|
||||
|
||||
def _download(self) -> None:
|
||||
ignore_patterns = [] if self.preferred_format == ModelFormat.ARMNN else ["*.armnn"]
|
||||
ignore_patterns = [] if self.preferred_runtime == ModelRuntime.ARMNN else ["*.armnn"]
|
||||
snapshot_download(
|
||||
f"immich-app/{clean_name(self.model_name)}",
|
||||
get_hf_model_name(self.model_name),
|
||||
cache_dir=self.cache_dir,
|
||||
local_dir=self.cache_dir,
|
||||
local_dir_use_symlinks=False,
|
||||
ignore_patterns=ignore_patterns,
|
||||
)
|
||||
|
||||
def _load(self) -> ModelSession:
|
||||
return self._make_session(self.model_path)
|
||||
@abstractmethod
|
||||
def _load(self) -> None: ...
|
||||
|
||||
def clear_cache(self) -> None:
|
||||
if not self.cache_dir.exists():
|
||||
@@ -101,7 +99,7 @@ class InferenceModel(ABC):
|
||||
self.cache_dir.unlink()
|
||||
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def _make_session(self, model_path: Path) -> ModelSession:
|
||||
def _make_session(self, model_path: Path) -> AnnSession | ort.InferenceSession:
|
||||
if not model_path.is_file():
|
||||
onnx_path = model_path.with_suffix(".onnx")
|
||||
if not onnx_path.is_file():
|
||||
@@ -126,21 +124,9 @@ class InferenceModel(ABC):
|
||||
raise ValueError(f"Unsupported model file type: {model_path.suffix}")
|
||||
return session
|
||||
|
||||
@property
|
||||
def model_dir(self) -> Path:
|
||||
return self.cache_dir / self.model_type.value
|
||||
|
||||
@property
|
||||
def model_path(self) -> Path:
|
||||
return self.model_dir / f"model.{self.preferred_format}"
|
||||
|
||||
@property
|
||||
def model_task(self) -> ModelTask:
|
||||
return self.identity[1]
|
||||
|
||||
@property
|
||||
def model_type(self) -> ModelType:
|
||||
return self.identity[0]
|
||||
return self._model_type
|
||||
|
||||
@property
|
||||
def cache_dir(self) -> Path:
|
||||
@@ -152,11 +138,11 @@ class InferenceModel(ABC):
|
||||
|
||||
@property
|
||||
def cache_dir_default(self) -> Path:
|
||||
return settings.cache_folder / self.model_task.value / self.model_name
|
||||
return get_cache_dir(self.model_name, self.model_type)
|
||||
|
||||
@property
|
||||
def cached(self) -> bool:
|
||||
return self.model_path.is_file()
|
||||
return self.cache_dir.is_dir() and any(self.cache_dir.iterdir())
|
||||
|
||||
@property
|
||||
def providers(self) -> list[str]:
|
||||
@@ -240,14 +226,14 @@ class InferenceModel(ABC):
|
||||
return sess_options
|
||||
|
||||
@property
|
||||
def preferred_format(self) -> ModelFormat:
|
||||
return self._preferred_format
|
||||
def preferred_runtime(self) -> ModelRuntime:
|
||||
return self._preferred_runtime
|
||||
|
||||
@preferred_format.setter
|
||||
def preferred_format(self, preferred_format: ModelFormat) -> None:
|
||||
log.debug(f"Setting preferred format to {preferred_format}")
|
||||
self._preferred_format = preferred_format
|
||||
@preferred_runtime.setter
|
||||
def preferred_runtime(self, preferred_runtime: ModelRuntime) -> None:
|
||||
log.debug(f"Setting preferred runtime to {preferred_runtime}")
|
||||
self._preferred_runtime = preferred_runtime
|
||||
|
||||
@property
|
||||
def preferred_format_default(self) -> ModelFormat:
|
||||
return ModelFormat.ARMNN if ann.ann.is_available and settings.ann else ModelFormat.ONNX
|
||||
def preferred_runtime_default(self) -> ModelRuntime:
|
||||
return ModelRuntime.ARMNN if ann.ann.is_available and settings.ann else ModelRuntime.ONNX
|
||||
|
||||
@@ -5,9 +5,9 @@ from aiocache.lock import OptimisticLock
|
||||
from aiocache.plugins import TimingPlugin
|
||||
|
||||
from app.models import from_model_type
|
||||
from app.models.base import InferenceModel
|
||||
|
||||
from ..schemas import ModelTask, ModelType, has_profiling
|
||||
from ..schemas import ModelType, has_profiling
|
||||
from .base import InferenceModel
|
||||
|
||||
|
||||
class ModelCache:
|
||||
@@ -31,21 +31,28 @@ class ModelCache:
|
||||
if profiling:
|
||||
plugins.append(TimingPlugin())
|
||||
|
||||
self.should_revalidate = revalidate
|
||||
self.revalidate_enable = revalidate
|
||||
|
||||
self.cache = SimpleMemoryCache(timeout=timeout, plugins=plugins, namespace=None)
|
||||
|
||||
async def get(
|
||||
self, model_name: str, model_type: ModelType, model_task: ModelTask, **model_kwargs: Any
|
||||
) -> InferenceModel:
|
||||
key = f"{model_name}{model_type}{model_task}"
|
||||
async def get(self, model_name: str, model_type: ModelType, **model_kwargs: Any) -> InferenceModel:
|
||||
"""
|
||||
Args:
|
||||
model_name: Name of model in the model hub used for the task.
|
||||
model_type: Model type or task, which determines which model zoo is used.
|
||||
|
||||
Returns:
|
||||
model: The requested model.
|
||||
"""
|
||||
|
||||
key = f"{model_name}{model_type.value}{model_kwargs.get('mode', '')}"
|
||||
|
||||
async with OptimisticLock(self.cache, key) as lock:
|
||||
model: InferenceModel | None = await self.cache.get(key)
|
||||
if model is None:
|
||||
model = from_model_type(model_name, model_type, model_task, **model_kwargs)
|
||||
model = from_model_type(model_type, model_name, **model_kwargs)
|
||||
await lock.cas(model, ttl=model_kwargs.get("ttl", None))
|
||||
elif self.should_revalidate:
|
||||
elif self.revalidate_enable:
|
||||
await self.revalidate(key, model_kwargs.get("ttl", None))
|
||||
return model
|
||||
|
||||
|
||||
189
machine-learning/app/models/clip.py
Normal file
@@ -0,0 +1,189 @@
|
||||
import json
|
||||
from abc import abstractmethod
|
||||
from functools import cached_property
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
|
||||
import numpy as np
|
||||
from numpy.typing import NDArray
|
||||
from PIL import Image
|
||||
from tokenizers import Encoding, Tokenizer
|
||||
|
||||
from app.config import clean_name, log
|
||||
from app.models.transforms import crop, get_pil_resampling, normalize, resize, to_numpy
|
||||
from app.schemas import ModelType
|
||||
|
||||
from .base import InferenceModel
|
||||
|
||||
|
||||
class BaseCLIPEncoder(InferenceModel):
|
||||
_model_type = ModelType.CLIP
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str,
|
||||
cache_dir: Path | str | None = None,
|
||||
mode: Literal["text", "vision"] | None = None,
|
||||
**model_kwargs: Any,
|
||||
) -> None:
|
||||
self.mode = mode
|
||||
super().__init__(model_name, cache_dir, **model_kwargs)
|
||||
|
||||
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 = self._make_session(self.textual_path)
|
||||
log.debug(f"Loaded clip text model '{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 = self._make_session(self.visual_path)
|
||||
log.debug(f"Loaded clip vision model '{self.model_name}'")
|
||||
|
||||
def _predict(self, image_or_text: Image.Image | str) -> NDArray[np.float32]:
|
||||
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")
|
||||
outputs: NDArray[np.float32] = self.vision_model.run(None, self.transform(image_or_text))[0][0]
|
||||
case str():
|
||||
if self.mode == "vision":
|
||||
raise TypeError("Cannot encode text as vision-only model")
|
||||
outputs = self.text_model.run(None, self.tokenize(image_or_text))[0][0]
|
||||
case _:
|
||||
raise TypeError(f"Expected Image or str, but got: {type(image_or_text)}")
|
||||
|
||||
return outputs
|
||||
|
||||
@abstractmethod
|
||||
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]:
|
||||
pass
|
||||
|
||||
@property
|
||||
def textual_dir(self) -> Path:
|
||||
return self.cache_dir / "textual"
|
||||
|
||||
@property
|
||||
def visual_dir(self) -> Path:
|
||||
return self.cache_dir / "visual"
|
||||
|
||||
@property
|
||||
def model_cfg_path(self) -> Path:
|
||||
return self.cache_dir / "config.json"
|
||||
|
||||
@property
|
||||
def textual_path(self) -> Path:
|
||||
return self.textual_dir / f"model.{self.preferred_runtime}"
|
||||
|
||||
@property
|
||||
def visual_path(self) -> Path:
|
||||
return self.visual_dir / f"model.{self.preferred_runtime}"
|
||||
|
||||
@property
|
||||
def tokenizer_file_path(self) -> Path:
|
||||
return self.textual_dir / "tokenizer.json"
|
||||
|
||||
@property
|
||||
def tokenizer_cfg_path(self) -> Path:
|
||||
return self.textual_dir / "tokenizer_config.json"
|
||||
|
||||
@property
|
||||
def preprocess_cfg_path(self) -> Path:
|
||||
return self.visual_dir / "preprocess_cfg.json"
|
||||
|
||||
@property
|
||||
def cached(self) -> bool:
|
||||
return self.textual_path.is_file() and self.visual_path.is_file()
|
||||
|
||||
@cached_property
|
||||
def model_cfg(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading model config for CLIP model '{self.model_name}'")
|
||||
model_cfg: dict[str, Any] = json.load(self.model_cfg_path.open())
|
||||
log.debug(f"Loaded model config for CLIP model '{self.model_name}'")
|
||||
return model_cfg
|
||||
|
||||
@cached_property
|
||||
def tokenizer_file(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading tokenizer file for CLIP model '{self.model_name}'")
|
||||
tokenizer_file: dict[str, Any] = json.load(self.tokenizer_file_path.open())
|
||||
log.debug(f"Loaded tokenizer file for CLIP model '{self.model_name}'")
|
||||
return tokenizer_file
|
||||
|
||||
@cached_property
|
||||
def tokenizer_cfg(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading tokenizer config for CLIP model '{self.model_name}'")
|
||||
tokenizer_cfg: dict[str, Any] = json.load(self.tokenizer_cfg_path.open())
|
||||
log.debug(f"Loaded tokenizer config for CLIP model '{self.model_name}'")
|
||||
return tokenizer_cfg
|
||||
|
||||
@cached_property
|
||||
def preprocess_cfg(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading visual preprocessing config for CLIP model '{self.model_name}'")
|
||||
preprocess_cfg: dict[str, Any] = json.load(self.preprocess_cfg_path.open())
|
||||
log.debug(f"Loaded visual preprocessing config for CLIP model '{self.model_name}'")
|
||||
return preprocess_cfg
|
||||
|
||||
|
||||
class OpenCLIPEncoder(BaseCLIPEncoder):
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str,
|
||||
cache_dir: Path | str | None = None,
|
||||
mode: Literal["text", "vision"] | None = None,
|
||||
**model_kwargs: Any,
|
||||
) -> None:
|
||||
super().__init__(clean_name(model_name), cache_dir, mode, **model_kwargs)
|
||||
|
||||
def _load(self) -> None:
|
||||
super()._load()
|
||||
self._load_tokenizer()
|
||||
|
||||
size: list[int] | int = self.preprocess_cfg["size"]
|
||||
self.size = size[0] if isinstance(size, list) else size
|
||||
|
||||
self.resampling = get_pil_resampling(self.preprocess_cfg["interpolation"])
|
||||
self.mean = np.array(self.preprocess_cfg["mean"], dtype=np.float32)
|
||||
self.std = np.array(self.preprocess_cfg["std"], dtype=np.float32)
|
||||
|
||||
def _load_tokenizer(self) -> Tokenizer:
|
||||
log.debug(f"Loading tokenizer for CLIP model '{self.model_name}'")
|
||||
|
||||
text_cfg: dict[str, Any] = self.model_cfg["text_cfg"]
|
||||
context_length: int = text_cfg.get("context_length", 77)
|
||||
pad_token: str = self.tokenizer_cfg["pad_token"]
|
||||
|
||||
self.tokenizer: Tokenizer = Tokenizer.from_file(self.tokenizer_file_path.as_posix())
|
||||
|
||||
pad_id: int = self.tokenizer.token_to_id(pad_token)
|
||||
self.tokenizer.enable_padding(length=context_length, pad_token=pad_token, pad_id=pad_id)
|
||||
self.tokenizer.enable_truncation(max_length=context_length)
|
||||
|
||||
log.debug(f"Loaded tokenizer for CLIP model '{self.model_name}'")
|
||||
|
||||
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
|
||||
tokens: Encoding = self.tokenizer.encode(text)
|
||||
return {"text": np.array([tokens.ids], dtype=np.int32)}
|
||||
|
||||
def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]:
|
||||
image = resize(image, self.size)
|
||||
image = crop(image, self.size)
|
||||
image_np = to_numpy(image)
|
||||
image_np = normalize(image_np, self.mean, self.std)
|
||||
return {"image": np.expand_dims(image_np.transpose(2, 0, 1), 0)}
|
||||
|
||||
|
||||
class MCLIPEncoder(OpenCLIPEncoder):
|
||||
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
|
||||
tokens: Encoding = self.tokenizer.encode(text)
|
||||
return {
|
||||
"input_ids": np.array([tokens.ids], dtype=np.int32),
|
||||
"attention_mask": np.array([tokens.attention_mask], dtype=np.int32),
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
import json
|
||||
from abc import abstractmethod
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from numpy.typing import NDArray
|
||||
from tokenizers import Encoding, Tokenizer
|
||||
|
||||
from app.config import log
|
||||
from app.models.base import InferenceModel
|
||||
from app.schemas import ModelSession, ModelTask, ModelType
|
||||
|
||||
|
||||
class BaseCLIPTextualEncoder(InferenceModel):
|
||||
depends = []
|
||||
identity = (ModelType.TEXTUAL, ModelTask.SEARCH)
|
||||
|
||||
def _predict(self, inputs: str, **kwargs: Any) -> NDArray[np.float32]:
|
||||
res: NDArray[np.float32] = self.session.run(None, self.tokenize(inputs))[0][0]
|
||||
return res
|
||||
|
||||
def _load(self) -> ModelSession:
|
||||
log.debug(f"Loading tokenizer for CLIP model '{self.model_name}'")
|
||||
self.tokenizer = self._load_tokenizer()
|
||||
log.debug(f"Loaded tokenizer for CLIP model '{self.model_name}'")
|
||||
|
||||
return super()._load()
|
||||
|
||||
@abstractmethod
|
||||
def _load_tokenizer(self) -> Tokenizer:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
|
||||
pass
|
||||
|
||||
@property
|
||||
def model_cfg_path(self) -> Path:
|
||||
return self.cache_dir / "config.json"
|
||||
|
||||
@property
|
||||
def tokenizer_file_path(self) -> Path:
|
||||
return self.model_dir / "tokenizer.json"
|
||||
|
||||
@property
|
||||
def tokenizer_cfg_path(self) -> Path:
|
||||
return self.model_dir / "tokenizer_config.json"
|
||||
|
||||
@cached_property
|
||||
def model_cfg(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading model config for CLIP model '{self.model_name}'")
|
||||
model_cfg: dict[str, Any] = json.load(self.model_cfg_path.open())
|
||||
log.debug(f"Loaded model config for CLIP model '{self.model_name}'")
|
||||
return model_cfg
|
||||
|
||||
@cached_property
|
||||
def tokenizer_file(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading tokenizer file for CLIP model '{self.model_name}'")
|
||||
tokenizer_file: dict[str, Any] = json.load(self.tokenizer_file_path.open())
|
||||
log.debug(f"Loaded tokenizer file for CLIP model '{self.model_name}'")
|
||||
return tokenizer_file
|
||||
|
||||
@cached_property
|
||||
def tokenizer_cfg(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading tokenizer config for CLIP model '{self.model_name}'")
|
||||
tokenizer_cfg: dict[str, Any] = json.load(self.tokenizer_cfg_path.open())
|
||||
log.debug(f"Loaded tokenizer config for CLIP model '{self.model_name}'")
|
||||
return tokenizer_cfg
|
||||
|
||||
|
||||
class OpenClipTextualEncoder(BaseCLIPTextualEncoder):
|
||||
def _load_tokenizer(self) -> Tokenizer:
|
||||
text_cfg: dict[str, Any] = self.model_cfg["text_cfg"]
|
||||
context_length: int = text_cfg.get("context_length", 77)
|
||||
pad_token: str = self.tokenizer_cfg["pad_token"]
|
||||
|
||||
tokenizer: Tokenizer = Tokenizer.from_file(self.tokenizer_file_path.as_posix())
|
||||
|
||||
pad_id: int = tokenizer.token_to_id(pad_token)
|
||||
tokenizer.enable_padding(length=context_length, pad_token=pad_token, pad_id=pad_id)
|
||||
tokenizer.enable_truncation(max_length=context_length)
|
||||
|
||||
return tokenizer
|
||||
|
||||
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
|
||||
tokens: Encoding = self.tokenizer.encode(text)
|
||||
return {"text": np.array([tokens.ids], dtype=np.int32)}
|
||||
|
||||
|
||||
class MClipTextualEncoder(OpenClipTextualEncoder):
|
||||
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
|
||||
tokens: Encoding = self.tokenizer.encode(text)
|
||||
return {
|
||||
"input_ids": np.array([tokens.ids], dtype=np.int32),
|
||||
"attention_mask": np.array([tokens.attention_mask], dtype=np.int32),
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import json
|
||||
from abc import abstractmethod
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from numpy.typing import NDArray
|
||||
from PIL import Image
|
||||
|
||||
from app.config import log
|
||||
from app.models.base import InferenceModel
|
||||
from app.models.transforms import crop_pil, decode_pil, get_pil_resampling, normalize, resize_pil, to_numpy
|
||||
from app.schemas import ModelSession, ModelTask, ModelType
|
||||
|
||||
|
||||
class BaseCLIPVisualEncoder(InferenceModel):
|
||||
depends = []
|
||||
identity = (ModelType.VISUAL, ModelTask.SEARCH)
|
||||
|
||||
def _predict(self, inputs: Image.Image | bytes, **kwargs: Any) -> NDArray[np.float32]:
|
||||
image = decode_pil(inputs)
|
||||
res: NDArray[np.float32] = self.session.run(None, self.transform(image))[0][0]
|
||||
return res
|
||||
|
||||
@abstractmethod
|
||||
def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]:
|
||||
pass
|
||||
|
||||
@property
|
||||
def model_cfg_path(self) -> Path:
|
||||
return self.cache_dir / "config.json"
|
||||
|
||||
@property
|
||||
def preprocess_cfg_path(self) -> Path:
|
||||
return self.model_dir / "preprocess_cfg.json"
|
||||
|
||||
@cached_property
|
||||
def model_cfg(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading model config for CLIP model '{self.model_name}'")
|
||||
model_cfg: dict[str, Any] = json.load(self.model_cfg_path.open())
|
||||
log.debug(f"Loaded model config for CLIP model '{self.model_name}'")
|
||||
return model_cfg
|
||||
|
||||
@cached_property
|
||||
def preprocess_cfg(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading visual preprocessing config for CLIP model '{self.model_name}'")
|
||||
preprocess_cfg: dict[str, Any] = json.load(self.preprocess_cfg_path.open())
|
||||
log.debug(f"Loaded visual preprocessing config for CLIP model '{self.model_name}'")
|
||||
return preprocess_cfg
|
||||
|
||||
|
||||
class OpenClipVisualEncoder(BaseCLIPVisualEncoder):
|
||||
def _load(self) -> ModelSession:
|
||||
size: list[int] | int = self.preprocess_cfg["size"]
|
||||
self.size = size[0] if isinstance(size, list) else size
|
||||
|
||||
self.resampling = get_pil_resampling(self.preprocess_cfg["interpolation"])
|
||||
self.mean = np.array(self.preprocess_cfg["mean"], dtype=np.float32)
|
||||
self.std = np.array(self.preprocess_cfg["std"], dtype=np.float32)
|
||||
|
||||
return super()._load()
|
||||
|
||||
def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]:
|
||||
image = resize_pil(image, self.size)
|
||||
image = crop_pil(image, self.size)
|
||||
image_np = to_numpy(image)
|
||||
image_np = normalize(image_np, self.mean, self.std)
|
||||
return {"image": np.expand_dims(image_np.transpose(2, 0, 1), 0)}
|
||||
@@ -1,5 +1,4 @@
|
||||
from app.config import clean_name
|
||||
from app.schemas import ModelSource
|
||||
|
||||
_OPENCLIP_MODELS = {
|
||||
"RN50__openai",
|
||||
@@ -55,16 +54,13 @@ _INSIGHTFACE_MODELS = {
|
||||
SUPPORTED_PROVIDERS = ["CUDAExecutionProvider", "OpenVINOExecutionProvider", "CPUExecutionProvider"]
|
||||
|
||||
|
||||
def get_model_source(model_name: str) -> ModelSource | None:
|
||||
cleaned_name = clean_name(model_name)
|
||||
def is_openclip(model_name: str) -> bool:
|
||||
return clean_name(model_name) in _OPENCLIP_MODELS
|
||||
|
||||
if cleaned_name in _INSIGHTFACE_MODELS:
|
||||
return ModelSource.INSIGHTFACE
|
||||
|
||||
if cleaned_name in _MCLIP_MODELS:
|
||||
return ModelSource.MCLIP
|
||||
def is_mclip(model_name: str) -> bool:
|
||||
return clean_name(model_name) in _MCLIP_MODELS
|
||||
|
||||
if cleaned_name in _OPENCLIP_MODELS:
|
||||
return ModelSource.OPENCLIP
|
||||
|
||||
return None
|
||||
def is_insightface(model_name: str) -> bool:
|
||||
return clean_name(model_name) in _INSIGHTFACE_MODELS
|
||||
|
||||
90
machine-learning/app/models/facial_recognition.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from insightface.model_zoo import ArcFaceONNX, RetinaFace
|
||||
from insightface.utils.face_align import norm_crop
|
||||
from numpy.typing import NDArray
|
||||
|
||||
from app.config import clean_name
|
||||
from app.schemas import Face, ModelType, is_ndarray
|
||||
|
||||
from .base import InferenceModel
|
||||
|
||||
|
||||
class FaceRecognizer(InferenceModel):
|
||||
_model_type = ModelType.FACIAL_RECOGNITION
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str,
|
||||
min_score: float = 0.7,
|
||||
cache_dir: Path | str | None = None,
|
||||
**model_kwargs: Any,
|
||||
) -> None:
|
||||
self.min_score = model_kwargs.pop("minScore", min_score)
|
||||
super().__init__(clean_name(model_name), cache_dir, **model_kwargs)
|
||||
|
||||
def _load(self) -> None:
|
||||
self.det_model = RetinaFace(session=self._make_session(self.det_file))
|
||||
self.rec_model = ArcFaceONNX(
|
||||
self.rec_file.with_suffix(".onnx").as_posix(),
|
||||
session=self._make_session(self.rec_file),
|
||||
)
|
||||
|
||||
self.det_model.prepare(
|
||||
ctx_id=0,
|
||||
det_thresh=self.min_score,
|
||||
input_size=(640, 640),
|
||||
)
|
||||
self.rec_model.prepare(ctx_id=0)
|
||||
|
||||
def _predict(self, image: NDArray[np.uint8] | bytes) -> list[Face]:
|
||||
if isinstance(image, bytes):
|
||||
decoded_image = cv2.imdecode(np.frombuffer(image, np.uint8), cv2.IMREAD_COLOR)
|
||||
else:
|
||||
decoded_image = image
|
||||
assert is_ndarray(decoded_image, np.uint8)
|
||||
bboxes, kpss = self.det_model.detect(decoded_image)
|
||||
if bboxes.size == 0:
|
||||
return []
|
||||
assert is_ndarray(kpss, np.float32)
|
||||
|
||||
scores = bboxes[:, 4].tolist()
|
||||
bboxes = bboxes[:, :4].round().tolist()
|
||||
|
||||
results = []
|
||||
height, width, _ = decoded_image.shape
|
||||
for (x1, y1, x2, y2), score, kps in zip(bboxes, scores, kpss):
|
||||
cropped_img = norm_crop(decoded_image, kps)
|
||||
embedding: NDArray[np.float32] = self.rec_model.get_feat(cropped_img)[0]
|
||||
face: Face = {
|
||||
"imageWidth": width,
|
||||
"imageHeight": height,
|
||||
"boundingBox": {
|
||||
"x1": x1,
|
||||
"y1": y1,
|
||||
"x2": x2,
|
||||
"y2": y2,
|
||||
},
|
||||
"score": score,
|
||||
"embedding": embedding,
|
||||
}
|
||||
results.append(face)
|
||||
return results
|
||||
|
||||
@property
|
||||
def cached(self) -> bool:
|
||||
return self.det_file.is_file() and self.rec_file.is_file()
|
||||
|
||||
@property
|
||||
def det_file(self) -> Path:
|
||||
return self.cache_dir / "detection" / f"model.{self.preferred_runtime}"
|
||||
|
||||
@property
|
||||
def rec_file(self) -> Path:
|
||||
return self.cache_dir / "recognition" / f"model.{self.preferred_runtime}"
|
||||
|
||||
def configure(self, **model_kwargs: Any) -> None:
|
||||
self.det_model.det_thresh = model_kwargs.pop("minScore", self.det_model.det_thresh)
|
||||
@@ -1,48 +0,0 @@
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from insightface.model_zoo import RetinaFace
|
||||
from numpy.typing import NDArray
|
||||
|
||||
from app.models.base import InferenceModel
|
||||
from app.models.transforms import decode_cv2
|
||||
from app.schemas import FaceDetectionOutput, ModelSession, ModelTask, ModelType
|
||||
|
||||
|
||||
class FaceDetector(InferenceModel):
|
||||
depends = []
|
||||
identity = (ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str,
|
||||
min_score: float = 0.7,
|
||||
cache_dir: Path | str | None = None,
|
||||
**model_kwargs: Any,
|
||||
) -> None:
|
||||
self.min_score = model_kwargs.pop("minScore", min_score)
|
||||
super().__init__(model_name, cache_dir, **model_kwargs)
|
||||
|
||||
def _load(self) -> ModelSession:
|
||||
session = self._make_session(self.model_path)
|
||||
self.model = RetinaFace(session=session)
|
||||
self.model.prepare(ctx_id=0, det_thresh=self.min_score, input_size=(640, 640))
|
||||
|
||||
return session
|
||||
|
||||
def _predict(self, inputs: NDArray[np.uint8] | bytes, **kwargs: Any) -> FaceDetectionOutput:
|
||||
inputs = decode_cv2(inputs)
|
||||
|
||||
bboxes, landmarks = self._detect(inputs)
|
||||
return {
|
||||
"boxes": bboxes[:, :4].round(),
|
||||
"scores": bboxes[:, 4],
|
||||
"landmarks": landmarks,
|
||||
}
|
||||
|
||||
def _detect(self, inputs: NDArray[np.uint8] | bytes) -> tuple[NDArray[np.float32], NDArray[np.float32]]:
|
||||
return self.model.detect(inputs) # type: ignore
|
||||
|
||||
def configure(self, **kwargs: Any) -> None:
|
||||
self.model.det_thresh = kwargs.pop("minScore", self.model.det_thresh)
|
||||
@@ -1,77 +0,0 @@
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import onnx
|
||||
import onnxruntime as ort
|
||||
from insightface.model_zoo import ArcFaceONNX
|
||||
from insightface.utils.face_align import norm_crop
|
||||
from numpy.typing import NDArray
|
||||
from onnx.tools.update_model_dims import update_inputs_outputs_dims
|
||||
from PIL import Image
|
||||
|
||||
from app.config import clean_name, log
|
||||
from app.models.base import InferenceModel
|
||||
from app.models.transforms import decode_cv2
|
||||
from app.schemas import FaceDetectionOutput, FacialRecognitionOutput, ModelSession, ModelTask, ModelType
|
||||
|
||||
|
||||
class FaceRecognizer(InferenceModel):
|
||||
depends = [(ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)]
|
||||
identity = (ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str,
|
||||
min_score: float = 0.7,
|
||||
cache_dir: Path | str | None = None,
|
||||
**model_kwargs: Any,
|
||||
) -> None:
|
||||
self.min_score = model_kwargs.pop("minScore", min_score)
|
||||
super().__init__(clean_name(model_name), cache_dir, **model_kwargs)
|
||||
|
||||
def _load(self) -> ModelSession:
|
||||
session = self._make_session(self.model_path)
|
||||
if not self._has_batch_dim(session):
|
||||
self._add_batch_dim(self.model_path)
|
||||
session = self._make_session(self.model_path)
|
||||
self.model = ArcFaceONNX(
|
||||
self.model_path.with_suffix(".onnx").as_posix(),
|
||||
session=session,
|
||||
)
|
||||
return session
|
||||
|
||||
def _predict(
|
||||
self, inputs: NDArray[np.uint8] | bytes | Image.Image, faces: FaceDetectionOutput, **kwargs: Any
|
||||
) -> FacialRecognitionOutput:
|
||||
if faces["boxes"].shape[0] == 0:
|
||||
return []
|
||||
inputs = decode_cv2(inputs)
|
||||
embeddings: NDArray[np.float32] = self.model.get_feat(self._crop(inputs, faces))
|
||||
return self.postprocess(faces, embeddings)
|
||||
|
||||
def postprocess(self, faces: FaceDetectionOutput, embeddings: NDArray[np.float32]) -> FacialRecognitionOutput:
|
||||
return [
|
||||
{
|
||||
"boundingBox": {"x1": x1, "y1": y1, "x2": x2, "y2": y2},
|
||||
"embedding": embedding,
|
||||
"score": score,
|
||||
}
|
||||
for (x1, y1, x2, y2), embedding, score in zip(faces["boxes"], embeddings, faces["scores"])
|
||||
]
|
||||
|
||||
def _crop(self, image: NDArray[np.uint8], faces: FaceDetectionOutput) -> list[NDArray[np.uint8]]:
|
||||
return [norm_crop(image, landmark) for landmark in faces["landmarks"]]
|
||||
|
||||
def _has_batch_dim(self, session: ort.InferenceSession) -> bool:
|
||||
return not isinstance(session, ort.InferenceSession) or session.get_inputs()[0].shape[0] == "batch"
|
||||
|
||||
def _add_batch_dim(self, model_path: Path) -> None:
|
||||
log.debug(f"Adding batch dimension to model {model_path}")
|
||||
proto = onnx.load(model_path)
|
||||
static_input_dims = [shape.dim_value for shape in proto.graph.input[0].type.tensor_type.shape.dim[1:]]
|
||||
static_output_dims = [shape.dim_value for shape in proto.graph.output[0].type.tensor_type.shape.dim[1:]]
|
||||
input_dims = {proto.graph.input[0].name: ["batch"] + static_input_dims}
|
||||
output_dims = {proto.graph.output[0].name: ["batch"] + static_output_dims}
|
||||
updated_proto = update_inputs_outputs_dims(proto, input_dims, output_dims)
|
||||
onnx.save(updated_proto, model_path)
|
||||
@@ -1,7 +1,3 @@
|
||||
from io import BytesIO
|
||||
from typing import IO
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from numpy.typing import NDArray
|
||||
from PIL import Image
|
||||
@@ -9,7 +5,7 @@ from PIL import Image
|
||||
_PIL_RESAMPLING_METHODS = {resampling.name.lower(): resampling for resampling in Image.Resampling}
|
||||
|
||||
|
||||
def resize_pil(img: Image.Image, size: int) -> Image.Image:
|
||||
def resize(img: Image.Image, size: int) -> Image.Image:
|
||||
if img.width < img.height:
|
||||
return img.resize((size, int((img.height / img.width) * size)), resample=Image.Resampling.BICUBIC)
|
||||
else:
|
||||
@@ -17,7 +13,7 @@ def resize_pil(img: Image.Image, size: int) -> Image.Image:
|
||||
|
||||
|
||||
# https://stackoverflow.com/a/60883103
|
||||
def crop_pil(img: Image.Image, size: int) -> Image.Image:
|
||||
def crop(img: Image.Image, size: int) -> Image.Image:
|
||||
left = int((img.size[0] / 2) - (size / 2))
|
||||
upper = int((img.size[1] / 2) - (size / 2))
|
||||
right = left + size
|
||||
@@ -27,36 +23,14 @@ def crop_pil(img: Image.Image, size: int) -> Image.Image:
|
||||
|
||||
|
||||
def to_numpy(img: Image.Image) -> NDArray[np.float32]:
|
||||
return np.asarray(img if img.mode == "RGB" else img.convert("RGB"), dtype=np.float32) / 255.0
|
||||
return np.asarray(img.convert("RGB")).astype(np.float32) / 255.0
|
||||
|
||||
|
||||
def normalize(
|
||||
img: NDArray[np.float32], mean: float | NDArray[np.float32], std: float | NDArray[np.float32]
|
||||
) -> NDArray[np.float32]:
|
||||
return np.divide(img - mean, std, dtype=np.float32)
|
||||
return (img - mean) / std
|
||||
|
||||
|
||||
def get_pil_resampling(resample: str) -> Image.Resampling:
|
||||
return _PIL_RESAMPLING_METHODS[resample.lower()]
|
||||
|
||||
|
||||
def pil_to_cv2(image: Image.Image) -> NDArray[np.uint8]:
|
||||
return cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) # type: ignore
|
||||
|
||||
|
||||
def decode_pil(image_bytes: bytes | IO[bytes] | Image.Image) -> Image.Image:
|
||||
if isinstance(image_bytes, Image.Image):
|
||||
return image_bytes
|
||||
image = Image.open(BytesIO(image_bytes) if isinstance(image_bytes, bytes) else image_bytes)
|
||||
image.load() # type: ignore
|
||||
if not image.mode == "RGB":
|
||||
image = image.convert("RGB")
|
||||
return image
|
||||
|
||||
|
||||
def decode_cv2(image_bytes: NDArray[np.uint8] | bytes | Image.Image) -> NDArray[np.uint8]:
|
||||
if isinstance(image_bytes, bytes):
|
||||
image_bytes = decode_pil(image_bytes) # pillow is much faster than cv2
|
||||
if isinstance(image_bytes, Image.Image):
|
||||
return pil_to_cv2(image_bytes)
|
||||
return image_bytes
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from enum import Enum
|
||||
from typing import Any, Literal, Protocol, TypedDict, TypeGuard, TypeVar
|
||||
from typing import Any, Protocol, TypedDict, TypeGuard
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
@@ -28,87 +28,31 @@ class BoundingBox(TypedDict):
|
||||
y2: int
|
||||
|
||||
|
||||
class ModelTask(StrEnum):
|
||||
FACIAL_RECOGNITION = "facial-recognition"
|
||||
SEARCH = "clip"
|
||||
|
||||
|
||||
class ModelType(StrEnum):
|
||||
DETECTION = "detection"
|
||||
RECOGNITION = "recognition"
|
||||
TEXTUAL = "textual"
|
||||
VISUAL = "visual"
|
||||
CLIP = "clip"
|
||||
FACIAL_RECOGNITION = "facial-recognition"
|
||||
|
||||
|
||||
class ModelFormat(StrEnum):
|
||||
ARMNN = "armnn"
|
||||
class ModelRuntime(StrEnum):
|
||||
ONNX = "onnx"
|
||||
|
||||
|
||||
class ModelSource(StrEnum):
|
||||
INSIGHTFACE = "insightface"
|
||||
MCLIP = "mclip"
|
||||
OPENCLIP = "openclip"
|
||||
|
||||
|
||||
ModelIdentity = tuple[ModelType, ModelTask]
|
||||
|
||||
|
||||
class ModelSession(Protocol):
|
||||
def run(
|
||||
self,
|
||||
output_names: list[str] | None,
|
||||
input_feed: dict[str, npt.NDArray[np.float32]] | dict[str, npt.NDArray[np.int32]],
|
||||
run_options: Any = None,
|
||||
) -> list[npt.NDArray[np.float32]]: ...
|
||||
ARMNN = "armnn"
|
||||
|
||||
|
||||
class HasProfiling(Protocol):
|
||||
profiling: dict[str, float]
|
||||
|
||||
|
||||
class FaceDetectionOutput(TypedDict):
|
||||
boxes: npt.NDArray[np.float32]
|
||||
scores: npt.NDArray[np.float32]
|
||||
landmarks: npt.NDArray[np.float32]
|
||||
|
||||
|
||||
class DetectedFace(TypedDict):
|
||||
class Face(TypedDict):
|
||||
boundingBox: BoundingBox
|
||||
embedding: npt.NDArray[np.float32]
|
||||
imageWidth: int
|
||||
imageHeight: int
|
||||
score: float
|
||||
|
||||
|
||||
FacialRecognitionOutput = list[DetectedFace]
|
||||
|
||||
|
||||
class PipelineEntry(TypedDict):
|
||||
modelName: str
|
||||
options: dict[str, Any]
|
||||
|
||||
|
||||
PipelineRequest = dict[ModelTask, dict[ModelType, PipelineEntry]]
|
||||
|
||||
|
||||
class InferenceEntry(TypedDict):
|
||||
name: str
|
||||
task: ModelTask
|
||||
type: ModelType
|
||||
options: dict[str, Any]
|
||||
|
||||
|
||||
InferenceEntries = tuple[list[InferenceEntry], list[InferenceEntry]]
|
||||
|
||||
|
||||
InferenceResponse = dict[ModelTask | Literal["imageHeight"] | Literal["imageWidth"], Any]
|
||||
|
||||
|
||||
def has_profiling(obj: Any) -> TypeGuard[HasProfiling]:
|
||||
return hasattr(obj, "profiling") and isinstance(obj.profiling, dict)
|
||||
|
||||
|
||||
def is_ndarray(obj: Any, dtype: "type[np._DTypeScalar_co]") -> "TypeGuard[npt.NDArray[np._DTypeScalar_co]]":
|
||||
return isinstance(obj, np.ndarray) and obj.dtype == dtype
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -17,15 +17,13 @@ from pytest import MonkeyPatch
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from app.main import load, preload_models
|
||||
from app.models.clip.textual import MClipTextualEncoder, OpenClipTextualEncoder
|
||||
from app.models.clip.visual import OpenClipVisualEncoder
|
||||
from app.models.facial_recognition.detection import FaceDetector
|
||||
from app.models.facial_recognition.recognition import FaceRecognizer
|
||||
|
||||
from .config import Settings, log, settings
|
||||
from .models.base import InferenceModel
|
||||
from .models.cache import ModelCache
|
||||
from .schemas import ModelFormat, ModelTask, ModelType
|
||||
from .models.clip import MCLIPEncoder, OpenCLIPEncoder
|
||||
from .models.facial_recognition import FaceRecognizer
|
||||
from .schemas import ModelRuntime, ModelType
|
||||
|
||||
|
||||
class TestBase:
|
||||
@@ -37,13 +35,13 @@ class TestBase:
|
||||
|
||||
@pytest.mark.providers(CPU_EP)
|
||||
def test_sets_cpu_provider(self, providers: list[str]) -> None:
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.providers == self.CPU_EP
|
||||
|
||||
@pytest.mark.providers(CUDA_EP)
|
||||
def test_sets_cuda_provider_if_available(self, providers: list[str]) -> None:
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.providers == self.CUDA_EP
|
||||
|
||||
@@ -52,7 +50,7 @@ class TestBase:
|
||||
mocked = mocker.patch("app.models.base.ort.capi._pybind_state")
|
||||
mocked.get_available_openvino_device_ids.return_value = ["GPU.0", "CPU"]
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.providers == self.OV_EP
|
||||
|
||||
@@ -61,25 +59,25 @@ class TestBase:
|
||||
mocked = mocker.patch("app.models.base.ort.capi._pybind_state")
|
||||
mocked.get_available_openvino_device_ids.return_value = ["CPU"]
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.providers == self.CPU_EP
|
||||
|
||||
@pytest.mark.providers(CUDA_EP_OUT_OF_ORDER)
|
||||
def test_sets_providers_in_correct_order(self, providers: list[str]) -> None:
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.providers == self.CUDA_EP
|
||||
|
||||
@pytest.mark.providers(TRT_EP)
|
||||
def test_ignores_unsupported_providers(self, providers: list[str]) -> None:
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.providers == self.CUDA_EP
|
||||
|
||||
def test_sets_provider_kwarg(self) -> None:
|
||||
providers = ["CUDAExecutionProvider"]
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", providers=providers)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", providers=providers)
|
||||
|
||||
assert encoder.providers == providers
|
||||
|
||||
@@ -87,9 +85,7 @@ class TestBase:
|
||||
mocked = mocker.patch("app.models.base.ort.capi._pybind_state")
|
||||
mocked.get_available_openvino_device_ids.return_value = ["GPU.0", "CPU"]
|
||||
|
||||
encoder = OpenClipTextualEncoder(
|
||||
"ViT-B-32__openai", providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"]
|
||||
)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"])
|
||||
|
||||
assert encoder.provider_options == [
|
||||
{"device_type": "GPU_FP32", "cache_dir": (encoder.cache_dir / "openvino").as_posix()},
|
||||
@@ -97,7 +93,7 @@ class TestBase:
|
||||
]
|
||||
|
||||
def test_sets_provider_options_kwarg(self) -> None:
|
||||
encoder = OpenClipTextualEncoder(
|
||||
encoder = OpenCLIPEncoder(
|
||||
"ViT-B-32__openai",
|
||||
providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"],
|
||||
provider_options=[],
|
||||
@@ -106,7 +102,7 @@ class TestBase:
|
||||
assert encoder.provider_options == []
|
||||
|
||||
def test_sets_default_sess_options(self) -> None:
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.sess_options.execution_mode == ort.ExecutionMode.ORT_SEQUENTIAL
|
||||
assert encoder.sess_options.inter_op_num_threads == 1
|
||||
@@ -114,9 +110,7 @@ class TestBase:
|
||||
assert encoder.sess_options.enable_cpu_mem_arena is False
|
||||
|
||||
def test_sets_default_sess_options_does_not_set_threads_if_non_cpu_and_default_threads(self) -> None:
|
||||
encoder = OpenClipTextualEncoder(
|
||||
"ViT-B-32__openai", providers=["CUDAExecutionProvider", "CPUExecutionProvider"]
|
||||
)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", providers=["CUDAExecutionProvider", "CPUExecutionProvider"])
|
||||
|
||||
assert encoder.sess_options.inter_op_num_threads == 0
|
||||
assert encoder.sess_options.intra_op_num_threads == 0
|
||||
@@ -126,16 +120,14 @@ class TestBase:
|
||||
mock_settings.model_inter_op_threads = 2
|
||||
mock_settings.model_intra_op_threads = 4
|
||||
|
||||
encoder = OpenClipTextualEncoder(
|
||||
"ViT-B-32__openai", providers=["CUDAExecutionProvider", "CPUExecutionProvider"]
|
||||
)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", providers=["CUDAExecutionProvider", "CPUExecutionProvider"])
|
||||
|
||||
assert encoder.sess_options.inter_op_num_threads == 2
|
||||
assert encoder.sess_options.intra_op_num_threads == 4
|
||||
|
||||
def test_sets_sess_options_kwarg(self) -> None:
|
||||
sess_options = ort.SessionOptions()
|
||||
encoder = OpenClipTextualEncoder(
|
||||
encoder = OpenCLIPEncoder(
|
||||
"ViT-B-32__openai",
|
||||
providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"],
|
||||
provider_options=[],
|
||||
@@ -145,43 +137,43 @@ class TestBase:
|
||||
assert sess_options is encoder.sess_options
|
||||
|
||||
def test_sets_default_cache_dir(self) -> None:
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.cache_dir == Path(settings.cache_folder) / "clip" / "ViT-B-32__openai"
|
||||
|
||||
def test_sets_cache_dir_kwarg(self) -> None:
|
||||
cache_dir = Path("/test_cache")
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=cache_dir)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir=cache_dir)
|
||||
|
||||
assert encoder.cache_dir == cache_dir
|
||||
|
||||
def test_sets_default_preferred_format(self, mocker: MockerFixture) -> None:
|
||||
def test_sets_default_preferred_runtime(self, mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(settings, "ann", True)
|
||||
mocker.patch("ann.ann.is_available", False)
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.preferred_format == ModelFormat.ONNX
|
||||
assert encoder.preferred_runtime == ModelRuntime.ONNX
|
||||
|
||||
def test_sets_default_preferred_format_to_armnn_if_available(self, mocker: MockerFixture) -> None:
|
||||
def test_sets_default_preferred_runtime_to_armnn_if_available(self, mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(settings, "ann", True)
|
||||
mocker.patch("ann.ann.is_available", True)
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.preferred_format == ModelFormat.ARMNN
|
||||
assert encoder.preferred_runtime == ModelRuntime.ARMNN
|
||||
|
||||
def test_sets_preferred_format_kwarg(self, mocker: MockerFixture) -> None:
|
||||
def test_sets_preferred_runtime_kwarg(self, mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(settings, "ann", False)
|
||||
mocker.patch("ann.ann.is_available", False)
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", preferred_format=ModelFormat.ARMNN)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", preferred_runtime=ModelRuntime.ARMNN)
|
||||
|
||||
assert encoder.preferred_format == ModelFormat.ARMNN
|
||||
assert encoder.preferred_runtime == ModelRuntime.ARMNN
|
||||
|
||||
def test_casts_cache_dir_string_to_path(self) -> None:
|
||||
cache_dir = "/test_cache"
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=cache_dir)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir=cache_dir)
|
||||
|
||||
assert encoder.cache_dir == Path(cache_dir)
|
||||
|
||||
@@ -194,7 +186,7 @@ class TestBase:
|
||||
mocker.patch("app.models.base.Path", return_value=mock_cache_dir)
|
||||
info = mocker.spy(log, "info")
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=mock_cache_dir)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir=mock_cache_dir)
|
||||
encoder.clear_cache()
|
||||
|
||||
mock_rmtree.assert_called_once_with(encoder.cache_dir)
|
||||
@@ -209,7 +201,7 @@ class TestBase:
|
||||
mocker.patch("app.models.base.Path", return_value=mock_cache_dir)
|
||||
warning = mocker.spy(log, "warning")
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=mock_cache_dir)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir=mock_cache_dir)
|
||||
encoder.clear_cache()
|
||||
|
||||
mock_rmtree.assert_not_called()
|
||||
@@ -223,7 +215,7 @@ class TestBase:
|
||||
mock_cache_dir.is_dir.return_value = True
|
||||
mocker.patch("app.models.base.Path", return_value=mock_cache_dir)
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=mock_cache_dir)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir=mock_cache_dir)
|
||||
with pytest.raises(RuntimeError):
|
||||
encoder.clear_cache()
|
||||
|
||||
@@ -238,7 +230,7 @@ class TestBase:
|
||||
mocker.patch("app.models.base.Path", return_value=mock_cache_dir)
|
||||
warning = mocker.spy(log, "warning")
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=mock_cache_dir)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir=mock_cache_dir)
|
||||
encoder.clear_cache()
|
||||
|
||||
mock_rmtree.assert_not_called()
|
||||
@@ -253,7 +245,7 @@ class TestBase:
|
||||
mock_model_path.with_suffix.return_value = mock_model_path
|
||||
mock_ann = mocker.patch("app.models.base.AnnSession")
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
encoder._make_session(mock_model_path)
|
||||
|
||||
mock_ann.assert_called_once()
|
||||
@@ -271,7 +263,7 @@ class TestBase:
|
||||
mock_ann = mocker.patch("app.models.base.AnnSession")
|
||||
mock_ort = mocker.patch("app.models.base.ort.InferenceSession")
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
encoder._make_session(mock_armnn_path)
|
||||
|
||||
mock_ort.assert_called_once()
|
||||
@@ -285,7 +277,7 @@ class TestBase:
|
||||
mock_ann = mocker.patch("app.models.base.AnnSession")
|
||||
mock_ort = mocker.patch("app.models.base.ort.InferenceSession")
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
with pytest.raises(ValueError):
|
||||
encoder._make_session(mock_model_path)
|
||||
|
||||
@@ -295,7 +287,7 @@ class TestBase:
|
||||
def test_download(self, mocker: MockerFixture) -> None:
|
||||
mock_snapshot_download = mocker.patch("app.models.base.snapshot_download")
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="/path/to/cache")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir="/path/to/cache")
|
||||
encoder.download()
|
||||
|
||||
mock_snapshot_download.assert_called_once_with(
|
||||
@@ -306,10 +298,10 @@ class TestBase:
|
||||
ignore_patterns=["*.armnn"],
|
||||
)
|
||||
|
||||
def test_download_downloads_armnn_if_preferred_format(self, mocker: MockerFixture) -> None:
|
||||
def test_download_downloads_armnn_if_preferred_runtime(self, mocker: MockerFixture) -> None:
|
||||
mock_snapshot_download = mocker.patch("app.models.base.snapshot_download")
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", preferred_format=ModelFormat.ARMNN)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", preferred_runtime=ModelRuntime.ARMNN)
|
||||
encoder.download()
|
||||
|
||||
mock_snapshot_download.assert_called_once_with(
|
||||
@@ -331,17 +323,21 @@ class TestCLIP:
|
||||
mocker: MockerFixture,
|
||||
clip_model_cfg: dict[str, Any],
|
||||
clip_preprocess_cfg: Callable[[Path], dict[str, Any]],
|
||||
clip_tokenizer_cfg: Callable[[Path], dict[str, Any]],
|
||||
) -> None:
|
||||
mocker.patch.object(OpenClipVisualEncoder, "download")
|
||||
mocker.patch.object(OpenClipVisualEncoder, "model_cfg", clip_model_cfg)
|
||||
mocker.patch.object(OpenClipVisualEncoder, "preprocess_cfg", clip_preprocess_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "download")
|
||||
mocker.patch.object(OpenCLIPEncoder, "model_cfg", clip_model_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "preprocess_cfg", clip_preprocess_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "tokenizer_cfg", clip_tokenizer_cfg)
|
||||
|
||||
mocked = mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value
|
||||
mocked.run.return_value = [[self.embedding]]
|
||||
mocker.patch("app.models.clip.Tokenizer.from_file", autospec=True)
|
||||
|
||||
clip_encoder = OpenClipVisualEncoder("ViT-B-32__openai", cache_dir="test_cache")
|
||||
clip_encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir="test_cache", mode="vision")
|
||||
embedding = clip_encoder.predict(pil_image)
|
||||
|
||||
assert clip_encoder.mode == "vision"
|
||||
assert isinstance(embedding, np.ndarray)
|
||||
assert embedding.shape[0] == clip_model_cfg["embed_dim"]
|
||||
assert embedding.dtype == np.float32
|
||||
@@ -351,19 +347,22 @@ class TestCLIP:
|
||||
self,
|
||||
mocker: MockerFixture,
|
||||
clip_model_cfg: dict[str, Any],
|
||||
clip_preprocess_cfg: Callable[[Path], dict[str, Any]],
|
||||
clip_tokenizer_cfg: Callable[[Path], dict[str, Any]],
|
||||
) -> None:
|
||||
mocker.patch.object(OpenClipTextualEncoder, "download")
|
||||
mocker.patch.object(OpenClipTextualEncoder, "model_cfg", clip_model_cfg)
|
||||
mocker.patch.object(OpenClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "download")
|
||||
mocker.patch.object(OpenCLIPEncoder, "model_cfg", clip_model_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "preprocess_cfg", clip_preprocess_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "tokenizer_cfg", clip_tokenizer_cfg)
|
||||
|
||||
mocked = mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value
|
||||
mocked.run.return_value = [[self.embedding]]
|
||||
mocker.patch("app.models.clip.textual.Tokenizer.from_file", autospec=True)
|
||||
mocker.patch("app.models.clip.Tokenizer.from_file", autospec=True)
|
||||
|
||||
clip_encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache")
|
||||
clip_encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir="test_cache", mode="text")
|
||||
embedding = clip_encoder.predict("test search query")
|
||||
|
||||
assert clip_encoder.mode == "text"
|
||||
assert isinstance(embedding, np.ndarray)
|
||||
assert embedding.shape[0] == clip_model_cfg["embed_dim"]
|
||||
assert embedding.dtype == np.float32
|
||||
@@ -373,18 +372,19 @@ class TestCLIP:
|
||||
self,
|
||||
mocker: MockerFixture,
|
||||
clip_model_cfg: dict[str, Any],
|
||||
clip_preprocess_cfg: Callable[[Path], dict[str, Any]],
|
||||
clip_tokenizer_cfg: Callable[[Path], dict[str, Any]],
|
||||
) -> None:
|
||||
mocker.patch.object(OpenClipTextualEncoder, "download")
|
||||
mocker.patch.object(OpenClipTextualEncoder, "model_cfg", clip_model_cfg)
|
||||
mocker.patch.object(OpenClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg)
|
||||
mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value
|
||||
mock_tokenizer = mocker.patch("app.models.clip.textual.Tokenizer.from_file", autospec=True).return_value
|
||||
mocker.patch.object(OpenCLIPEncoder, "download")
|
||||
mocker.patch.object(OpenCLIPEncoder, "model_cfg", clip_model_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "preprocess_cfg", clip_preprocess_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "tokenizer_cfg", clip_tokenizer_cfg)
|
||||
mock_tokenizer = mocker.patch("app.models.clip.Tokenizer.from_file", autospec=True).return_value
|
||||
mock_ids = [randint(0, 50000) for _ in range(77)]
|
||||
mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids)
|
||||
|
||||
clip_encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache")
|
||||
clip_encoder._load()
|
||||
clip_encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir="test_cache", mode="text")
|
||||
clip_encoder._load_tokenizer()
|
||||
tokens = clip_encoder.tokenize("test search query")
|
||||
|
||||
assert "text" in tokens
|
||||
@@ -397,19 +397,20 @@ class TestCLIP:
|
||||
self,
|
||||
mocker: MockerFixture,
|
||||
clip_model_cfg: dict[str, Any],
|
||||
clip_preprocess_cfg: Callable[[Path], dict[str, Any]],
|
||||
clip_tokenizer_cfg: Callable[[Path], dict[str, Any]],
|
||||
) -> None:
|
||||
mocker.patch.object(MClipTextualEncoder, "download")
|
||||
mocker.patch.object(MClipTextualEncoder, "model_cfg", clip_model_cfg)
|
||||
mocker.patch.object(MClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg)
|
||||
mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value
|
||||
mock_tokenizer = mocker.patch("app.models.clip.textual.Tokenizer.from_file", autospec=True).return_value
|
||||
mocker.patch.object(OpenCLIPEncoder, "download")
|
||||
mocker.patch.object(OpenCLIPEncoder, "model_cfg", clip_model_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "preprocess_cfg", clip_preprocess_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "tokenizer_cfg", clip_tokenizer_cfg)
|
||||
mock_tokenizer = mocker.patch("app.models.clip.Tokenizer.from_file", autospec=True).return_value
|
||||
mock_ids = [randint(0, 50000) for _ in range(77)]
|
||||
mock_attention_mask = [randint(0, 1) for _ in range(77)]
|
||||
mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids, attention_mask=mock_attention_mask)
|
||||
|
||||
clip_encoder = MClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache")
|
||||
clip_encoder._load()
|
||||
clip_encoder = MCLIPEncoder("ViT-B-32__openai", cache_dir="test_cache", mode="text")
|
||||
clip_encoder._load_tokenizer()
|
||||
tokens = clip_encoder.tokenize("test search query")
|
||||
|
||||
assert "input_ids" in tokens
|
||||
@@ -429,90 +430,59 @@ class TestFaceRecognition:
|
||||
|
||||
assert face_recognizer.min_score == 0.5
|
||||
|
||||
def test_detection(self, cv_image: cv2.Mat, mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(FaceDetector, "load")
|
||||
face_detector = FaceDetector("buffalo_s", min_score=0.0, cache_dir="test_cache")
|
||||
def test_basic(self, cv_image: cv2.Mat, mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(FaceRecognizer, "load")
|
||||
face_recognizer = FaceRecognizer("buffalo_s", min_score=0.0, cache_dir="test_cache")
|
||||
|
||||
det_model = mock.Mock()
|
||||
num_faces = 2
|
||||
bbox = np.random.rand(num_faces, 4).astype(np.float32)
|
||||
scores = np.array([[0.67]] * num_faces).astype(np.float32)
|
||||
score = np.array([[0.67]] * num_faces).astype(np.float32)
|
||||
kpss = np.random.rand(num_faces, 5, 2).astype(np.float32)
|
||||
det_model.detect.return_value = (np.concatenate([bbox, scores], axis=-1), kpss)
|
||||
face_detector.model = det_model
|
||||
|
||||
faces = face_detector.predict(cv_image)
|
||||
|
||||
assert isinstance(faces, dict)
|
||||
assert isinstance(faces.get("boxes", None), np.ndarray)
|
||||
assert isinstance(faces.get("landmarks", None), np.ndarray)
|
||||
assert isinstance(faces.get("scores", None), np.ndarray)
|
||||
assert np.equal(faces["boxes"], bbox.round()).all()
|
||||
assert np.equal(faces["landmarks"], kpss).all()
|
||||
assert np.equal(faces["scores"], scores).all()
|
||||
det_model.detect.assert_called_once()
|
||||
|
||||
def test_recognition(self, cv_image: cv2.Mat, mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(FaceRecognizer, "load")
|
||||
face_recognizer = FaceRecognizer("buffalo_s", min_score=0.0, cache_dir="test_cache")
|
||||
|
||||
num_faces = 2
|
||||
bbox = np.random.rand(num_faces, 4).astype(np.float32)
|
||||
scores = np.array([0.67] * num_faces).astype(np.float32)
|
||||
kpss = np.random.rand(num_faces, 5, 2).astype(np.float32)
|
||||
faces = {"boxes": bbox, "landmarks": kpss, "scores": scores}
|
||||
det_model.detect.return_value = (np.concatenate([bbox, score], axis=-1), kpss)
|
||||
face_recognizer.det_model = det_model
|
||||
|
||||
rec_model = mock.Mock()
|
||||
embedding = np.random.rand(num_faces, 512).astype(np.float32)
|
||||
rec_model.get_feat.return_value = embedding
|
||||
face_recognizer.model = rec_model
|
||||
face_recognizer.rec_model = rec_model
|
||||
|
||||
faces = face_recognizer.predict(cv_image, faces)
|
||||
faces = face_recognizer.predict(cv_image)
|
||||
|
||||
assert isinstance(faces, list)
|
||||
assert len(faces) == num_faces
|
||||
for face in faces:
|
||||
assert isinstance(face.get("boundingBox"), dict)
|
||||
assert set(face["boundingBox"]) == {"x1", "y1", "x2", "y2"}
|
||||
assert all(isinstance(val, np.float32) for val in face["boundingBox"].values())
|
||||
assert isinstance(face.get("embedding"), np.ndarray)
|
||||
assert face["imageHeight"] == 800
|
||||
assert face["imageWidth"] == 600
|
||||
assert isinstance(face["embedding"], np.ndarray)
|
||||
assert face["embedding"].shape[0] == 512
|
||||
assert isinstance(face.get("score", None), np.float32)
|
||||
assert face["embedding"].dtype == np.float32
|
||||
|
||||
rec_model.get_feat.assert_called_once()
|
||||
call_args = rec_model.get_feat.call_args_list[0].args
|
||||
assert len(call_args) == 1
|
||||
assert isinstance(call_args[0], list)
|
||||
assert isinstance(call_args[0][0], np.ndarray)
|
||||
assert call_args[0][0].shape == (112, 112, 3)
|
||||
det_model.detect.assert_called_once()
|
||||
assert rec_model.get_feat.call_count == num_faces
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestCache:
|
||||
async def test_caches(self, mock_get_model: mock.Mock) -> None:
|
||||
model_cache = ModelCache()
|
||||
await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION)
|
||||
await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION)
|
||||
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION)
|
||||
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION)
|
||||
assert len(model_cache.cache._cache) == 1
|
||||
mock_get_model.assert_called_once()
|
||||
|
||||
async def test_kwargs_used(self, mock_get_model: mock.Mock) -> None:
|
||||
model_cache = ModelCache()
|
||||
await model_cache.get(
|
||||
"test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, cache_dir="test_cache"
|
||||
)
|
||||
mock_get_model.assert_called_once_with(
|
||||
"test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, cache_dir="test_cache"
|
||||
)
|
||||
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION, cache_dir="test_cache")
|
||||
mock_get_model.assert_called_once_with(ModelType.FACIAL_RECOGNITION, "test_model_name", cache_dir="test_cache")
|
||||
|
||||
async def test_different_clip(self, mock_get_model: mock.Mock) -> None:
|
||||
model_cache = ModelCache()
|
||||
await model_cache.get("test_model_name", ModelType.VISUAL, ModelTask.SEARCH)
|
||||
await model_cache.get("test_model_name", ModelType.TEXTUAL, ModelTask.SEARCH)
|
||||
await model_cache.get("test_image_model_name", ModelType.CLIP)
|
||||
await model_cache.get("test_text_model_name", ModelType.CLIP)
|
||||
mock_get_model.assert_has_calls(
|
||||
[
|
||||
mock.call("test_model_name", ModelType.VISUAL, ModelTask.SEARCH),
|
||||
mock.call("test_model_name", ModelType.TEXTUAL, ModelTask.SEARCH),
|
||||
mock.call(ModelType.CLIP, "test_image_model_name"),
|
||||
mock.call(ModelType.CLIP, "test_text_model_name"),
|
||||
]
|
||||
)
|
||||
assert len(model_cache.cache._cache) == 2
|
||||
@@ -520,19 +490,19 @@ class TestCache:
|
||||
@mock.patch("app.models.cache.OptimisticLock", autospec=True)
|
||||
async def test_model_ttl(self, mock_lock_cls: mock.Mock, mock_get_model: mock.Mock) -> None:
|
||||
model_cache = ModelCache()
|
||||
await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, ttl=100)
|
||||
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION, ttl=100)
|
||||
mock_lock_cls.return_value.__aenter__.return_value.cas.assert_called_with(mock.ANY, ttl=100)
|
||||
|
||||
@mock.patch("app.models.cache.SimpleMemoryCache.expire")
|
||||
async def test_revalidate_get(self, mock_cache_expire: mock.Mock, mock_get_model: mock.Mock) -> None:
|
||||
model_cache = ModelCache(revalidate=True)
|
||||
await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, ttl=100)
|
||||
await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, ttl=100)
|
||||
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION, ttl=100)
|
||||
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION, ttl=100)
|
||||
mock_cache_expire.assert_called_once_with(mock.ANY, 100)
|
||||
|
||||
async def test_profiling(self, mock_get_model: mock.Mock) -> None:
|
||||
model_cache = ModelCache(profiling=True)
|
||||
await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, ttl=100)
|
||||
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION, ttl=100)
|
||||
profiling = await model_cache.get_profiling()
|
||||
assert isinstance(profiling, dict)
|
||||
assert profiling == model_cache.cache.profiling
|
||||
@@ -540,9 +510,9 @@ class TestCache:
|
||||
async def test_loads_mclip(self) -> None:
|
||||
model_cache = ModelCache()
|
||||
|
||||
model = await model_cache.get("XLM-Roberta-Large-Vit-B-32", ModelType.TEXTUAL, ModelTask.SEARCH)
|
||||
model = await model_cache.get("XLM-Roberta-Large-Vit-B-32", ModelType.CLIP, mode="text")
|
||||
|
||||
assert isinstance(model, MClipTextualEncoder)
|
||||
assert isinstance(model, MCLIPEncoder)
|
||||
assert model.model_name == "XLM-Roberta-Large-Vit-B-32"
|
||||
|
||||
async def test_raises_exception_if_invalid_model_type(self) -> None:
|
||||
@@ -550,55 +520,15 @@ class TestCache:
|
||||
model_cache = ModelCache()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await model_cache.get("XLM-Roberta-Large-Vit-B-32", ModelType.TEXTUAL, invalid)
|
||||
await model_cache.get("XLM-Roberta-Large-Vit-B-32", invalid, mode="text")
|
||||
|
||||
async def test_raises_exception_if_unknown_model_name(self) -> None:
|
||||
model_cache = ModelCache()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await model_cache.get("test_model_name", ModelType.TEXTUAL, ModelTask.SEARCH)
|
||||
await model_cache.get("test_model_name", ModelType.CLIP, mode="text")
|
||||
|
||||
async def test_preloads_clip_models(self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock) -> None:
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__CLIP"] = "ViT-B-32__openai"
|
||||
|
||||
settings = Settings()
|
||||
assert settings.preload is not None
|
||||
assert settings.preload.clip == "ViT-B-32__openai"
|
||||
|
||||
model_cache = ModelCache()
|
||||
monkeypatch.setattr("app.main.model_cache", model_cache)
|
||||
|
||||
await preload_models(settings.preload)
|
||||
mock_get_model.assert_has_calls(
|
||||
[
|
||||
mock.call("ViT-B-32__openai", ModelType.TEXTUAL, ModelTask.SEARCH),
|
||||
mock.call("ViT-B-32__openai", ModelType.VISUAL, ModelTask.SEARCH),
|
||||
],
|
||||
any_order=True,
|
||||
)
|
||||
|
||||
async def test_preloads_facial_recognition_models(
|
||||
self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock
|
||||
) -> None:
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION"] = "buffalo_s"
|
||||
|
||||
settings = Settings()
|
||||
assert settings.preload is not None
|
||||
assert settings.preload.facial_recognition == "buffalo_s"
|
||||
|
||||
model_cache = ModelCache()
|
||||
monkeypatch.setattr("app.main.model_cache", model_cache)
|
||||
|
||||
await preload_models(settings.preload)
|
||||
mock_get_model.assert_has_calls(
|
||||
[
|
||||
mock.call("buffalo_s", ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION),
|
||||
mock.call("buffalo_s", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION),
|
||||
],
|
||||
any_order=True,
|
||||
)
|
||||
|
||||
async def test_preloads_all_models(self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock) -> None:
|
||||
async def test_preloads_models(self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock) -> None:
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__CLIP"] = "ViT-B-32__openai"
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION"] = "buffalo_s"
|
||||
|
||||
@@ -611,15 +541,11 @@ class TestCache:
|
||||
monkeypatch.setattr("app.main.model_cache", model_cache)
|
||||
|
||||
await preload_models(settings.preload)
|
||||
mock_get_model.assert_has_calls(
|
||||
[
|
||||
mock.call("ViT-B-32__openai", ModelType.TEXTUAL, ModelTask.SEARCH),
|
||||
mock.call("ViT-B-32__openai", ModelType.VISUAL, ModelTask.SEARCH),
|
||||
mock.call("buffalo_s", ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION),
|
||||
mock.call("buffalo_s", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION),
|
||||
],
|
||||
any_order=True,
|
||||
)
|
||||
assert len(model_cache.cache._cache) == 2
|
||||
assert mock_get_model.call_count == 2
|
||||
await model_cache.get("ViT-B-32__openai", ModelType.CLIP, ttl=100)
|
||||
await model_cache.get("buffalo_s", ModelType.FACIAL_RECOGNITION, ttl=100)
|
||||
assert mock_get_model.call_count == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -646,8 +572,7 @@ class TestLoad:
|
||||
async def test_load_clears_cache_and_retries_if_os_error(self) -> None:
|
||||
mock_model = mock.Mock(spec=InferenceModel)
|
||||
mock_model.model_name = "test_model_name"
|
||||
mock_model.model_type = ModelType.VISUAL
|
||||
mock_model.model_task = ModelTask.SEARCH
|
||||
mock_model.model_type = ModelType.CLIP
|
||||
mock_model.load.side_effect = [OSError, None]
|
||||
mock_model.loaded = False
|
||||
|
||||
@@ -672,15 +597,13 @@ class TestEndpoints:
|
||||
|
||||
response = deployed_app.post(
|
||||
"http://localhost:3003/predict",
|
||||
data={"entries": json.dumps({"clip": {"visual": {"modelName": "ViT-B-32__openai"}}})},
|
||||
data={"modelName": "ViT-B-32__openai", "modelType": "clip", "options": json.dumps({"mode": "vision"})},
|
||||
files={"image": byte_image.getvalue()},
|
||||
)
|
||||
|
||||
actual = response.json()
|
||||
assert response.status_code == 200
|
||||
assert isinstance(actual, dict)
|
||||
assert isinstance(actual.get("clip", None), list)
|
||||
assert np.allclose(expected, actual["clip"])
|
||||
assert np.allclose(expected, actual)
|
||||
|
||||
def test_clip_text_endpoint(self, responses: dict[str, Any], deployed_app: TestClient) -> None:
|
||||
expected = responses["clip"]["text"]
|
||||
@@ -688,49 +611,38 @@ class TestEndpoints:
|
||||
response = deployed_app.post(
|
||||
"http://localhost:3003/predict",
|
||||
data={
|
||||
"entries": json.dumps(
|
||||
{
|
||||
"clip": {"textual": {"modelName": "ViT-B-32__openai"}},
|
||||
},
|
||||
),
|
||||
"modelName": "ViT-B-32__openai",
|
||||
"modelType": "clip",
|
||||
"text": "test search query",
|
||||
"options": json.dumps({"mode": "text"}),
|
||||
},
|
||||
)
|
||||
|
||||
actual = response.json()
|
||||
assert response.status_code == 200
|
||||
assert isinstance(actual, dict)
|
||||
assert isinstance(actual.get("clip", None), list)
|
||||
assert np.allclose(expected, actual["clip"])
|
||||
assert np.allclose(expected, actual)
|
||||
|
||||
def test_face_endpoint(self, pil_image: Image.Image, responses: dict[str, Any], deployed_app: TestClient) -> None:
|
||||
byte_image = BytesIO()
|
||||
pil_image.save(byte_image, format="jpeg")
|
||||
expected = responses["facial-recognition"]
|
||||
|
||||
response = deployed_app.post(
|
||||
"http://localhost:3003/predict",
|
||||
data={
|
||||
"entries": json.dumps(
|
||||
{
|
||||
"facial-recognition": {
|
||||
"detection": {"modelName": "buffalo_l", "options": {"minScore": 0.034}},
|
||||
"recognition": {"modelName": "buffalo_l"},
|
||||
}
|
||||
}
|
||||
)
|
||||
"modelName": "buffalo_l",
|
||||
"modelType": "facial-recognition",
|
||||
"options": json.dumps({"minScore": 0.034}),
|
||||
},
|
||||
files={"image": byte_image.getvalue()},
|
||||
)
|
||||
|
||||
actual = response.json()
|
||||
assert response.status_code == 200
|
||||
assert isinstance(actual, dict)
|
||||
assert actual.get("imageHeight", None) == responses["imageHeight"]
|
||||
assert actual.get("imageWidth", None) == responses["imageWidth"]
|
||||
assert "facial-recognition" in actual and isinstance(actual["facial-recognition"], list)
|
||||
assert len(actual["facial-recognition"]) == len(responses["facial-recognition"])
|
||||
|
||||
for expected_face, actual_face in zip(responses["facial-recognition"], actual["facial-recognition"]):
|
||||
assert len(expected) == len(actual)
|
||||
for expected_face, actual_face in zip(expected, actual):
|
||||
assert expected_face["imageHeight"] == actual_face["imageHeight"]
|
||||
assert expected_face["imageWidth"] == actual_face["imageWidth"]
|
||||
assert expected_face["boundingBox"] == actual_face["boundingBox"]
|
||||
assert np.allclose(expected_face["embedding"], actual_face["embedding"])
|
||||
assert np.allclose(expected_face["score"], actual_face["score"])
|
||||
|
||||
@@ -37,6 +37,7 @@ def on_test_start(environment: Environment, **kwargs: Any) -> None:
|
||||
global byte_image
|
||||
assert environment.parsed_options is not None
|
||||
image = Image.new("RGB", (environment.parsed_options.image_size, environment.parsed_options.image_size))
|
||||
byte_image = BytesIO()
|
||||
image.save(byte_image, format="jpeg")
|
||||
|
||||
|
||||
@@ -44,25 +45,34 @@ class InferenceLoadTest(HttpUser):
|
||||
abstract: bool = True
|
||||
host = "http://127.0.0.1:3003"
|
||||
data: bytes
|
||||
headers: dict[str, str] = {"Content-Type": "image/jpg"}
|
||||
|
||||
# re-use the image across all instances in a process
|
||||
def on_start(self) -> None:
|
||||
global byte_image
|
||||
self.data = byte_image.getvalue()
|
||||
|
||||
|
||||
class CLIPTextFormDataLoadTest(InferenceLoadTest):
|
||||
@task
|
||||
def encode_text(self) -> None:
|
||||
request = {"clip": {"textual": {"modelName": self.environment.parsed_options.clip_model}}}
|
||||
data = [("entries", json.dumps(request)), ("text", "test search query")]
|
||||
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)
|
||||
|
||||
|
||||
class CLIPVisionFormDataLoadTest(InferenceLoadTest):
|
||||
@task
|
||||
def encode_image(self) -> None:
|
||||
request = {"clip": {"visual": {"modelName": self.environment.parsed_options.clip_model, "options": {}}}}
|
||||
data = [("entries", json.dumps(request))]
|
||||
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)
|
||||
|
||||
@@ -70,18 +80,11 @@ class CLIPVisionFormDataLoadTest(InferenceLoadTest):
|
||||
class RecognitionFormDataLoadTest(InferenceLoadTest):
|
||||
@task
|
||||
def recognize(self) -> None:
|
||||
request = {
|
||||
"facial-recognition": {
|
||||
"recognition": {
|
||||
"modelName": self.environment.parsed_options.face_model,
|
||||
"options": {"minScore": self.environment.parsed_options.face_min_score},
|
||||
},
|
||||
"detection": {
|
||||
"modelName": self.environment.parsed_options.face_model,
|
||||
},
|
||||
}
|
||||
}
|
||||
data = [("entries", json.dumps(request))]
|
||||
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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "machine-learning"
|
||||
version = "1.106.0"
|
||||
version = "1.105.1"
|
||||
description = ""
|
||||
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
||||
readme = "README.md"
|
||||
|
||||
@@ -213,6 +213,8 @@
|
||||
},
|
||||
"facial-recognition": [
|
||||
{
|
||||
"imageWidth": 600,
|
||||
"imageHeight": 800,
|
||||
"boundingBox": {
|
||||
"x1": 690.0,
|
||||
"y1": -89.0,
|
||||
@@ -323,7 +325,5 @@
|
||||
-0.077056274, 0.002099529
|
||||
]
|
||||
}
|
||||
],
|
||||
"imageWidth": 600,
|
||||
"imageHeight": 800
|
||||
]
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ platform :android do
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 141,
|
||||
"android.injected.version.name" => "1.106.0",
|
||||
"android.injected.version.code" => 140,
|
||||
"android.injected.version.name" => "1.105.1",
|
||||
}
|
||||
)
|
||||
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')
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"action_common_back": "خلف",
|
||||
"action_common_back": "Back",
|
||||
"action_common_cancel": "يلغي",
|
||||
"action_common_clear": "مسح",
|
||||
"action_common_confirm": "تأكيد",
|
||||
"action_common_clear": "Clear",
|
||||
"action_common_confirm": "Confirm",
|
||||
"action_common_update": "تحديث",
|
||||
"add_to_album_bottom_sheet_added": "تمت الاضافة{album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "موجودة مسبقا {album}",
|
||||
"advanced_settings_log_level_title": "Log level: {}",
|
||||
"advanced_settings_log_level_title": "تسجيل مستوى: {}",
|
||||
"advanced_settings_prefer_remote_subtitle": "تكون بعض الأجهزة بطيئة للغاية في تحميل الصور المصغرة من الأصول الموجودة على الجهاز. قم بتنشيط هذا الإعداد لتحميل الصور البعيدة بدلاً من ذلك.",
|
||||
"advanced_settings_prefer_remote_title": "تفضل الصور البعيدة",
|
||||
"advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.",
|
||||
@@ -18,11 +18,11 @@
|
||||
"album_info_card_backup_album_excluded": "مستبعد",
|
||||
"album_info_card_backup_album_included": "متضمنة",
|
||||
"album_thumbnail_card_item": "عنصر واحد",
|
||||
"album_thumbnail_card_items": "{} items",
|
||||
"album_thumbnail_card_items": "{} عناصر",
|
||||
"album_thumbnail_card_shared": " · . مشترك",
|
||||
"album_thumbnail_owned": "مملوكة",
|
||||
"album_thumbnail_shared_by": "Shared by {}",
|
||||
"album_viewer_appbar_delete_confirm": "هل أنت متأكد أنك تريد حذف هذا الألبوم من حسابك؟",
|
||||
"album_thumbnail_shared_by": "مشترك مع",
|
||||
"album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?",
|
||||
"album_viewer_appbar_share_delete": "حذف الألبوم",
|
||||
"album_viewer_appbar_share_err_delete": "فشل في حذف الألبوم",
|
||||
"album_viewer_appbar_share_err_leave": "فشل في ترك الألبوم",
|
||||
@@ -38,20 +38,20 @@
|
||||
"app_bar_signout_dialog_ok": "نعم",
|
||||
"app_bar_signout_dialog_title": "خروج",
|
||||
"archive_page_no_archived_assets": "لم يتم العثور على الأصول المؤرشفة",
|
||||
"archive_page_title": "Archive ({})",
|
||||
"archive_page_title": "أرشيف ({})",
|
||||
"asset_action_delete_err_read_only": "لا يمكن حذف الأصول ذات للقراءة فقط، وسوف يتم التخطي",
|
||||
"asset_action_share_err_offline": "لا يمكن جلب الأصول غير المتصلة بالإنترنت، وسوف يتم التخطي",
|
||||
"asset_list_group_by_sub_title": "تنظيم بواسطة",
|
||||
"asset_list_group_by_sub_title": "Group by",
|
||||
"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_sub_title": "تصميم",
|
||||
"asset_list_layout_sub_title": "Layout",
|
||||
"asset_list_settings_subtitle": "إعدادات تخطيط شبكة الصور",
|
||||
"asset_list_settings_title": "شبكة الصور",
|
||||
"asset_viewer_settings_title": "عارض الأصول",
|
||||
"backup_album_selection_page_albums_device": "Albums on device ({})",
|
||||
"asset_viewer_settings_title": "Asset Viewer",
|
||||
"backup_album_selection_page_albums_device": "الألبومات الموجودة على الجهاز ({})",
|
||||
"backup_album_selection_page_albums_tap": "انقر للتضمين، وانقر نقرًا مزدوجًا للاستثناء",
|
||||
"backup_album_selection_page_assets_scatter": "يمكن أن تنتشر الأصول عبر ألبومات متعددة. وبالتالي، يمكن تضمين الألبومات أو استبعادها أثناء عملية النسخ الاحتياطي.",
|
||||
"backup_album_selection_page_select_albums": "حدد الألبومات",
|
||||
@@ -60,22 +60,22 @@
|
||||
"backup_all": "الجميع",
|
||||
"backup_background_service_backup_failed_message": "فشل في النسخ الاحتياطي للأصول. جارٍ إعادة المحاولة...",
|
||||
"backup_background_service_connection_failed_message": "فشل في الاتصال بالخادم. جارٍ إعادة المحاولة...",
|
||||
"backup_background_service_current_upload_notification": "Uploading {}",
|
||||
"backup_background_service_current_upload_notification": "تحميل {}",
|
||||
"backup_background_service_default_notification": "التحقق من الأصول الجديدة ...",
|
||||
"backup_background_service_error_title": "خطأ في النسخ الاحتياطي",
|
||||
"backup_background_service_in_progress_notification": "النسخ الاحتياطي للأصول الخاصة بك...",
|
||||
"backup_background_service_upload_failure_notification": "Failed to upload {}",
|
||||
"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_battery_info_link": "أرني كيف",
|
||||
"backup_controller_page_background_battery_info_message": "للحصول على أفضل تجربة نسخ احتياطي في الخلفية، يرجى تعطيل أي تحسينات للبطارية تقيد نشاط الخلفية لـ تطبيق.\n\nنظرًا لأن هذا خاص بالجهاز، يرجى البحث عن المعلومات المطلوبة للشركة المصنعة لجهازك.",
|
||||
"backup_controller_page_background_battery_info_message": "للحصول على أفضل تجربة نسخ احتياطي في الخلفية، يرجى تعطيل أي تحسينات للبطارية تقيد نشاط الخلفية لـ Immich.\n\nنظرًا لأن هذا خاص بالجهاز، يرجى البحث عن المعلومات المطلوبة للشركة المصنعة لجهازك.",
|
||||
"backup_controller_page_background_battery_info_ok": "نعم",
|
||||
"backup_controller_page_background_battery_info_title": "تحسين البطارية",
|
||||
"backup_controller_page_background_charging": "فقط أثناء الشحن",
|
||||
"backup_controller_page_background_configure_error": "فشل في تكوين خدمة الخلفية",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_delay": "تأخير النسخ الاحتياطي للأصول الجديدة: {}",
|
||||
"backup_controller_page_background_description": "قم بتشغيل خدمة الخلفية لإجراء نسخ احتياطي لأي أصول جديدة تلقائيًا دون الحاجة إلى فتح التطبيق",
|
||||
"backup_controller_page_background_is_off": "تم إيقاف النسخ الاحتياطي التلقائي للخلفية",
|
||||
"backup_controller_page_background_is_on": "النسخ الاحتياطي التلقائي للخلفية قيد التشغيل",
|
||||
@@ -86,12 +86,12 @@
|
||||
"backup_controller_page_backup_selected": "المحدد: ",
|
||||
"backup_controller_page_backup_sub": "النسخ الاحتياطي للصور ومقاطع الفيديو",
|
||||
"backup_controller_page_cancel": "يلغي",
|
||||
"backup_controller_page_created": "Created on: {}",
|
||||
"backup_controller_page_created": "تم إنشاؤها على: {}",
|
||||
"backup_controller_page_desc_backup": "قم بتشغيل النسخ الاحتياطي الأمامي لتحميل الأصول الجديدة تلقائيًا إلى الخادم عند فتح التطبيق.",
|
||||
"backup_controller_page_excluded": "مستبعد: ",
|
||||
"backup_controller_page_failed": "Failed ({})",
|
||||
"backup_controller_page_filename": "File name: {} [{}]",
|
||||
"backup_controller_page_id": "ID: {}",
|
||||
"backup_controller_page_failed": "فشل ({})",
|
||||
"backup_controller_page_filename": "اسم الملف: {} [{}]",
|
||||
"backup_controller_page_id": "رقم البطاقة: {}",
|
||||
"backup_controller_page_info": "معلومات النسخ الاحتياطي",
|
||||
"backup_controller_page_none_selected": "لم يتم التحديد",
|
||||
"backup_controller_page_remainder": "بقية",
|
||||
@@ -101,7 +101,7 @@
|
||||
"backup_controller_page_start_backup": "بدء النسخ الاحتياطي",
|
||||
"backup_controller_page_status_off": "النسخة الاحتياطية التلقائية غير فعالة",
|
||||
"backup_controller_page_status_on": "النسخة الاحتياطية التلقائية فعالة",
|
||||
"backup_controller_page_storage_format": "{} of {} used",
|
||||
"backup_controller_page_storage_format": "{} من {} المستخدم",
|
||||
"backup_controller_page_to_backup": "الألبومات الاحتياطية",
|
||||
"backup_controller_page_total": "المجموع",
|
||||
"backup_controller_page_total_sub": "جميع الصور ومقاطع الفيديو الفريدة من ألبومات مختارة",
|
||||
@@ -115,22 +115,22 @@
|
||||
"backup_manual_in_progress": "قيد التحميل حاول مره اخرى",
|
||||
"backup_manual_success": "نجاح",
|
||||
"backup_manual_title": "حالة التحميل",
|
||||
"backup_options_page_title": "خيارات النسخ الاحتياطي",
|
||||
"cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
|
||||
"backup_options_page_title": "Backup options",
|
||||
"cache_settings_album_thumbnails": "صور صفحة المكتبة ({} الأصول)",
|
||||
"cache_settings_clear_cache_button": "مسح ذاكرة التخزين المؤقت",
|
||||
"cache_settings_clear_cache_button_title": "يقوم بمسح ذاكرة التخزين المؤقت للتطبيق.سيؤثر هذا بشكل كبير على أداء التطبيق حتى إعادة بناء ذاكرة التخزين المؤقت.",
|
||||
"cache_settings_duplicated_assets_clear_button": "واضح",
|
||||
"cache_settings_duplicated_assets_subtitle": "الصور ومقاطع الفيديو اللتي تم تجاهلها المدرجة في التطبيق",
|
||||
"cache_settings_duplicated_assets_title": "Duplicated Assets ({})",
|
||||
"cache_settings_image_cache_size": "Image cache size ({} assets)",
|
||||
"cache_settings_duplicated_assets_title": "الأصول المكررة ({})",
|
||||
"cache_settings_image_cache_size": "حجم ذاكرة التخزين المؤقت للصور ({} الأصول)",
|
||||
"cache_settings_statistics_album": "مكتبه الصور المصغره",
|
||||
"cache_settings_statistics_assets": "{} assets ({})",
|
||||
"cache_settings_statistics_assets": " ({})أصول ",
|
||||
"cache_settings_statistics_full": "صور كاملة",
|
||||
"cache_settings_statistics_shared": "صورة ألبوم مشتركة",
|
||||
"cache_settings_statistics_thumbnail": "الصورة المصغرة",
|
||||
"cache_settings_statistics_title": "استخدام ذاكرة التخزين المؤقت",
|
||||
"cache_settings_subtitle": "تحكم في سلوك التخزين المؤقت لتطبيق الجوال.",
|
||||
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
|
||||
"cache_settings_subtitle": "تحكم في سلوك التخزين المؤقت لتطبيق الجوال Imich",
|
||||
"cache_settings_thumbnail_size": "حجم ذاكرة التخزين المؤقت Thumbnail ({} الأصول)",
|
||||
"cache_settings_tile_subtitle": "التحكم في سلوك التخزين المحلي",
|
||||
"cache_settings_tile_title": "التخزين المحلي",
|
||||
"cache_settings_title": "إعدادات التخزين المؤقت",
|
||||
@@ -145,12 +145,12 @@
|
||||
"common_server_error": "يرجى التحقق من اتصال الشبكة الخاص بك ، والتأكد من أن الجهاز قابل للوصول وإصدارات التطبيق/الجهاز متوافقة.",
|
||||
"common_shared": "مشترك",
|
||||
"control_bottom_app_bar_add_to_album": "أضف إلى الألبوم",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_album_info": "{} أغراض",
|
||||
"control_bottom_app_bar_album_info_shared": "{} العناصر المشتركة",
|
||||
"control_bottom_app_bar_archive": "أرشيف",
|
||||
"control_bottom_app_bar_create_new_album": "إنشاء ألبوم جديد",
|
||||
"control_bottom_app_bar_delete": "يمسح",
|
||||
"control_bottom_app_bar_delete_from_immich": " حذف منال تطبيق",
|
||||
"control_bottom_app_bar_delete_from_immich": " Immich حذف منال تطبيق",
|
||||
"control_bottom_app_bar_delete_from_local": "حذف من الجهاز",
|
||||
"control_bottom_app_bar_edit_location": "تحديد الوجهة",
|
||||
"control_bottom_app_bar_edit_time": "تحرير التاريخ والوقت",
|
||||
@@ -172,10 +172,10 @@
|
||||
"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_local": " العناصر التي تم حذفها من جهازك ولكنها موجوده في تطبيق",
|
||||
"delete_dialog_alert_local_non_backed_up": "بعض العناصر التي سيتم حذفها بشكل دائم ولا يوجد لها نسخه احتياطيه في تطبيق ",
|
||||
"delete_dialog_alert_remote": "العناصر التي سيتم حذفها بشكل دائم من تطبيق",
|
||||
"delete_dialog_alert": " Immich هذه العناصر سيتم حذفها بشكل دائم من جهازك ومن تطبيق",
|
||||
"delete_dialog_alert_local": " Immich العناصر التي تم حذفها من جهازك ولكنها موجوده في تطبيق",
|
||||
"delete_dialog_alert_local_non_backed_up": "Immich بعض العناصر التي سيتم حذفها بشكل دائم ولا يوجد لها نسخه احتياطيه في تطبيق ",
|
||||
"delete_dialog_alert_remote": "Immich العناصر التي سيتم حذفها بشكل دائم من تطبيق",
|
||||
"delete_dialog_cancel": "يلغي",
|
||||
"delete_dialog_ok": "يمسح",
|
||||
"delete_dialog_ok_force": "احذف على أي حال",
|
||||
@@ -194,15 +194,15 @@
|
||||
"exif_bottom_sheet_location": "موقع",
|
||||
"exif_bottom_sheet_location_add": "إضافة موقع",
|
||||
"exif_bottom_sheet_people": "الناس",
|
||||
"exif_bottom_sheet_person_add_person": "اضف اسما",
|
||||
"exif_bottom_sheet_person_add_person": "Add name",
|
||||
"experimental_settings_new_asset_list_subtitle": "أعمال جارية",
|
||||
"experimental_settings_new_asset_list_title": "تمكين شبكة الصور التجريبية",
|
||||
"experimental_settings_subtitle": "استخدام على مسؤوليتك الخاصة!",
|
||||
"experimental_settings_title": "تجريبي",
|
||||
"favorites_page_no_favorites": "لم يتم العثور على الأصول المفضلة",
|
||||
"favorites_page_title": "المفضلة",
|
||||
"haptic_feedback_switch": "تمكين ردود الفعل اللمسية",
|
||||
"haptic_feedback_title": "ردود فعل لمسية",
|
||||
"haptic_feedback_switch": "Enable haptic feedback",
|
||||
"haptic_feedback_title": "Haptic Feedback",
|
||||
"home_page_add_to_album_conflicts": "تمت إضافة {تمت إضافة} الأصول إلى الألبوم {الألبوم}.{فشل} الأصول موجودة بالفعل في الألبوم.",
|
||||
"home_page_add_to_album_err_local": "لا يمكن إضافة الأصول المحلية إلى الألبومات حتى الآن ، سوف يتخطى",
|
||||
"home_page_add_to_album_success": "تمت إضافة {تمت إضافة} الأصول إلى الألبوم {الألبوم}.",
|
||||
@@ -218,7 +218,7 @@
|
||||
"home_page_share_err_local": "لا يمكن مشاركة الأصول المحلية عبر الرابط ، سوف يتخطى",
|
||||
"home_page_upload_err_limit": "لا يمكن إلا تحميل 30 أحد الأصول في وقت واحد ، سوف يتخطى",
|
||||
"image_viewer_page_state_provider_download_error": "خطا في التحميل",
|
||||
"image_viewer_page_state_provider_download_started": "بدأ التنزيل",
|
||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "تم التنزيل بنجاح",
|
||||
"image_viewer_page_state_provider_share_error": "خطأ في المشاركة",
|
||||
"library_page_albums": "ألبومات",
|
||||
@@ -265,8 +265,8 @@
|
||||
"login_form_server_error": "لا يمكن الاتصال بالخادم.",
|
||||
"login_password_changed_error": "كان هناك خطأ في تحديث كلمة المرور الخاصة بك",
|
||||
"login_password_changed_success": "تم تحديث كلمة السر بنجاح",
|
||||
"map_assets_in_bound": "{} photo",
|
||||
"map_assets_in_bounds": "{} photos",
|
||||
"map_assets_in_bound": "{} صورة",
|
||||
"map_assets_in_bounds": "{} الصور",
|
||||
"map_cannot_get_user_location": "لا يمكن الحصول على موقع المستخدم",
|
||||
"map_location_dialog_cancel": "يلغي",
|
||||
"map_location_dialog_yes": "نعم",
|
||||
@@ -279,27 +279,27 @@
|
||||
"map_settings_dark_mode": "الوضع المظلم",
|
||||
"map_settings_date_range_option_all": "الجميع",
|
||||
"map_settings_date_range_option_day": "24 ساعة الماضية",
|
||||
"map_settings_date_range_option_days": "Past {} days",
|
||||
"map_settings_date_range_option_days": "الأيام الماضية {}",
|
||||
"map_settings_date_range_option_year": "السنة الفائتة",
|
||||
"map_settings_date_range_option_years": "Past {} years",
|
||||
"map_settings_date_range_option_years": "السنوات الماضية {}",
|
||||
"map_settings_dialog_cancel": "يلغي",
|
||||
"map_settings_dialog_save": "يحفظ",
|
||||
"map_settings_dialog_title": "إعدادات الخريطة",
|
||||
"map_settings_include_show_archived": "تشمل الأرشفة",
|
||||
"map_settings_include_show_partners": "تضمين الشركاء",
|
||||
"map_settings_include_show_partners": "Include Partners",
|
||||
"map_settings_only_relative_range": "نطاق الموعد",
|
||||
"map_settings_only_show_favorites": "اظهار المفضلة فقط",
|
||||
"map_settings_theme_settings": "مظهر الخريطة",
|
||||
"map_zoom_to_see_photos": "قم بتصغيرها لرؤية الصور",
|
||||
"memories_all_caught_up": "كل شيء محدث",
|
||||
"memories_check_back_tomorrow": "التحقق مرة أخرى غدا لمزيد من الذكريات",
|
||||
"memories_start_over": "ابدأ من جديد",
|
||||
"memories_swipe_to_close": "اسحب لأعلى للإغلاق",
|
||||
"memories_all_caught_up": "All caught up",
|
||||
"memories_check_back_tomorrow": "Check back tomorrow for more memories",
|
||||
"memories_start_over": "Start Over",
|
||||
"memories_swipe_to_close": "Swipe up to close",
|
||||
"monthly_title_text_date_format": "ط ط ط",
|
||||
"motion_photos_page_title": "الصور المتحركة",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "لا يمكن تعديل تاريخ الأصول (المواد) للقراءة فقط، سوف يتخطى",
|
||||
"multiselect_grid_edit_gps_err_read_only": "لا يمكن تعديل موقع الأصول (المواد) للقراءة فقط، سوف يتخطى",
|
||||
"no_assets_to_show": "لا توجد أصول لعرضها",
|
||||
"no_assets_to_show": "No assets to show",
|
||||
"notification_permission_dialog_cancel": "يلغي",
|
||||
"notification_permission_dialog_content": "لتمكين الإخطارات ، انتقل إلى الإعدادات و اختار السماح.",
|
||||
"notification_permission_dialog_settings": "إعدادات",
|
||||
@@ -307,14 +307,14 @@
|
||||
"notification_permission_list_tile_enable_button": "تمكين الإخطارات",
|
||||
"notification_permission_list_tile_title": "إذن الإخطار",
|
||||
"partner_list_user_photos": "{user}'s photos",
|
||||
"partner_list_view_all": "عرض الكل",
|
||||
"partner_list_view_all": "View all",
|
||||
"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": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_content": "{} لن يكون قادرًا على الوصول إلى صورك.",
|
||||
"partner_page_stop_sharing_title": "توقف عن مشاركة صورك؟",
|
||||
"partner_page_title": "شريك",
|
||||
"permission_onboarding_back": "خلف",
|
||||
@@ -327,7 +327,7 @@
|
||||
"permission_onboarding_permission_granted": "تم تأمين التصريح! وضعك تمام.",
|
||||
"permission_onboarding_permission_limited": "إذن محدود. للسماح بالنسخ الاحتياطي للتطبيق وإدارة مجموعة المعرض بالكامل، امنح أذونات الصور والفيديو في الإعدادات.",
|
||||
"permission_onboarding_request": "يتطلب التطبيق إذنًا لعرض الصور ومقاطع الفيديو الخاصة بك",
|
||||
"preferences_settings_title": "التفضيلات",
|
||||
"preferences_settings_title": "Preferences",
|
||||
"profile_drawer_app_logs": "السجلات",
|
||||
"profile_drawer_client_out_of_date_major": "تطبيق الهاتف المحمول قديم.يرجى التحديث إلى أحدث إصدار رئيسي.",
|
||||
"profile_drawer_client_out_of_date_minor": "تطبيق الهاتف المحمول قديم.يرجى التحديث إلى أحدث إصدار صغير.",
|
||||
@@ -342,18 +342,18 @@
|
||||
"recently_added_page_title": "أضيف مؤخرا",
|
||||
"scaffold_body_error_occurred": "حدث خطأ",
|
||||
"search_bar_hint": "ابحث عن صورك",
|
||||
"search_filter_apply": "اختار الفلتر ",
|
||||
"search_filter_camera_make": "صنع",
|
||||
"search_filter_camera_model": "نموذج",
|
||||
"search_filter_display_option_archive": "أرشيف",
|
||||
"search_filter_display_option_favorite": "مفضل",
|
||||
"search_filter_display_option_not_in_album": "ليس في الألبوم",
|
||||
"search_filter_location_city": "مدينة",
|
||||
"search_filter_location_country": "دولة",
|
||||
"search_filter_location_state": "ولاية",
|
||||
"search_filter_media_type_all": "الجميع",
|
||||
"search_filter_media_type_image": "صورة",
|
||||
"search_filter_media_type_video": "شريط فيديو",
|
||||
"search_filter_apply": "Apply filter",
|
||||
"search_filter_camera_make": "Make",
|
||||
"search_filter_camera_model": "Model",
|
||||
"search_filter_display_option_archive": "Archive",
|
||||
"search_filter_display_option_favorite": "Favorite",
|
||||
"search_filter_display_option_not_in_album": "Not in album",
|
||||
"search_filter_location_city": "City",
|
||||
"search_filter_location_country": "Country",
|
||||
"search_filter_location_state": "State",
|
||||
"search_filter_media_type_all": "All",
|
||||
"search_filter_media_type_image": "Image",
|
||||
"search_filter_media_type_video": "Video",
|
||||
"search_page_categories": "فئات",
|
||||
"search_page_favorites": "المفضلة",
|
||||
"search_page_motion_photos": "الصور المتحركه",
|
||||
@@ -391,15 +391,14 @@
|
||||
"setting_image_viewer_original_title": "تحميل الصورة الأصلية",
|
||||
"setting_image_viewer_preview_subtitle": "تمكين تحميل صورة متوسطة الدقة.تعطيل إما لتحميل مباشرة أو استخدام الصورة المصغرة مباشرة.",
|
||||
"setting_image_viewer_preview_title": "تحميل صورة معاينة",
|
||||
"setting_image_viewer_title": "الصور",
|
||||
"setting_languages_apply": "تغيير الإعدادات",
|
||||
"setting_languages_title": "اللغات",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
"setting_notifications_notify_hours": "{} hours",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "أخطر فشل النسخ الاحتياطي في الخلفية: {}",
|
||||
"setting_notifications_notify_hours": "{} ساعات",
|
||||
"setting_notifications_notify_immediately": "في الحال",
|
||||
"setting_notifications_notify_minutes": "{} minutes",
|
||||
"setting_notifications_notify_minutes": "{} دقائق",
|
||||
"setting_notifications_notify_never": "أبداً",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_notify_seconds": "{} ثانية",
|
||||
"setting_notifications_single_progress_subtitle": "معلومات التقدم التفصيلية تحميل لكل أصل",
|
||||
"setting_notifications_single_progress_title": "إظهار تقدم التفاصيل الاحتياطية الخلفية",
|
||||
"setting_notifications_subtitle": "اضبط تفضيلات الإخطار",
|
||||
@@ -407,10 +406,7 @@
|
||||
"setting_notifications_total_progress_subtitle": "التقدم التحميل العام (تم القيام به/إجمالي الأصول)",
|
||||
"setting_notifications_total_progress_title": "إظهار النسخ الاحتياطي الخلفية التقدم المحرز",
|
||||
"setting_pages_app_bar_settings": "إعدادات",
|
||||
"settings_require_restart": "يرجى إعادة تشغيل لتطبيق هذا الإعداد",
|
||||
"setting_video_viewer_looping_subtitle": "تمكين تكرار مقطع فيديو تلقائيًا في عارض التفاصيل.",
|
||||
"setting_video_viewer_looping_title": "تكرار مقطع فيديو تلقائيًا",
|
||||
"setting_video_viewer_title": "أشرطة فيديو",
|
||||
"settings_require_restart": "يرجى إعادة تشغيل Imich لتطبيق هذا الإعداد",
|
||||
"share_add": "يضيف",
|
||||
"share_add_photos": "إضافة الصور",
|
||||
"share_add_title": "إضافة عنوان",
|
||||
@@ -430,7 +426,7 @@
|
||||
"share_dialog_preparing": "تحضير...",
|
||||
"shared_link_app_bar_title": "روابط مشتركة",
|
||||
"shared_link_clipboard_copied_massage": "نسخ إلى الحافظة",
|
||||
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
|
||||
"shared_link_clipboard_text": "وصلة: {}كلمة المرور: {}",
|
||||
"shared_link_create_app_bar_title": "إنشاء رابط للمشاركة",
|
||||
"shared_link_create_error": "خطأ أثناء إنشاء رابط مشترك",
|
||||
"shared_link_create_info": "دع أي شخص لديه الرابط يرى الصور المحددة)",
|
||||
@@ -443,11 +439,11 @@
|
||||
"shared_link_edit_description_hint": "أدخل وصف المشاركة",
|
||||
"shared_link_edit_expire_after": "تنتهي بعد",
|
||||
"shared_link_edit_expire_after_option_day": "يوم 1",
|
||||
"shared_link_edit_expire_after_option_days": "{} days",
|
||||
"shared_link_edit_expire_after_option_days": "{} أيام",
|
||||
"shared_link_edit_expire_after_option_hour": "1 ساعة",
|
||||
"shared_link_edit_expire_after_option_hours": "{} hours",
|
||||
"shared_link_edit_expire_after_option_hours": "{} ساعات",
|
||||
"shared_link_edit_expire_after_option_minute": "1 دقيقة",
|
||||
"shared_link_edit_expire_after_option_minutes": "{} minutes",
|
||||
"shared_link_edit_expire_after_option_minutes": "{} دقائق",
|
||||
"shared_link_edit_expire_after_option_months": "{} months",
|
||||
"shared_link_edit_expire_after_option_never": "أبداً",
|
||||
"shared_link_edit_expire_after_option_year": "{} year",
|
||||
@@ -458,21 +454,21 @@
|
||||
"shared_link_empty": "ليس لديك أي روابط مشتركة",
|
||||
"shared_link_error_server_url_fetch": "لا يمكن جلب عنوان الخادم",
|
||||
"shared_link_expired": "منتهي الصلاحية",
|
||||
"shared_link_expires_day": "Expires in {} day",
|
||||
"shared_link_expires_days": "Expires in {} days",
|
||||
"shared_link_expires_hour": "Expires in {} hour",
|
||||
"shared_link_expires_hours": "Expires in {} hours",
|
||||
"shared_link_expires_minute": "Expires in {} minute",
|
||||
"shared_link_expires_minutes": "Expires in {} minutes",
|
||||
"shared_link_expires_day": "تنتهي في اليوم {}",
|
||||
"shared_link_expires_days": "تنتهي في {} أيام",
|
||||
"shared_link_expires_hour": "تنتهي في الساعة {}",
|
||||
"shared_link_expires_hours": "تنتهي في {} ساعات",
|
||||
"shared_link_expires_minute": "تنتهي في {} دقيقة",
|
||||
"shared_link_expires_minutes": "تنتهي في الدقائق {}",
|
||||
"shared_link_expires_never": "تنتهي ∞",
|
||||
"shared_link_expires_second": "Expires in {} second",
|
||||
"shared_link_expires_seconds": "Expires in {} seconds",
|
||||
"shared_link_expires_second": "تنتهي في {} الثاني",
|
||||
"shared_link_expires_seconds": "تنتهي في ثواني {}",
|
||||
"shared_link_individual_shared": "Individual shared",
|
||||
"shared_link_info_chip_download": "تحميل",
|
||||
"shared_link_info_chip_metadata": "EXIF",
|
||||
"shared_link_info_chip_upload": "رفع",
|
||||
"shared_link_manage_links": "إدارة الروابط المشتركة",
|
||||
"shared_link_public_album": "الألبوم العام",
|
||||
"shared_link_public_album": "Public album",
|
||||
"share_done": "منتهي",
|
||||
"share_invite": "دعوة إلى الألبوم",
|
||||
"sharing_page_album": "ألبومات مشتركة",
|
||||
@@ -486,7 +482,7 @@
|
||||
"tab_controller_nav_search": "يبحث",
|
||||
"tab_controller_nav_sharing": "مشاركة",
|
||||
"theme_setting_asset_list_storage_indicator_title": "عرض مؤشر التخزين على بلاط الأصول",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "عدد الأصول لكل صف ({})",
|
||||
"theme_setting_dark_mode_switch": "الوضع المظلم",
|
||||
"theme_setting_image_viewer_quality_subtitle": "اضبط جودة عارض الصورة التفصيلية",
|
||||
"theme_setting_image_viewer_quality_title": "جودة عارض الصورة",
|
||||
@@ -501,13 +497,13 @@
|
||||
"trash_page_empty_trash_btn": "افرغ سله المهملات ",
|
||||
"trash_page_empty_trash_dialog_content": "هل تريد تفريغ أصولك المهملة؟ ستتم إزالة هذه العناصر نهائيًا من التطبيق",
|
||||
"trash_page_empty_trash_dialog_ok": "نعم",
|
||||
"trash_page_info": "Trashed items will be permanently deleted after {} days",
|
||||
"trash_page_info": "سيتم حذف العناصر المحذوفة بشكل دائم بعد {} أيام",
|
||||
"trash_page_no_assets": "لا توجد اصول في سله المهملات",
|
||||
"trash_page_restore": "الترجيع من سله المهملات",
|
||||
"trash_page_restore_all": "استعادة الكل",
|
||||
"trash_page_select_assets_btn": "اختر الأصول ",
|
||||
"trash_page_select_btn": "يختار",
|
||||
"trash_page_title": "Trash ({})",
|
||||
"trash_page_title": "نفايات ({})",
|
||||
"upload_dialog_cancel": "يلغي",
|
||||
"upload_dialog_info": "هل تريد النسخ الاحتياطي للأصول (الأصول) المحددة إلى الخادم؟",
|
||||
"upload_dialog_ok": "رفع",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Načíst původní obrázek",
|
||||
"setting_image_viewer_preview_subtitle": "Umožňuje načíst obrázek se středním rozlišením. Zakažte, pokud chcete přímo načíst originál nebo použít pouze miniaturu.",
|
||||
"setting_image_viewer_preview_title": "Načíst náhled obrázku",
|
||||
"setting_image_viewer_title": "Obrázky",
|
||||
"setting_languages_apply": "Použít",
|
||||
"setting_languages_title": "Jazyk",
|
||||
"setting_notifications_notify_failures_grace_period": "Oznámení o selhání zálohování na pozadí: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"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",
|
||||
"setting_video_viewer_looping_subtitle": "Povolení automatické smyčky videa v prohlížeči detailů.",
|
||||
"setting_video_viewer_looping_title": "Smyčka",
|
||||
"setting_video_viewer_title": "Videa",
|
||||
"share_add": "Přidat",
|
||||
"share_add_photos": "Přidat fotografie",
|
||||
"share_add_title": "Přidat název",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"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_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Anvend",
|
||||
"setting_languages_title": "Sprog",
|
||||
"setting_notifications_notify_failures_grace_period": "Giv besked om fejl med sikkerhedskopiering i baggrunden: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Vis samlet baggrundsuploadstatus",
|
||||
"setting_pages_app_bar_settings": "Indstillinger",
|
||||
"settings_require_restart": "Genstart venligst Immich for at anvende denne ændring",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Tilføj",
|
||||
"share_add_photos": "Tilføj billeder",
|
||||
"share_add_title": "Tilføj en titel",
|
||||
|
||||
@@ -52,10 +52,10 @@
|
||||
"asset_list_settings_title": "Fotogitter",
|
||||
"asset_viewer_settings_title": "Fotoanzeige",
|
||||
"backup_album_selection_page_albums_device": "Alben auf dem Gerät ({})",
|
||||
"backup_album_selection_page_albums_tap": "Einmalig tippen um das Album zu verwenden, doppelt tippen um es zu entfernen.",
|
||||
"backup_album_selection_page_assets_scatter": "Elemente (Fotos / Videos) können sich über mehrere Alben verteilen. Daher können diese vor der Sicherung eingeschlossen oder ausgeschlossen werden.",
|
||||
"backup_album_selection_page_albums_tap": "Einmalig tippen um einzuschließen, doppelt tippen um zu entfernen",
|
||||
"backup_album_selection_page_assets_scatter": "Elemente können sich über mehrere Alben verteilen. Daher können diese vor der Sicherung eingeschlossen oder ausgeschlossen werden",
|
||||
"backup_album_selection_page_select_albums": "Alben auswählen",
|
||||
"backup_album_selection_page_selection_info": "Information",
|
||||
"backup_album_selection_page_selection_info": "Auswahl",
|
||||
"backup_album_selection_page_total_assets": "Elemente",
|
||||
"backup_all": "Alle",
|
||||
"backup_background_service_backup_failed_message": "Fehler beim Sichern von Elementen. Probiere erneut...",
|
||||
@@ -75,7 +75,7 @@
|
||||
"backup_controller_page_background_battery_info_title": "Batterieoptimierungen",
|
||||
"backup_controller_page_background_charging": "Nur während des Ladens",
|
||||
"backup_controller_page_background_configure_error": "Konnte Hintergrundservice nicht konfigurieren",
|
||||
"backup_controller_page_background_delay": "Sicherung neuer Elemente verzögern um: {}",
|
||||
"backup_controller_page_background_delay": "Sicherung neuer Elemente verzögern: {}",
|
||||
"backup_controller_page_background_description": "Schalte den Hintergrundservice ein, um neue Elemente automatisch im Hintergrund zu sichern ohne die App zu öffnen",
|
||||
"backup_controller_page_background_is_off": "Automatische Sicherung im Hintergrund ist deaktiviert",
|
||||
"backup_controller_page_background_is_on": "Automatische Sicherung im Hintergrund ist aktiviert",
|
||||
@@ -331,7 +331,7 @@
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_out_of_date_major": "Mobile-App ist veraltet. Bitte aktualisiere auf die neueste Major-Version.",
|
||||
"profile_drawer_client_out_of_date_minor": "Mobile-App ist veraltet. Bitte aktualisiere auf die neueste Minor-Version.",
|
||||
"profile_drawer_client_server_up_to_date": "Die App-Version / Server-Version sind aktuell.",
|
||||
"profile_drawer_client_server_up_to_date": "App und Server sind aktuell",
|
||||
"profile_drawer_documentation": "Dokumentation",
|
||||
"profile_drawer_github": "GitHub",
|
||||
"profile_drawer_server_out_of_date_major": "Server-Version ist veraltet. Bitte aktualisiere auf die neueste Major-Version.",
|
||||
@@ -382,19 +382,19 @@
|
||||
"select_additional_user_for_sharing_page_suggestions": "Vorschläge",
|
||||
"select_user_for_sharing_page_err_album": "Album konnte nicht erstellt werden",
|
||||
"select_user_for_sharing_page_share_suggestions": "Empfehlungen",
|
||||
"server_info_box_app_version": "App-Version",
|
||||
"server_info_box_app_version": "App Version",
|
||||
"server_info_box_latest_release": "Neueste Version",
|
||||
"server_info_box_server_url": "Server-URL",
|
||||
"server_info_box_server_version": "Server-Version",
|
||||
"setting_image_viewer_help": "Der Detailbildbetrachter lädt zuerst ein (kleines) Vorschaubild, dann ein Vorschaubild in mittlerer Größe (falls aktiviert) und schließlich das Original (falls aktiviert).",
|
||||
"server_info_box_server_version": "Server Version",
|
||||
"setting_image_viewer_help": "Der Detailbildbetrachter lädt zuerst die kleine Miniaturansicht, dann die Vorschau in mittlerer Größe (falls aktiviert) und schließlich das Original (falls aktiviert).",
|
||||
"setting_image_viewer_original_subtitle": "Aktivieren, um das Originalbild in voller Auflösung (groß!) zu laden. Deaktivieren, um den Datenverbrauch zu reduzieren (sowohl im Netzwerk als auch im Gerätespeicher).",
|
||||
"setting_image_viewer_original_title": "Original laden",
|
||||
"setting_image_viewer_preview_subtitle": "Aktivieren, um ein Bild mit mittlerer Auflösung zu laden. Deaktivieren, um entweder das Original direkt zu laden oder nur das Vorschaubild zu verwenden.",
|
||||
"setting_image_viewer_preview_subtitle": "Aktivieren, um ein Bild mit mittlerer Auflösung zu laden. Deaktivieren, um entweder das Original direkt zu laden oder nur die Miniaturansicht zu verwenden.",
|
||||
"setting_image_viewer_preview_title": "Vorschaubild laden",
|
||||
"setting_image_viewer_title": "Bilder",
|
||||
"setting_languages_apply": "Anwenden",
|
||||
"setting_languages_title": "Sprachen",
|
||||
"setting_notifications_notify_failures_grace_period": "Benachrichtigung bei Fehler/n in der Hintergrundsicherung: {}",
|
||||
"setting_notifications_notify_failures_grace_period": "Benachrichtigung über Fehler bei der Hintergrundsicherung: {}",
|
||||
"setting_notifications_notify_hours": "{} Stunden",
|
||||
"setting_notifications_notify_immediately": "sofort",
|
||||
"setting_notifications_notify_minutes": "{} Minuten",
|
||||
@@ -407,10 +407,10 @@
|
||||
"setting_notifications_total_progress_subtitle": "Gesamter Upload-Fortschritt (abgeschlossen/Anzahl Elemente)",
|
||||
"setting_notifications_total_progress_title": "Zeige Gesamtfortschritt bei der Hintergrundsicherung",
|
||||
"setting_pages_app_bar_settings": "Einstellungen",
|
||||
"settings_require_restart": "Bitte starte Immich neu, um diese Einstellung anzuwenden.",
|
||||
"setting_video_viewer_looping_subtitle": "Aktiviere diese Option, um ein Video in der Detailansicht automatisch in einer Schleife anzuzeigen.",
|
||||
"setting_video_viewer_looping_title": "Schleife / Looping",
|
||||
"setting_video_viewer_looping_subtitle": "Aktivieren, damit sich ein Video in der Detailansicht automatisch wiederholt.",
|
||||
"setting_video_viewer_looping_title": "Wiederholen",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"settings_require_restart": "Bitte starte Immich neu, um diese Einstellung anzuwenden.",
|
||||
"share_add": "Hinzufügen",
|
||||
"share_add_photos": "Fotos hinzufügen",
|
||||
"share_add_title": "Titel hinzufügen",
|
||||
@@ -485,12 +485,12 @@
|
||||
"tab_controller_nav_photos": "Fotos",
|
||||
"tab_controller_nav_search": "Suche",
|
||||
"tab_controller_nav_sharing": "Teilen",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Zeige Sicherungsstatus auf Vorschaubild",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Zeige Sicherungsstatus auf Miniaturbild",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Anzahl der Elemente pro Reihe ({})",
|
||||
"theme_setting_dark_mode_switch": "Dunkler Modus",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Einstellen der Qualität des Detailbildbetrachters",
|
||||
"theme_setting_image_viewer_quality_title": "Qualität des Bildbetrachters",
|
||||
"theme_setting_system_theme_switch": "Automatisch (Systemeinstellung)",
|
||||
"theme_setting_system_theme_switch": "Automatisch (Systemeinstellung folgen)",
|
||||
"theme_setting_theme_subtitle": "Wählen Sie die Themeneinstellung der App",
|
||||
"theme_setting_theme_title": "Theme",
|
||||
"theme_setting_three_stage_loading_subtitle": "Das dreistufige Ladeverfahren kann die Performance beim Laden verbessern, erhöht allerdings den Datenverbrauch deutlich",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"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_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
"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 backed up",
|
||||
"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",
|
||||
@@ -407,10 +407,10 @@
|
||||
"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": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Cargar imagen original",
|
||||
"setting_image_viewer_preview_subtitle": "Activar para cargar una imagen de resolución media. Deshabilitar para cargar directamente la imagen original o usar una miniatura.",
|
||||
"setting_image_viewer_preview_title": "Cargar imagen de previsualización",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Aplicar",
|
||||
"setting_languages_title": "Idiomas",
|
||||
"setting_notifications_notify_failures_grace_period": "Notificar fallos de copia de seguridad en segundo plano: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Mostrar progreso total de copia de seguridad en segundo plano",
|
||||
"setting_pages_app_bar_settings": "Ajustes",
|
||||
"settings_require_restart": "Por favor, reinicia Immich para aplicar este ajuste",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Agregar",
|
||||
"share_add_photos": "Agregar fotos",
|
||||
"share_add_title": "Agregar un título",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Cargar imagen original",
|
||||
"setting_image_viewer_preview_subtitle": "Activar para cargar una imagen de resolución media. Deshabilitar para cargar directamente la imagen original o usar una miniatura.",
|
||||
"setting_image_viewer_preview_title": "Cargar imagen de previsualización",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notificar fallos de copia de seguridad en segundo plano: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Mostrar progreso total de copia de seguridad en segundo plano",
|
||||
"setting_pages_app_bar_settings": "Ajustes",
|
||||
"settings_require_restart": "Por favor, reinicia Immich para aplicar este ajuste",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Agregar",
|
||||
"share_add_photos": "Agregar fotos",
|
||||
"share_add_title": "Agregar un título",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Cargar imagen original",
|
||||
"setting_image_viewer_preview_subtitle": "Activar para cargar una imagen de resolución media. Deshabilitar para cargar directamente la imagen original o usar una miniatura.",
|
||||
"setting_image_viewer_preview_title": "Cargar imagen de previsualización",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notificar fallos de copia de seguridad en segundo plano: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Mostrar progreso total de copia de seguridad en segundo plano",
|
||||
"setting_pages_app_bar_settings": "Ajustes",
|
||||
"settings_require_restart": "Por favor, reinicia Immich para aplicar este ajuste",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Agregar",
|
||||
"share_add_photos": "Agregar fotos",
|
||||
"share_add_title": "Agregar un título",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Cargar imagen original",
|
||||
"setting_image_viewer_preview_subtitle": "Activar para cargar una imagen de resolución media. Deshabilitar para cargar directamente la imagen original o usar una miniatura.",
|
||||
"setting_image_viewer_preview_title": "Cargar imagen de previsualización",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notificar fallos de copia de seguridad en segundo plano: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Mostrar progreso total de copia de seguridad en segundo plano",
|
||||
"setting_pages_app_bar_settings": "Configuración",
|
||||
"settings_require_restart": "Por favor, reinicia Immich para aplicar este ajuste",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Agregar",
|
||||
"share_add_photos": "Agregar fotos",
|
||||
"share_add_title": "Agregar un título",
|
||||
|
||||
@@ -218,7 +218,7 @@
|
||||
"home_page_share_err_local": "Paikallisia kohteita ei voitu jakaa linkkien avulla. Hypätään yli",
|
||||
"home_page_upload_err_limit": "Voit lähettää palvelimelle enintään 30 kohdetta kerrallaan, ohitetaan",
|
||||
"image_viewer_page_state_provider_download_error": "Lataus epäonnistui",
|
||||
"image_viewer_page_state_provider_download_started": "Lataaminen aloitettu",
|
||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "Lataus onnistui",
|
||||
"image_viewer_page_state_provider_share_error": "Jakovirhe",
|
||||
"library_page_albums": "Albumit",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Lataa alkuperäinen kuva",
|
||||
"setting_image_viewer_preview_subtitle": "Ota käyttöön ladataksesi keskitarkkuuksinen kuva. Poista käytöstä ladataksesi alkuperäinen kuva tai käyttääksesi vain esikatselukuvaa.",
|
||||
"setting_image_viewer_preview_title": "Lataa esikatselukuva",
|
||||
"setting_image_viewer_title": "Kuvat",
|
||||
"setting_languages_apply": "Käytä",
|
||||
"setting_languages_title": "Kieli",
|
||||
"setting_notifications_notify_failures_grace_period": "Ilmoita taustavarmuuskopioinnin epäonnistumisista: {}",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "Näytä taustavarmuuskopioinnin kokonaisedistyminen",
|
||||
"setting_pages_app_bar_settings": "Asetukset",
|
||||
"settings_require_restart": "Käynnistä Immich uudelleen ottaaksesti tämän asetuksen käyttöön",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videot",
|
||||
"share_add": "Lisää",
|
||||
"share_add_photos": "Lisää kuvia",
|
||||
"share_add_title": "Lisää nimi",
|
||||
"share_assets_selected": "{} valittu",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "Luo albumi",
|
||||
"shared_album_activities_input_disable": "Kommentointi on kytketty pois päältä",
|
||||
"shared_album_activities_input_hint": "Sano jotain",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Charger l'image originale",
|
||||
"setting_image_viewer_preview_subtitle": "Activer pour charger une image de résolution moyenne. Désactiver pour charger directement l'original ou utiliser uniquement la vignette.",
|
||||
"setting_image_viewer_preview_title": "Charger l'image d'aperçu",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notifier les échecs de la sauvegarde en arrière-plan: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Afficher la progression totale de la sauvegarde en arrière-plan",
|
||||
"setting_pages_app_bar_settings": "Paramètres",
|
||||
"settings_require_restart": "Veuillez redémarrer Immich pour appliquer ce paramètre",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Ajouter",
|
||||
"share_add_photos": "Ajouter des photos",
|
||||
"share_add_title": "Ajouter un titre",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"action_common_back": "Retour",
|
||||
"action_common_back": "Back",
|
||||
"action_common_cancel": "Annuler",
|
||||
"action_common_clear": "Vider",
|
||||
"action_common_confirm": "Confirmer",
|
||||
"action_common_clear": "Clear",
|
||||
"action_common_confirm": "Confirm",
|
||||
"action_common_update": "Mise à jour",
|
||||
"add_to_album_bottom_sheet_added": "Ajouté à {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Déjà dans {album}",
|
||||
@@ -22,7 +22,7 @@
|
||||
"album_thumbnail_card_shared": " · Partagé",
|
||||
"album_thumbnail_owned": "Possédé",
|
||||
"album_thumbnail_shared_by": "Partagé par {}",
|
||||
"album_viewer_appbar_delete_confirm": "Êtes-vous sur de vouloir supprimer cet album de votre compte?",
|
||||
"album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?",
|
||||
"album_viewer_appbar_share_delete": "Supprimer l'album",
|
||||
"album_viewer_appbar_share_err_delete": "Échec de la suppression de l'album",
|
||||
"album_viewer_appbar_share_err_leave": "Impossible de quitter l'album",
|
||||
@@ -41,7 +41,7 @@
|
||||
"archive_page_title": "Archive ({})",
|
||||
"asset_action_delete_err_read_only": "Impossible de supprimer le(s) élément(s) en lecture seule.",
|
||||
"asset_action_share_err_offline": "Impossible de récupérer le(s) élément(s) hors ligne.",
|
||||
"asset_list_group_by_sub_title": "Regrouper par",
|
||||
"asset_list_group_by_sub_title": "Groupe par",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Affichage dynamique",
|
||||
"asset_list_layout_settings_group_automatically": "Automatique",
|
||||
"asset_list_layout_settings_group_by": "Grouper les éléments par",
|
||||
@@ -115,7 +115,7 @@
|
||||
"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_options_page_title": "Options de sauvegarde",
|
||||
"backup_options_page_title": "Backup options",
|
||||
"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.",
|
||||
@@ -218,7 +218,7 @@
|
||||
"home_page_share_err_local": "Impossible de partager par lien les médias locaux, cette opération est donc ignorée.",
|
||||
"home_page_upload_err_limit": "Limite de téléchargement de 30 éléments en même temps, demande ignorée",
|
||||
"image_viewer_page_state_provider_download_error": "Erreur de téléchargement",
|
||||
"image_viewer_page_state_provider_download_started": "Téléchargement Démarré",
|
||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "Téléchargement réussi",
|
||||
"image_viewer_page_state_provider_share_error": "Erreur de partage",
|
||||
"library_page_albums": "Albums",
|
||||
@@ -286,7 +286,7 @@
|
||||
"map_settings_dialog_save": "Sauvegarder",
|
||||
"map_settings_dialog_title": "Paramètres de la carte",
|
||||
"map_settings_include_show_archived": "Inclure les archives",
|
||||
"map_settings_include_show_partners": "Inclure les partenaires",
|
||||
"map_settings_include_show_partners": "Include Partners",
|
||||
"map_settings_only_relative_range": "Plage de dates",
|
||||
"map_settings_only_show_favorites": "Afficher uniquement les favoris",
|
||||
"map_settings_theme_settings": "Thème de la carte",
|
||||
@@ -299,15 +299,15 @@
|
||||
"motion_photos_page_title": "Photos avec mouvement",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "Impossible de modifier la date d'un élément d'actif en lecture seule.",
|
||||
"multiselect_grid_edit_gps_err_read_only": "Impossible de modifier l'emplacement d'un élément en lecture seule.",
|
||||
"no_assets_to_show": "Aucuns éléments à afficher",
|
||||
"no_assets_to_show": "Aucun élément à afficher",
|
||||
"notification_permission_dialog_cancel": "Annuler",
|
||||
"notification_permission_dialog_content": "Pour activer les notifications, allez dans Paramètres et sélectionnez Autoriser.",
|
||||
"notification_permission_dialog_settings": "Paramètres",
|
||||
"notification_permission_list_tile_content": "Accordez la permission d'activer les notifications.",
|
||||
"notification_permission_list_tile_enable_button": "Activer les notifications",
|
||||
"notification_permission_list_tile_title": "Permission de notification",
|
||||
"partner_list_user_photos": "Photos de {user}",
|
||||
"partner_list_view_all": "Voir tous",
|
||||
"partner_list_user_photos": "{user}'s photos",
|
||||
"partner_list_view_all": "View all",
|
||||
"partner_page_add_partner": "Ajouter un partenaire",
|
||||
"partner_page_empty_message": "Vos photos ne sont pas encore partagées avec un partenaire.",
|
||||
"partner_page_no_more_users": "Plus d'utilisateurs à ajouter",
|
||||
@@ -342,18 +342,18 @@
|
||||
"recently_added_page_title": "Récemment ajouté",
|
||||
"scaffold_body_error_occurred": "Une erreur s'est produite",
|
||||
"search_bar_hint": "Rechercher vos photos",
|
||||
"search_filter_apply": "Appliquer le filtre",
|
||||
"search_filter_camera_make": "Fabricant",
|
||||
"search_filter_camera_model": "Modéle",
|
||||
"search_filter_display_option_archive": "Achive",
|
||||
"search_filter_display_option_favorite": "Favoris",
|
||||
"search_filter_display_option_not_in_album": "Pas dans un album",
|
||||
"search_filter_location_city": "Ville",
|
||||
"search_filter_location_country": "Pays",
|
||||
"search_filter_location_state": "Région",
|
||||
"search_filter_media_type_all": "Tous",
|
||||
"search_filter_apply": "Apply filter",
|
||||
"search_filter_camera_make": "Make",
|
||||
"search_filter_camera_model": "Model",
|
||||
"search_filter_display_option_archive": "Archive",
|
||||
"search_filter_display_option_favorite": "Favorite",
|
||||
"search_filter_display_option_not_in_album": "Not in album",
|
||||
"search_filter_location_city": "City",
|
||||
"search_filter_location_country": "Country",
|
||||
"search_filter_location_state": "State",
|
||||
"search_filter_media_type_all": "All",
|
||||
"search_filter_media_type_image": "Image",
|
||||
"search_filter_media_type_video": "Vidéo",
|
||||
"search_filter_media_type_video": "Video",
|
||||
"search_page_categories": "Catégories",
|
||||
"search_page_favorites": "Favoris",
|
||||
"search_page_motion_photos": "Photos animées",
|
||||
@@ -391,9 +391,8 @@
|
||||
"setting_image_viewer_original_title": "Charger l'image originale",
|
||||
"setting_image_viewer_preview_subtitle": "Activer pour charger une image de résolution moyenne. Désactiver pour charger directement l'original ou utiliser uniquement la miniature.",
|
||||
"setting_image_viewer_preview_title": "Charger l'image d'aperçu",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Appliquer",
|
||||
"setting_languages_title": "Langues",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notifier les échecs de la sauvegarde en arrière-plan : {}",
|
||||
"setting_notifications_notify_hours": "{} heures",
|
||||
"setting_notifications_notify_immediately": "immédiatement",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "Afficher la progression totale de la sauvegarde en arrière-plan",
|
||||
"setting_pages_app_bar_settings": "Paramètres",
|
||||
"settings_require_restart": "Veuillez redémarrer Immich pour appliquer ce paramètre",
|
||||
"setting_video_viewer_looping_subtitle": "Activer pour voir les vidéos en boucle dans le lecteur détaillé",
|
||||
"setting_video_viewer_looping_title": "Boucle",
|
||||
"setting_video_viewer_title": "Vidéos",
|
||||
"share_add": "Ajouter",
|
||||
"share_add_photos": "Ajouter des photos",
|
||||
"share_add_title": "Ajouter un titre",
|
||||
"share_assets_selected": "{} séléctionné(s)",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "Créer un album",
|
||||
"shared_album_activities_input_disable": "Les commentaires sont désactivés",
|
||||
"shared_album_activities_input_hint": "Dire quelque chose",
|
||||
@@ -448,9 +444,9 @@
|
||||
"shared_link_edit_expire_after_option_hours": "{} heures",
|
||||
"shared_link_edit_expire_after_option_minute": "1 minute",
|
||||
"shared_link_edit_expire_after_option_minutes": "{} minutes",
|
||||
"shared_link_edit_expire_after_option_months": "{} mois",
|
||||
"shared_link_edit_expire_after_option_months": "{} months",
|
||||
"shared_link_edit_expire_after_option_never": "Jamais",
|
||||
"shared_link_edit_expire_after_option_year": "{} ans",
|
||||
"shared_link_edit_expire_after_option_year": "{} year",
|
||||
"shared_link_edit_password": "Mot de passe",
|
||||
"shared_link_edit_password_hint": "Saisir le mot de passe de partage",
|
||||
"shared_link_edit_show_meta": "Afficher les métadonnées",
|
||||
@@ -467,12 +463,12 @@
|
||||
"shared_link_expires_never": "Expire ∞",
|
||||
"shared_link_expires_second": "Expire dans {} seconde",
|
||||
"shared_link_expires_seconds": "Expire dans {} secondes",
|
||||
"shared_link_individual_shared": "Partagé individuellement",
|
||||
"shared_link_individual_shared": "Individual shared",
|
||||
"shared_link_info_chip_download": "Téléchargement",
|
||||
"shared_link_info_chip_metadata": "EXIF",
|
||||
"shared_link_info_chip_upload": "Chargement",
|
||||
"shared_link_manage_links": "Gérer les liens partagés",
|
||||
"shared_link_public_album": "Album public",
|
||||
"shared_link_public_album": "Public album",
|
||||
"share_done": "Fait",
|
||||
"share_invite": "Inviter à l'album",
|
||||
"sharing_page_album": "Albums partagés",
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
"backup_controller_page_background_turn_off": "כבה שירות ברקע",
|
||||
"backup_controller_page_background_turn_on": "הפעל שירות ברקע",
|
||||
"backup_controller_page_background_wifi": "רק ברשת אלחוטית",
|
||||
"backup_controller_page_backup": "גובו",
|
||||
"backup_controller_page_backup": "גיבוי",
|
||||
"backup_controller_page_backup_selected": "נבחרו:",
|
||||
"backup_controller_page_backup_sub": "תמונות וסרטונים מגובים",
|
||||
"backup_controller_page_cancel": "ביטול",
|
||||
@@ -94,7 +94,7 @@
|
||||
"backup_controller_page_id": "מזהה: {}",
|
||||
"backup_controller_page_info": "פרטי גיבוי",
|
||||
"backup_controller_page_none_selected": "לא נבחרו",
|
||||
"backup_controller_page_remainder": "בתור לגיבוי",
|
||||
"backup_controller_page_remainder": "יתרה",
|
||||
"backup_controller_page_remainder_sub": "תמונות וסרטונים שנותרו לגבות מתוך בחירה",
|
||||
"backup_controller_page_select": "בחר",
|
||||
"backup_controller_page_server_storage": "אחסון שרת",
|
||||
@@ -107,7 +107,7 @@
|
||||
"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": "בוטל",
|
||||
@@ -218,7 +218,7 @@
|
||||
"home_page_share_err_local": "לא ניתן לשתף נכסים מקומיים על ידי קישור, מדלג",
|
||||
"home_page_upload_err_limit": "יכול רק להעלות מקסימום של 30 נכסים בכל פעם, מדלג",
|
||||
"image_viewer_page_state_provider_download_error": "שגיאת הורדה",
|
||||
"image_viewer_page_state_provider_download_started": "ההורדה החלה",
|
||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "הצלחת הורדה",
|
||||
"image_viewer_page_state_provider_share_error": "שיתוף שגיאה",
|
||||
"library_page_albums": "אלבומים",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "טען תמונה מקורית",
|
||||
"setting_image_viewer_preview_subtitle": "אפשר לטעון תמונה ברזלוציה בינונית. השבת כדי או לטעון את המקורית או רק להשתמש בתמונה הממוזערת.",
|
||||
"setting_image_viewer_preview_title": "טען תמונת תצוגה מקדימה",
|
||||
"setting_image_viewer_title": "תמונות",
|
||||
"setting_languages_apply": "החל",
|
||||
"setting_languages_title": "שפות",
|
||||
"setting_notifications_notify_failures_grace_period": "להודיע על כשלים בגיבוי ברקע: {}",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "הראה סה״כ התקדמות גיבוי ברקע",
|
||||
"setting_pages_app_bar_settings": "הגדרות",
|
||||
"settings_require_restart": "אנא הפעל מחדש את Immich כדי להחיל הגדרה זו",
|
||||
"setting_video_viewer_looping_subtitle": "אפשר וידיאו ברצף אוטומטית בחלון המידע",
|
||||
"setting_video_viewer_looping_title": "לולאה",
|
||||
"setting_video_viewer_title": "סרטונים",
|
||||
"share_add": "הוסף",
|
||||
"share_add_photos": "הוסף תמונות",
|
||||
"share_add_title": "הוסף כותרת",
|
||||
"share_assets_selected": "{} נבחרו",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "צור אלבום",
|
||||
"shared_album_activities_input_disable": "התגובה מושבתת",
|
||||
"shared_album_activities_input_hint": "הגב/י משהו",
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
"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 backed up",
|
||||
"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",
|
||||
@@ -391,7 +391,6 @@
|
||||
"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_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
|
||||
@@ -194,7 +194,7 @@
|
||||
"exif_bottom_sheet_location": "HELY",
|
||||
"exif_bottom_sheet_location_add": "Hely hozzáadása",
|
||||
"exif_bottom_sheet_people": "EMBEREK",
|
||||
"exif_bottom_sheet_person_add_person": "Elnevez",
|
||||
"exif_bottom_sheet_person_add_person": "Név hozzáadása",
|
||||
"experimental_settings_new_asset_list_subtitle": "Fejlesztés alatt",
|
||||
"experimental_settings_new_asset_list_title": "Kisérleti képrács engedélyezése",
|
||||
"experimental_settings_subtitle": "Csak saját felelősségre használd!",
|
||||
@@ -363,9 +363,9 @@
|
||||
"search_page_person_add_name_dialog_cancel": "Mégsem",
|
||||
"search_page_person_add_name_dialog_hint": "Név",
|
||||
"search_page_person_add_name_dialog_save": "Mentés",
|
||||
"search_page_person_add_name_dialog_title": "Elnevez",
|
||||
"search_page_person_add_name_dialog_title": "Név hozzáadása",
|
||||
"search_page_person_add_name_subtitle": "Név szerint gyorsan megtalálhatod a keresőben",
|
||||
"search_page_person_add_name_title": "Elnevez",
|
||||
"search_page_person_add_name_title": "Név hozzáadása",
|
||||
"search_page_person_edit_name": "Név módosítása",
|
||||
"search_page_places": "Helyek",
|
||||
"search_page_recently_added": "Nemrég hozzáadott",
|
||||
@@ -374,8 +374,8 @@
|
||||
"search_page_things": "Dolgok",
|
||||
"search_page_videos": "Videók",
|
||||
"search_page_view_all_button": "Összes mutatása",
|
||||
"search_page_your_activity": "Tevékenységek",
|
||||
"search_page_your_map": "Térkép",
|
||||
"search_page_your_activity": "Tevékenységeid",
|
||||
"search_page_your_map": "Térképed",
|
||||
"search_result_page_new_search_hint": "Új keresés",
|
||||
"search_suggestion_list_smart_search_hint_1": "Az intelligens keresés alapértelmezetten be van kapcsolva, metaadatokat így kereshetsz",
|
||||
"search_suggestion_list_smart_search_hint_2": "m:keresési-kifejezés",
|
||||
@@ -386,12 +386,11 @@
|
||||
"server_info_box_latest_release": "Legfrissebb Verzió",
|
||||
"server_info_box_server_url": "Szerver Címe",
|
||||
"server_info_box_server_version": "Szerver Verzió",
|
||||
"setting_image_viewer_help": "Az Elem Megjelenítő először a kis bélyegképet tölti be, aztán a közepes méretű előnézetet (ha elérhető), végül az eredetit (ha elérhető).",
|
||||
"setting_image_viewer_help": "A képnézegető először a kis bélyegképet tölti be, aztán a közepes méretű előnézetet (ha elérhető), végül az eredetit (ha elérhető).",
|
||||
"setting_image_viewer_original_subtitle": "Engedélyezi az eredeti teljes felbontású kép betöltését (nagy!). Kikapcsolva csökkenti az adathasználatot (a neten és az eszköz gyorsítótárán is).",
|
||||
"setting_image_viewer_original_title": "Eredeti kép betöltése",
|
||||
"setting_image_viewer_preview_subtitle": "Engedélyezi a közepes felbontású kép betöltését. Kikapcsolva vagy az eredeti kép töltődik be, vagy csak a bélyegkép.",
|
||||
"setting_image_viewer_preview_title": "Előnézet betöltése",
|
||||
"setting_image_viewer_title": "Képek",
|
||||
"setting_languages_apply": "Alkalmaz",
|
||||
"setting_languages_title": "Nyelvek",
|
||||
"setting_notifications_notify_failures_grace_period": "Értesítés a háttérben történő mentés hibáiról: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Mutassa a háttérben történő mentés teljes folyamatát",
|
||||
"setting_pages_app_bar_settings": "Beállítások",
|
||||
"settings_require_restart": "Ennek a beállításnak az érvénybe lépéséhez indítsd újra az Immich-et",
|
||||
"setting_video_viewer_looping_subtitle": "Engedélyezi a videók folyamatosan ismételt lejátszását az elem megjelenítőben",
|
||||
"setting_video_viewer_looping_title": "Ismétlés",
|
||||
"setting_video_viewer_title": "Videók",
|
||||
"share_add": "Hozzáadás",
|
||||
"share_add_photos": "Fotók hozzáadása",
|
||||
"share_add_title": "Album neve",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Carica l'immagine originale",
|
||||
"setting_image_viewer_preview_subtitle": "Abilita per caricare un'immagine a risoluzione media.\nDisabilita per caricare direttamente l'immagine originale o usare la thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Carica immagine di anteprima",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Applica",
|
||||
"setting_languages_title": "Lingue",
|
||||
"setting_notifications_notify_failures_grace_period": "Notifica caricamenti falliti in background: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Mostra avanzamento del backup in background",
|
||||
"setting_pages_app_bar_settings": "Impostazioni",
|
||||
"settings_require_restart": "Si prega di riavviare Immich perché vengano applicate le impostazioni",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Aggiungi",
|
||||
"share_add_photos": "Aggiungi foto",
|
||||
"share_add_title": "Aggiungi un titolo ",
|
||||
|
||||
@@ -174,7 +174,7 @@
|
||||
"date_format": "MM月 DD日, EE • hh時mm分",
|
||||
"delete_dialog_alert": "サーバーとデバイスの両方から永久的に削除されます!",
|
||||
"delete_dialog_alert_local": "選択された項目はデバイスから削除されますが、Immichには残ります",
|
||||
"delete_dialog_alert_local_non_backed_up": "Immichにバックアップされていない項目があります。デバイスからも永久に削除されます",
|
||||
"delete_dialog_alert_local_non_backed_up": "Immichにバックアップされていない項目があります。それらはデバイスからも永久に削除されます",
|
||||
"delete_dialog_alert_remote": "選択された項目はImmichから永久に削除されます",
|
||||
"delete_dialog_cancel": "キャンセル",
|
||||
"delete_dialog_ok": "削除",
|
||||
@@ -201,8 +201,8 @@
|
||||
"experimental_settings_title": "試験的機能",
|
||||
"favorites_page_no_favorites": "お気に入り登録された写真またはビデオがありません",
|
||||
"favorites_page_title": "お気に入り",
|
||||
"haptic_feedback_switch": "ハプティックフィードバック",
|
||||
"haptic_feedback_title": "ハプティックフィードバックを有効にする",
|
||||
"haptic_feedback_switch": "触覚フィードバック",
|
||||
"haptic_feedback_title": "触覚フィードバックを有効にする",
|
||||
"home_page_add_to_album_conflicts": "{album}に{added}枚写真を追加しました。追加済みの{failed}枚はスキップしました。",
|
||||
"home_page_add_to_album_err_local": "まだアップロードされてない項目は、アルバムに登録できません",
|
||||
"home_page_add_to_album_success": "{album}に{added}枚写真を追加しました",
|
||||
@@ -299,7 +299,7 @@
|
||||
"motion_photos_page_title": "モーションフォト",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "読み取り専用の項目の日付を変更できません",
|
||||
"multiselect_grid_edit_gps_err_read_only": "読み取り専用の項目の位置情報を変更できません",
|
||||
"no_assets_to_show": "表示する項目がありません",
|
||||
"no_assets_to_show": "表示するアセットがありません",
|
||||
"notification_permission_dialog_cancel": "キャンセル",
|
||||
"notification_permission_dialog_content": "通知を許可するには設定を開いてオンにしてください",
|
||||
"notification_permission_dialog_settings": "設定",
|
||||
@@ -330,12 +330,12 @@
|
||||
"preferences_settings_title": "設定",
|
||||
"profile_drawer_app_logs": "ログ",
|
||||
"profile_drawer_client_out_of_date_major": "アプリが更新されてません。最新のバージョンに更新してください",
|
||||
"profile_drawer_client_out_of_date_minor": "アプリが更新されてません。最新のバージョンに更新してください",
|
||||
"profile_drawer_client_out_of_date_minor": "アプリが更新されてません。最新のマイナーバージョンに更新してください",
|
||||
"profile_drawer_client_server_up_to_date": "すべて最新です",
|
||||
"profile_drawer_documentation": "Immichのドキュメント",
|
||||
"profile_drawer_github": "GitHub",
|
||||
"profile_drawer_server_out_of_date_major": "サーバーが更新されてません。最新のバージョンに更新してください",
|
||||
"profile_drawer_server_out_of_date_minor": "サーバーが更新されてません。最新のバージョンに更新してください",
|
||||
"profile_drawer_server_out_of_date_minor": "サーバーが更新されてません。最新のマイナーバージョンに更新してください",
|
||||
"profile_drawer_settings": "設定",
|
||||
"profile_drawer_sign_out": "サインアウト",
|
||||
"profile_drawer_trash": "ゴミ箱",
|
||||
@@ -347,7 +347,7 @@
|
||||
"search_filter_camera_model": "モデル",
|
||||
"search_filter_display_option_archive": "アーカイブ",
|
||||
"search_filter_display_option_favorite": "お気に入り",
|
||||
"search_filter_display_option_not_in_album": "アルバムにありません",
|
||||
"search_filter_display_option_not_in_album": "アルバムにない",
|
||||
"search_filter_location_city": "市町村",
|
||||
"search_filter_location_country": "国",
|
||||
"search_filter_location_state": "都道府県",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "オリジナル画像を読み込む",
|
||||
"setting_image_viewer_preview_subtitle": "中画質の写真をロードしたいときにオンにしてください。直接最大画質の写真を表示したい場合はオフにしてください。(ロード中はサムネイルが代わりに表示されます)",
|
||||
"setting_image_viewer_preview_title": "プレビュー画像をロードする",
|
||||
"setting_image_viewer_title": "画像",
|
||||
"setting_languages_apply": "適用する",
|
||||
"setting_languages_title": "言語",
|
||||
"setting_notifications_notify_failures_grace_period": "バックアップ失敗の通知: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "実行中のバックアップの進行状況を表示",
|
||||
"setting_pages_app_bar_settings": "設定",
|
||||
"settings_require_restart": "Immichを再起動して設定を適用してください",
|
||||
"setting_video_viewer_looping_subtitle": "有効にするとディテールビューで自動で動画がループします",
|
||||
"setting_video_viewer_looping_title": "ループ中",
|
||||
"setting_video_viewer_title": "ビデオ",
|
||||
"share_add": "追加",
|
||||
"share_add_photos": "写真を追加",
|
||||
"share_add_title": "タイトルを追加",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "원본 이미지 불러오기",
|
||||
"setting_image_viewer_preview_subtitle": "중간 해상도 이미지를 로드하려면 활성화합니다. 원본을 직접 로드하거나 썸네일만 사용하려면 비활성화 하세요.",
|
||||
"setting_image_viewer_preview_title": "미리보기 이미지 불러오기",
|
||||
"setting_image_viewer_title": "이미지",
|
||||
"setting_languages_apply": "적용",
|
||||
"setting_languages_title": "언어",
|
||||
"setting_notifications_notify_failures_grace_period": "백그라운드 백업 실패 알림: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "백그라운드 작업 전체 진행률 표시",
|
||||
"setting_pages_app_bar_settings": "설정",
|
||||
"settings_require_restart": "설정을 적용하려면 Immich를 다시 시작하세요.",
|
||||
"setting_video_viewer_looping_subtitle": "상세 뷰어에서 비디오를 자동으로 반복하도록 활성화합니다.",
|
||||
"setting_video_viewer_looping_title": "반복",
|
||||
"setting_video_viewer_title": "비디오",
|
||||
"share_add": "추가",
|
||||
"share_add_photos": "사진 추가",
|
||||
"share_add_title": "새 앨범제목",
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
"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 backed up",
|
||||
"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",
|
||||
@@ -391,7 +391,6 @@
|
||||
"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_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"action_common_back": "Atpakaļ",
|
||||
"action_common_cancel": "Atcelt",
|
||||
"action_common_clear": "Notīrīt",
|
||||
"action_common_confirm": "Apstiprināt",
|
||||
"action_common_update": "Atjaunināt",
|
||||
"action_common_back": "Back",
|
||||
"action_common_cancel": "Cancel",
|
||||
"action_common_clear": "Clear",
|
||||
"action_common_confirm": "Confirm",
|
||||
"action_common_update": "Update",
|
||||
"add_to_album_bottom_sheet_added": "Pievienots {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Jau pievienots {album}",
|
||||
"advanced_settings_log_level_title": "Žurnalēšanas līmenis: {}",
|
||||
"advanced_settings_log_level_title": "Log level: {}",
|
||||
"advanced_settings_prefer_remote_subtitle": "Dažās ierīcēs sīktēli no ierīcē esošajiem resursiem tiek ielādēti ļoti lēni. Aktivizējiet šo iestatījumu, lai tā vietā ielādētu attālus attēlus.",
|
||||
"advanced_settings_prefer_remote_title": "Dot priekšroku attāliem attēliem",
|
||||
"advanced_settings_self_signed_ssl_subtitle": "Izlaiž servera galapunkta SSL sertifikātu verifikāciju. Nepieciešams pašparakstītajiem sertifikātiem.",
|
||||
"advanced_settings_self_signed_ssl_title": "Atļaut pašparakstītus SSL sertifikātus",
|
||||
"advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.",
|
||||
"advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates",
|
||||
"advanced_settings_tile_subtitle": "Lietotāja papildu iestatījumi",
|
||||
"advanced_settings_tile_title": "Papildu",
|
||||
"advanced_settings_troubleshooting_subtitle": "Iespējot papildu aktīvus problēmu novēršanai",
|
||||
@@ -22,7 +22,7 @@
|
||||
"album_thumbnail_card_shared": "· Koplietots",
|
||||
"album_thumbnail_owned": "Īpašumā",
|
||||
"album_thumbnail_shared_by": "Kopīgoja {}",
|
||||
"album_viewer_appbar_delete_confirm": "Vai tiešām vēlaties dzēst šo albumu no sava konta?",
|
||||
"album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?",
|
||||
"album_viewer_appbar_share_delete": "Dzēst albumu",
|
||||
"album_viewer_appbar_share_err_delete": "Neizdevās izdzēst albumu",
|
||||
"album_viewer_appbar_share_err_leave": "Neizdevās pamest albumu",
|
||||
@@ -30,27 +30,27 @@
|
||||
"album_viewer_appbar_share_err_title": "Neizdevās mainīt albuma nosaukumu",
|
||||
"album_viewer_appbar_share_leave": "Pamest albumu",
|
||||
"album_viewer_appbar_share_remove": "Noņemt no albuma",
|
||||
"album_viewer_appbar_share_to": "Kopīgot Uz",
|
||||
"album_viewer_appbar_share_to": "Share To",
|
||||
"album_viewer_page_share_add_users": "Pievienot lietotājus",
|
||||
"all_people_page_title": "Cilvēki",
|
||||
"all_videos_page_title": "Videoklipi",
|
||||
"app_bar_signout_dialog_content": "Vai tiešām vēlaties izrakstīties?",
|
||||
"app_bar_signout_dialog_ok": "Jā",
|
||||
"app_bar_signout_dialog_title": "Izrakstīties",
|
||||
"app_bar_signout_dialog_content": "Are you sure you want to sign out?",
|
||||
"app_bar_signout_dialog_ok": "Yes",
|
||||
"app_bar_signout_dialog_title": "Sign out",
|
||||
"archive_page_no_archived_assets": "Nav atrasts neviens arhivēts aktīvs",
|
||||
"archive_page_title": "Arhīvs ({})",
|
||||
"asset_action_delete_err_read_only": "Nevar dzēst read only aktīvu(-s), notiek izlaišana",
|
||||
"asset_action_share_err_offline": "Nevar iegūt bezsaistes aktīvu(-s), notiek izlaišana",
|
||||
"asset_list_group_by_sub_title": "Grupēt pēc",
|
||||
"asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping",
|
||||
"asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping",
|
||||
"asset_list_group_by_sub_title": "Group by",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dinamiskais izkārtojums",
|
||||
"asset_list_layout_settings_group_automatically": "Automātiski",
|
||||
"asset_list_layout_settings_group_by": "Grupēt aktīvus pēc",
|
||||
"asset_list_layout_settings_group_by_month": "Mēnesis",
|
||||
"asset_list_layout_settings_group_by_month_day": "Mēnesis + diena",
|
||||
"asset_list_layout_sub_title": "Izvietojums",
|
||||
"asset_list_layout_sub_title": "Layout",
|
||||
"asset_list_settings_subtitle": "Fotorežģa izkārtojuma iestatījumi",
|
||||
"asset_list_settings_title": "Fotorežģis",
|
||||
"asset_viewer_settings_title": "Aktīvu Skatītājs",
|
||||
"asset_viewer_settings_title": "Asset Viewer",
|
||||
"backup_album_selection_page_albums_device": "Albumi ierīcē ({})",
|
||||
"backup_album_selection_page_albums_tap": "Pieskarieties, lai iekļautu, veiciet dubultskārienu, lai izslēgtu",
|
||||
"backup_album_selection_page_assets_scatter": "Aktīvi var būt izmētāti pa vairākiem albumiem. Tādējādi dublēšanas procesā albumus var iekļaut vai neiekļaut.",
|
||||
@@ -110,18 +110,18 @@
|
||||
"backup_controller_page_uploading_file_info": "Faila informācijas augšupielāde",
|
||||
"backup_err_only_album": "Nevar noņemt vienīgo albumu",
|
||||
"backup_info_card_assets": "aktīvi",
|
||||
"backup_manual_cancelled": "Atcelts",
|
||||
"backup_manual_cancelled": "Cancelled",
|
||||
"backup_manual_failed": "Neizdevās",
|
||||
"backup_manual_in_progress": "Augšupielāde jau notiek. Mēģiniet pēc kāda laika atkārtoti",
|
||||
"backup_manual_success": "Veiksmīgi",
|
||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
||||
"backup_manual_success": "Success",
|
||||
"backup_manual_title": "Augšupielādes statuss",
|
||||
"backup_options_page_title": "Dublēšanas iestatījumi",
|
||||
"backup_options_page_title": "Backup options",
|
||||
"cache_settings_album_thumbnails": "Bibliotēkas lapu sīktēli ({} aktīvi)",
|
||||
"cache_settings_clear_cache_button": "Iztīrīt kešatmiņu",
|
||||
"cache_settings_clear_cache_button_title": "Iztīra aplikācijas kešatmiņu. Tas būtiski ietekmēs lietotnes veiktspēju, līdz kešatmiņa būs pārbūvēta.",
|
||||
"cache_settings_duplicated_assets_clear_button": "NOTĪRĪT",
|
||||
"cache_settings_duplicated_assets_subtitle": "Fotoattēli un videoklipi, kurus lietotne ir iekļāvusi melnajā sarakstā",
|
||||
"cache_settings_duplicated_assets_title": "Dublicētie Aktīvi ({})",
|
||||
"cache_settings_duplicated_assets_clear_button": "CLEAR",
|
||||
"cache_settings_duplicated_assets_subtitle": "Photos and videos that are black listed by the app",
|
||||
"cache_settings_duplicated_assets_title": "Duplicated Assets ({})",
|
||||
"cache_settings_image_cache_size": "Attēlu kešatmiņas lielums ({} aktīvi)",
|
||||
"cache_settings_statistics_album": "Bibliotēkas sīktēli",
|
||||
"cache_settings_statistics_assets": "{} aktīvi ({})",
|
||||
@@ -131,8 +131,8 @@
|
||||
"cache_settings_statistics_title": "Kešatmiņas lietojums",
|
||||
"cache_settings_subtitle": "Kontrolēt Immich mobilās lietotnes kešdarbi",
|
||||
"cache_settings_thumbnail_size": "Sīktēlu keša lielums ({} aktīvi)",
|
||||
"cache_settings_tile_subtitle": "Kontrolēt lokālās krātuves uzvedību",
|
||||
"cache_settings_tile_title": "Lokālā Krātuve",
|
||||
"cache_settings_tile_subtitle": "Control the local storage behaviour",
|
||||
"cache_settings_tile_title": "Local Storage",
|
||||
"cache_settings_title": "Kešdarbes iestatījumi",
|
||||
"change_password_form_confirm_password": "Apstiprināt Paroli",
|
||||
"change_password_form_description": "Sveiki {name},\n\nŠī ir pirmā reize, kad pierakstāties sistēmā, vai arī ir iesniegts pieprasījums mainīt paroli. Lūdzu, zemāk ievadiet jauno paroli.",
|
||||
@@ -150,18 +150,18 @@
|
||||
"control_bottom_app_bar_archive": "Arhīvs",
|
||||
"control_bottom_app_bar_create_new_album": "Izveidot jaunu albumu",
|
||||
"control_bottom_app_bar_delete": "Dzēst",
|
||||
"control_bottom_app_bar_delete_from_immich": "Dzēst no Immich",
|
||||
"control_bottom_app_bar_delete_from_local": "Dzēst no ierīces",
|
||||
"control_bottom_app_bar_edit_location": "Rediģēt Atrašanās Vietu",
|
||||
"control_bottom_app_bar_edit_time": "Rediģēt Datumu un Laiku",
|
||||
"control_bottom_app_bar_delete_from_immich": "Delete from Immich",
|
||||
"control_bottom_app_bar_delete_from_local": "Delete from device",
|
||||
"control_bottom_app_bar_edit_location": "Edit Location",
|
||||
"control_bottom_app_bar_edit_time": "Edit Date & Time",
|
||||
"control_bottom_app_bar_favorite": "Izlase",
|
||||
"control_bottom_app_bar_share": "Kopīgot",
|
||||
"control_bottom_app_bar_share_to": "Kopīgot Uz",
|
||||
"control_bottom_app_bar_stack": "Steks",
|
||||
"control_bottom_app_bar_trash_from_immich": "Pārvietot uz Atkritni",
|
||||
"control_bottom_app_bar_share_to": "Share To",
|
||||
"control_bottom_app_bar_stack": "Stack",
|
||||
"control_bottom_app_bar_trash_from_immich": "Move to Trash",
|
||||
"control_bottom_app_bar_unarchive": "Atarhivēt",
|
||||
"control_bottom_app_bar_unfavorite": "Noņemt no Izlases",
|
||||
"control_bottom_app_bar_upload": "Augšupielādēt",
|
||||
"control_bottom_app_bar_unfavorite": "Unfavorite",
|
||||
"control_bottom_app_bar_upload": "Upload",
|
||||
"create_album_page_untitled": "Bez nosaukuma",
|
||||
"create_shared_album_page_create": "Izveidot",
|
||||
"create_shared_album_page_share": "Kopīgot",
|
||||
@@ -173,76 +173,76 @@
|
||||
"daily_title_text_date_year": "E, MMM dd, gggg",
|
||||
"date_format": "E, LLL d, g • h:mm a",
|
||||
"delete_dialog_alert": "Šie vienumi tiks neatgriezeniski dzēsti no Immich un jūsu ierīces",
|
||||
"delete_dialog_alert_local": "Šie vienumi tiks neatgriezeniski dzēsti no jūsu ierīces, bet joprojām būs pieejami Immich serverī.",
|
||||
"delete_dialog_alert_local_non_backed_up": "Daži no šiem elementiem netiek dublēti Immich un tiks neatgriezeniski dzēsti no jūsu ierīces.",
|
||||
"delete_dialog_alert_remote": "Šie vienumi tiks neatgriezeniski dzēsti no Immich servera.",
|
||||
"delete_dialog_alert_local": "These items will be permanently removed from your device but still be available on the Immich server",
|
||||
"delete_dialog_alert_local_non_backed_up": "Some of the items aren't backed up to Immich and will be permanently removed from your device",
|
||||
"delete_dialog_alert_remote": "These items will be permanently deleted from the Immich server",
|
||||
"delete_dialog_cancel": "Atcelt",
|
||||
"delete_dialog_ok": "Dzēst",
|
||||
"delete_dialog_ok_force": "Tā pat dzēst",
|
||||
"delete_dialog_ok_force": "Delete Anyway",
|
||||
"delete_dialog_title": "Neatgriezeniski Dzēst",
|
||||
"delete_local_dialog_ok_backed_up_only": "Dzēst tikai Dublētos",
|
||||
"delete_local_dialog_ok_force": "Tā pat dzēst",
|
||||
"delete_shared_link_dialog_content": "Vai tiešām vēlaties dzēst šo kopīgošanas saiti?",
|
||||
"delete_shared_link_dialog_title": "Dzēst Kopīgošanas saiti",
|
||||
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only",
|
||||
"delete_local_dialog_ok_force": "Delete Anyway",
|
||||
"delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?",
|
||||
"delete_shared_link_dialog_title": "Delete Shared Link",
|
||||
"description_input_hint_text": "Pievienot aprakstu...",
|
||||
"description_input_submit_error": "Atjauninot aprakstu, radās kļūda; papildinformāciju skatiet žurnālā",
|
||||
"edit_date_time_dialog_date_time": "Datums un Laiks",
|
||||
"edit_date_time_dialog_timezone": "Laika zona",
|
||||
"edit_location_dialog_title": "Atrašanās vieta",
|
||||
"edit_date_time_dialog_date_time": "Date and Time",
|
||||
"edit_date_time_dialog_timezone": "Timezone",
|
||||
"edit_location_dialog_title": "Location",
|
||||
"exif_bottom_sheet_description": "Pievienot Aprakstu...",
|
||||
"exif_bottom_sheet_details": "INFORMĀCIJA",
|
||||
"exif_bottom_sheet_location": "ATRAŠANĀS VIETA",
|
||||
"exif_bottom_sheet_location_add": "Pievienot atrašanās vietu",
|
||||
"exif_bottom_sheet_people": "CILVĒKI",
|
||||
"exif_bottom_sheet_person_add_person": "Pievienot vārdu",
|
||||
"exif_bottom_sheet_location_add": "Add a location",
|
||||
"exif_bottom_sheet_people": "PEOPLE",
|
||||
"exif_bottom_sheet_person_add_person": "Add name",
|
||||
"experimental_settings_new_asset_list_subtitle": "Izstrādes posmā",
|
||||
"experimental_settings_new_asset_list_title": "Iespējot eksperimentālo fotorežģi",
|
||||
"experimental_settings_subtitle": "Izmanto uzņemoties risku!",
|
||||
"experimental_settings_title": "Eksperimentāls",
|
||||
"favorites_page_no_favorites": "Nav atrasti iecienītākie aktīvi",
|
||||
"favorites_page_title": "Izlase",
|
||||
"haptic_feedback_switch": "Iestatīt haptisku reakciju",
|
||||
"haptic_feedback_title": "Haptiska Reakcija",
|
||||
"haptic_feedback_switch": "Enable haptic feedback",
|
||||
"haptic_feedback_title": "Haptic Feedback",
|
||||
"home_page_add_to_album_conflicts": "Pievienoja {added} aktīvus albumam {album}. {failed} aktīvi jau ir albumā.",
|
||||
"home_page_add_to_album_err_local": "Albumiem vēl nevar pievienot lokālos aktīvus, notiek izlaišana",
|
||||
"home_page_add_to_album_success": "Pievienoja {added} aktīvus albumam {album}.",
|
||||
"home_page_album_err_partner": "Pagaidām nevar pievienot partnera aktīvus albumam, notiek izlaišana",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Vēl nevar arhivēt lokālos aktīvus, notiek izlaišana",
|
||||
"home_page_archive_err_partner": "Nevarēja arhivēt partnera aktīvus, notiek izlaišana",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Tiek izveidota laika skala",
|
||||
"home_page_delete_err_partner": "Nevarēja dzēst partnera aktīvus, notiek izlaišana",
|
||||
"home_page_delete_remote_err_local": "Lokālie aktīvi dzēšanai attālinātajā izvēlē, tiek izlaists",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping",
|
||||
"home_page_favorite_err_local": "Vēl nevar pievienot izlaisei vietējos aktīvus, notiek izlaišana",
|
||||
"home_page_favorite_err_partner": "Pagaidām nevar ievietot izlasē partnera aktīvus, notiek izlaišana",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Ja šī ir pirmā reize, kad izmantojat aplikāciju, lūdzu, izvēlieties dublējuma albumu(s), lai laika skala varētu aizpildīt fotoattēlus un videoklipus albumā(os).",
|
||||
"home_page_share_err_local": "Caur saiti nevarēja kopīgot lokālos aktīvus, notiek izlaišana",
|
||||
"home_page_upload_err_limit": "Vienlaikus var augšupielādēt ne vairāk kā 30 aktīvus, notiek izlaišana",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Lejupielādes Kļūda",
|
||||
"image_viewer_page_state_provider_download_started": "Lejupielāde Uzsākta",
|
||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "Lejupielāde Izdevās",
|
||||
"image_viewer_page_state_provider_share_error": "Kopīgošanas Kļūda",
|
||||
"image_viewer_page_state_provider_share_error": "Share Error",
|
||||
"library_page_albums": "Albums",
|
||||
"library_page_archive": "Arhīvs",
|
||||
"library_page_device_albums": "Albumi ierīcē",
|
||||
"library_page_favorites": "Izlase",
|
||||
"library_page_new_album": "Jauns albums",
|
||||
"library_page_sharing": "Kopīgošana",
|
||||
"library_page_sort_asset_count": "Daudzums ar aktīviem",
|
||||
"library_page_sort_asset_count": "Number of assets",
|
||||
"library_page_sort_created": "Jaunākais izveidotais",
|
||||
"library_page_sort_last_modified": "Pēdējo reizi modificēts",
|
||||
"library_page_sort_most_oldest_photo": "Vecākais fotoattēls",
|
||||
"library_page_sort_most_recent_photo": "Jaunākais fotoattēls",
|
||||
"library_page_sort_last_modified": "Last modified",
|
||||
"library_page_sort_most_oldest_photo": "Oldest photo",
|
||||
"library_page_sort_most_recent_photo": "Most recent photo",
|
||||
"library_page_sort_title": "Albuma virsraksts",
|
||||
"location_picker_choose_on_map": "Izvēlēties uz kartes",
|
||||
"location_picker_latitude": "Ģeogrāfiskais platums",
|
||||
"location_picker_latitude_error": "Ievadiet korektu ģeogrāfisko platumu",
|
||||
"location_picker_latitude_hint": "Ievadiet savu ģeogrāfisko platumu šeit",
|
||||
"location_picker_longitude": "Ģeogrāfiskais garums",
|
||||
"location_picker_longitude_error": "Ievadiet korektu ģeogrāfisko garumu",
|
||||
"location_picker_longitude_hint": "Ievadiet savu ģeogrāfisko garumu šeit",
|
||||
"login_disabled": "Pieslēgšanās ir atslēgta",
|
||||
"location_picker_choose_on_map": "Choose on map",
|
||||
"location_picker_latitude": "Latitude",
|
||||
"location_picker_latitude_error": "Enter a valid latitude",
|
||||
"location_picker_latitude_hint": "Enter your latitude here",
|
||||
"location_picker_longitude": "Longitude",
|
||||
"location_picker_longitude_error": "Enter a valid longitude",
|
||||
"location_picker_longitude_hint": "Enter your longitude here",
|
||||
"login_disabled": "Login has been disabled",
|
||||
"login_form_api_exception": "API izņēmums. Lūdzu, pārbaudiet servera URL un mēģiniet vēlreiz.",
|
||||
"login_form_back_button_text": "Atpakaļ",
|
||||
"login_form_back_button_text": "Back",
|
||||
"login_form_button_text": "Pieteikties",
|
||||
"login_form_email_hint": "jūsuepasts@email.com",
|
||||
"login_form_endpoint_hint": "http://jūsu-servera-ip:ports/api",
|
||||
@@ -255,7 +255,7 @@
|
||||
"login_form_failed_get_oauth_server_config": "Pieslēdzoties, izmantojot OAuth, radās kļūda; pārbaudiet servera URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth līdzeklis šajā serverī nav pieejams",
|
||||
"login_form_failed_login": "Radās kļūda, piesakoties, pārbaudiet servera URL, e-pastu un paroli",
|
||||
"login_form_handshake_exception": "Ar serveri tika konstatēta Handshake Exception kļūda. Ja izmantojat pašparakstītu sertifikātu, tad iestatījumos iespējojiet pašparakstītu sertifikātu atbalstu.",
|
||||
"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_label_email": "E-pasts",
|
||||
"login_form_label_password": "Parole",
|
||||
"login_form_next_button": "Nākošais",
|
||||
@@ -263,51 +263,51 @@
|
||||
"login_form_save_login": "Palikt pieteiktam",
|
||||
"login_form_server_empty": "Ieraksties servera URL.",
|
||||
"login_form_server_error": "Nevarēja izveidot savienojumu ar serveri.",
|
||||
"login_password_changed_error": "Atjaunojot paroli radās kļūda",
|
||||
"login_password_changed_success": "Parole veiksmīgi atjaunota",
|
||||
"map_assets_in_bound": "{} fotoattēls",
|
||||
"map_assets_in_bounds": "{} fotoattēli",
|
||||
"map_cannot_get_user_location": "Nevar iegūt lietotāja atrašanās vietu",
|
||||
"map_location_dialog_cancel": "Atcelt",
|
||||
"map_location_dialog_yes": "Jā",
|
||||
"map_location_picker_page_use_location": "Izvēlēties šo atrašanās vietu",
|
||||
"map_location_service_disabled_content": "Lai tiktu rādīti jūsu pašreizējās atrašanās vietas aktīvi, ir jāaktivizē atrašanās vietas pakalpojums. Vai vēlaties to iespējot tagad?",
|
||||
"map_location_service_disabled_title": "Atrašanās vietas Pakalpojums atslēgts",
|
||||
"map_no_assets_in_bounds": "Šajā lokācijā nav neviena fotoattēla",
|
||||
"map_no_location_permission_content": "Atrašanās vietas atļauja ir nepieciešama, lai parādītu jūsu pašreizējās atrašanās vietas aktīvus. Vai vēlaties to atļaut tagad?",
|
||||
"map_no_location_permission_title": "Atrašanās vietas Atļaujas liegtas",
|
||||
"map_settings_dark_mode": "Tumšais režīms",
|
||||
"map_settings_date_range_option_all": "Viss",
|
||||
"map_settings_date_range_option_day": "Pēdējās 24 stundas",
|
||||
"map_settings_date_range_option_days": "Pēdējās {} dienas",
|
||||
"map_settings_date_range_option_year": "Pēdējo gadu",
|
||||
"map_settings_date_range_option_years": "Pēdējos {} gadus",
|
||||
"map_settings_dialog_cancel": "Atcelt",
|
||||
"map_settings_dialog_save": "Saglabāt",
|
||||
"map_settings_dialog_title": "Kartes Iestatījumi",
|
||||
"map_settings_include_show_archived": "Iekļaut Arhivētos",
|
||||
"map_settings_include_show_partners": "Iekļaut Partnerus",
|
||||
"map_settings_only_relative_range": "Datumu diapazons",
|
||||
"map_settings_only_show_favorites": "Rādīt tikai Izlasi",
|
||||
"map_settings_theme_settings": "Kartes Dizains",
|
||||
"map_zoom_to_see_photos": "Attāliniet, lai redzētu fotoattēlus",
|
||||
"memories_all_caught_up": "Šobrīd, tas arī viss",
|
||||
"memories_check_back_tomorrow": "Priekš vairāk atmiņām atgriezieties rītdien.",
|
||||
"memories_start_over": "Sākt no jauna",
|
||||
"memories_swipe_to_close": "Pavelciet uz augšu, lai aizvērtu",
|
||||
"login_password_changed_error": "There was an error updating your password",
|
||||
"login_password_changed_success": "Password updated successfully",
|
||||
"map_assets_in_bound": "{} photo",
|
||||
"map_assets_in_bounds": "{} photos",
|
||||
"map_cannot_get_user_location": "Cannot get user's location",
|
||||
"map_location_dialog_cancel": "Cancel",
|
||||
"map_location_dialog_yes": "Yes",
|
||||
"map_location_picker_page_use_location": "Use this location",
|
||||
"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_location_service_disabled_title": "Location Service disabled",
|
||||
"map_no_assets_in_bounds": "No photos in this area",
|
||||
"map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?",
|
||||
"map_no_location_permission_title": "Location Permission denied",
|
||||
"map_settings_dark_mode": "Dark mode",
|
||||
"map_settings_date_range_option_all": "All",
|
||||
"map_settings_date_range_option_day": "Past 24 hours",
|
||||
"map_settings_date_range_option_days": "Past {} days",
|
||||
"map_settings_date_range_option_year": "Past year",
|
||||
"map_settings_date_range_option_years": "Past {} years",
|
||||
"map_settings_dialog_cancel": "Cancel",
|
||||
"map_settings_dialog_save": "Save",
|
||||
"map_settings_dialog_title": "Map Settings",
|
||||
"map_settings_include_show_archived": "Include Archived",
|
||||
"map_settings_include_show_partners": "Include Partners",
|
||||
"map_settings_only_relative_range": "Date range",
|
||||
"map_settings_only_show_favorites": "Show Favorite Only",
|
||||
"map_settings_theme_settings": "Map Theme",
|
||||
"map_zoom_to_see_photos": "Zoom out to see photos",
|
||||
"memories_all_caught_up": "All caught up",
|
||||
"memories_check_back_tomorrow": "Check back tomorrow for more memories",
|
||||
"memories_start_over": "Start Over",
|
||||
"memories_swipe_to_close": "Swipe up to close",
|
||||
"monthly_title_text_date_format": "MMMM g",
|
||||
"motion_photos_page_title": "Kustību Fotoattēli",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "Nevar rediģēt read only aktīva(-u) datumu, notiek izlaišana",
|
||||
"multiselect_grid_edit_gps_err_read_only": "Nevar rediģēt atrašanās vietu read only aktīva(-u) datumu, notiek izlaišana",
|
||||
"no_assets_to_show": "Nav uzrādāmo aktīvu",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping",
|
||||
"multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping",
|
||||
"no_assets_to_show": "No assets to show",
|
||||
"notification_permission_dialog_cancel": "Atcelt",
|
||||
"notification_permission_dialog_content": "Lai iespējotu paziņojumus, atveriet Iestatījumi un atlasiet Atļaut.",
|
||||
"notification_permission_dialog_settings": "Iestatījumi",
|
||||
"notification_permission_list_tile_content": "Piešķirt atļauju, lai iespējotu paziņojumus.",
|
||||
"notification_permission_list_tile_enable_button": "Iespējot Paziņojumus",
|
||||
"notification_permission_list_tile_title": "Paziņojumu Atļaujas",
|
||||
"partner_list_user_photos": "{user} fotoattēli",
|
||||
"partner_list_view_all": "Apskatīt visu",
|
||||
"partner_list_user_photos": "{user}'s photos",
|
||||
"partner_list_view_all": "View all",
|
||||
"partner_page_add_partner": "Pievienot partneri",
|
||||
"partner_page_empty_message": "Jūsu fotogrāfijas pagaidām nav kopīgotas ar nevienu partneri.",
|
||||
"partner_page_no_more_users": "Nav vairs lietotāju, kurus var pievienot",
|
||||
@@ -317,7 +317,7 @@
|
||||
"partner_page_stop_sharing_content": "{} vairs nevarēs piekļūt jūsu fotoattēliem.",
|
||||
"partner_page_stop_sharing_title": "Beigt kopīgot jūsu fotogrāfijas?",
|
||||
"partner_page_title": "Partneris",
|
||||
"permission_onboarding_back": "Atpakaļ",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Tomēr turpināt",
|
||||
"permission_onboarding_get_started": "Darba sākšana",
|
||||
"permission_onboarding_go_to_settings": "Doties uz iestatījumiem",
|
||||
@@ -327,46 +327,46 @@
|
||||
"permission_onboarding_permission_granted": "Atļauja piešķirta! Jūs esat gatavi darbam.",
|
||||
"permission_onboarding_permission_limited": "Atļauja ierobežota. Lai atļautu Immich dublēšanu un varētu pārvaldīt visu galeriju kolekciju, sadaļā Iestatījumi piešķiriet fotoattēlu un video atļaujas.",
|
||||
"permission_onboarding_request": "Immich nepieciešama atļauja skatīt jūsu fotoattēlus un videoklipus.",
|
||||
"preferences_settings_title": "Iestatījumi",
|
||||
"preferences_settings_title": "Preferences",
|
||||
"profile_drawer_app_logs": "Žurnāli",
|
||||
"profile_drawer_client_out_of_date_major": "Mobilā Aplikācija ir novecojusi. Lūdzu atjaunojiet to uz jaunāko lielo versiju",
|
||||
"profile_drawer_client_out_of_date_minor": "Mobilā Aplikācija ir novecojusi. Lūdzu atjaunojiet to uz jaunāko mazo versiju",
|
||||
"profile_drawer_client_out_of_date_major": "Mobile App is out of date. Please update to the latest major version.",
|
||||
"profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.",
|
||||
"profile_drawer_client_server_up_to_date": "Klients un serveris ir atjaunināti",
|
||||
"profile_drawer_documentation": "Dokumentācija",
|
||||
"profile_drawer_documentation": "Documentation",
|
||||
"profile_drawer_github": "GitHub",
|
||||
"profile_drawer_server_out_of_date_major": "Serveris ir novecojis. Lūdzu atjaunojiet to uz jaunāko lielo versiju",
|
||||
"profile_drawer_server_out_of_date_minor": "Serveris ir novecojis. Lūdzu atjaunojiet to uz jaunāko mazo versiju",
|
||||
"profile_drawer_server_out_of_date_major": "Server is out of date. Please update to the latest major version.",
|
||||
"profile_drawer_server_out_of_date_minor": "Server is out of date. Please update to the latest minor version.",
|
||||
"profile_drawer_settings": "Iestatījumi",
|
||||
"profile_drawer_sign_out": "Izrakstīties",
|
||||
"profile_drawer_trash": "Atkritne",
|
||||
"profile_drawer_trash": "Trash",
|
||||
"recently_added_page_title": "Nesen Pievienotais",
|
||||
"scaffold_body_error_occurred": "Radās kļūda",
|
||||
"scaffold_body_error_occurred": "Error occurred",
|
||||
"search_bar_hint": "Meklēt Jūsu fotoattēlus",
|
||||
"search_filter_apply": "Lietot filtru",
|
||||
"search_filter_camera_make": "Firma",
|
||||
"search_filter_camera_model": "Modelis",
|
||||
"search_filter_display_option_archive": "Arhīvs",
|
||||
"search_filter_display_option_favorite": "Izlase",
|
||||
"search_filter_display_option_not_in_album": "Nav albumā",
|
||||
"search_filter_location_city": "Pilsēta",
|
||||
"search_filter_location_country": "Valsts",
|
||||
"search_filter_location_state": "Štats",
|
||||
"search_filter_media_type_all": "Viss",
|
||||
"search_filter_media_type_image": "Attēls",
|
||||
"search_filter_media_type_video": "Videoklips",
|
||||
"search_filter_apply": "Apply filter",
|
||||
"search_filter_camera_make": "Make",
|
||||
"search_filter_camera_model": "Model",
|
||||
"search_filter_display_option_archive": "Archive",
|
||||
"search_filter_display_option_favorite": "Favorite",
|
||||
"search_filter_display_option_not_in_album": "Not in album",
|
||||
"search_filter_location_city": "City",
|
||||
"search_filter_location_country": "Country",
|
||||
"search_filter_location_state": "State",
|
||||
"search_filter_media_type_all": "All",
|
||||
"search_filter_media_type_image": "Image",
|
||||
"search_filter_media_type_video": "Video",
|
||||
"search_page_categories": "Kategorijas",
|
||||
"search_page_favorites": "Izlase",
|
||||
"search_page_motion_photos": "Kustību Fotoattēli",
|
||||
"search_page_no_objects": "Informācija par Objektiem nav pieejama",
|
||||
"search_page_no_places": "Nav pieejama Informācija par Vietām",
|
||||
"search_page_people": "Cilvēki",
|
||||
"search_page_person_add_name_dialog_cancel": "Atcelt",
|
||||
"search_page_person_add_name_dialog_hint": "Vārds",
|
||||
"search_page_person_add_name_dialog_save": "Saglabāt",
|
||||
"search_page_person_add_name_dialog_title": "Pievienot vārdu",
|
||||
"search_page_person_add_name_subtitle": "Atrast viņus ātri pēc vārdiem izmantojot meklēšanu",
|
||||
"search_page_person_add_name_title": "Pievienot vārdu",
|
||||
"search_page_person_edit_name": "Rediģēt vārdu",
|
||||
"search_page_person_add_name_dialog_cancel": "Cancel",
|
||||
"search_page_person_add_name_dialog_hint": "Name",
|
||||
"search_page_person_add_name_dialog_save": "Save",
|
||||
"search_page_person_add_name_dialog_title": "Add a name",
|
||||
"search_page_person_add_name_subtitle": "Find them fast by name with search",
|
||||
"search_page_person_add_name_title": "Add a name",
|
||||
"search_page_person_edit_name": "Edit name",
|
||||
"search_page_places": "Vietas",
|
||||
"search_page_recently_added": "Nesen Pievienotais",
|
||||
"search_page_screenshots": "Ekrānuzņēmumi",
|
||||
@@ -375,7 +375,7 @@
|
||||
"search_page_videos": "Videoklipi",
|
||||
"search_page_view_all_button": "Apskatīt visu",
|
||||
"search_page_your_activity": "Jūsu aktivitāte",
|
||||
"search_page_your_map": "Jūsu Karte",
|
||||
"search_page_your_map": "Your Map",
|
||||
"search_result_page_new_search_hint": "Jauns Meklējums",
|
||||
"search_suggestion_list_smart_search_hint_1": "Viedā meklēšana ir iespējota pēc noklusējuma, lai meklētu metadatus, izmantojiet sintaksi",
|
||||
"search_suggestion_list_smart_search_hint_2": "m:jūsu-meklēšanas-frāze",
|
||||
@@ -383,17 +383,16 @@
|
||||
"select_user_for_sharing_page_err_album": "Neizdevās izveidot albumu",
|
||||
"select_user_for_sharing_page_share_suggestions": "Ieteikumi",
|
||||
"server_info_box_app_version": "Aplikācijas Versija",
|
||||
"server_info_box_latest_release": "Jaunākā Versija",
|
||||
"server_info_box_server_url": "Servera URL",
|
||||
"server_info_box_latest_release": "Latest Version",
|
||||
"server_info_box_server_url": "Server URL",
|
||||
"server_info_box_server_version": "Servera Versija",
|
||||
"setting_image_viewer_help": "Detaļu skatītājs vispirms ielādē mazo sīktēlu, pēc tam ielādē vidēja lieluma priekšskatījumu (ja iespējots), visbeidzot ielādē oriģinālu (ja iespējots).",
|
||||
"setting_image_viewer_original_subtitle": "Iespējojiet sākotnējā pilnas izšķirtspējas attēla (liels!) ielādi. Atspējot lai samazinātu datu lietojumu (gan tīklā, gan ierīces kešatmiņā).",
|
||||
"setting_image_viewer_original_title": "Ielādēt oriģinālo attēlu",
|
||||
"setting_image_viewer_preview_subtitle": "Iespējojiet vidējas izšķirtspējas attēla ielādēšanu. Atspējojiet vai nu tiešu oriģināla ielādi, vai izmantojiet tikai sīktēlu.",
|
||||
"setting_image_viewer_preview_title": "Ielādēt priekšskatījuma attēlu",
|
||||
"setting_image_viewer_title": "Attēli",
|
||||
"setting_languages_apply": "Lietot",
|
||||
"setting_languages_title": "Valodas",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Paziņot par fona dublēšanas kļūmēm: {}",
|
||||
"setting_notifications_notify_hours": "{} stundas",
|
||||
"setting_notifications_notify_immediately": "nekavējoties",
|
||||
@@ -408,78 +407,75 @@
|
||||
"setting_notifications_total_progress_title": "Rādīt fona dublējuma kopējo progresu",
|
||||
"setting_pages_app_bar_settings": "Iestatījumi",
|
||||
"settings_require_restart": "Lūdzu, restartējiet Immich, lai lietotu šo iestatījumu",
|
||||
"setting_video_viewer_looping_subtitle": "Iespējot, lai automātiski videoklips tiktu cikliski palaists detaļu skatītājā.",
|
||||
"setting_video_viewer_looping_title": "Cikliski",
|
||||
"setting_video_viewer_title": "Videoklipi",
|
||||
"share_add": "Pievienot",
|
||||
"share_add_photos": "Pievienot fotoattēlus",
|
||||
"share_add_title": "Pievienot virsrakstu",
|
||||
"share_assets_selected": "{} izvēlēti",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "Izveidot albumu",
|
||||
"shared_album_activities_input_disable": "Komentāri atslēgti",
|
||||
"shared_album_activities_input_hint": "Teikt kaut ko",
|
||||
"shared_album_activity_remove_content": "Vai vēlaties šo aktivitāti dzēst?",
|
||||
"shared_album_activity_remove_title": "Dzēst Aktivitāti",
|
||||
"shared_album_activity_setting_subtitle": "Ļaut citiem atbildēt",
|
||||
"shared_album_activity_setting_title": "Komentāri un \"patīk\"",
|
||||
"shared_album_section_people_action_error": "Kļūme pametot/noņemot no albuma",
|
||||
"shared_album_section_people_action_leave": "Noņemt lietotāju no albuma",
|
||||
"shared_album_section_people_action_remove_user": "Noņemt lietotāju no albuma",
|
||||
"shared_album_section_people_owner_label": "Īpašnieks",
|
||||
"shared_album_section_people_title": "CILVĒKI",
|
||||
"shared_album_activities_input_disable": "Comment is disabled",
|
||||
"shared_album_activities_input_hint": "Say something",
|
||||
"shared_album_activity_remove_content": "Do you want to delete this activity?",
|
||||
"shared_album_activity_remove_title": "Delete Activity",
|
||||
"shared_album_activity_setting_subtitle": "Let others respond",
|
||||
"shared_album_activity_setting_title": "Comments & likes",
|
||||
"shared_album_section_people_action_error": "Error leaving/removing from album",
|
||||
"shared_album_section_people_action_leave": "Remove user from album",
|
||||
"shared_album_section_people_action_remove_user": "Remove user from album",
|
||||
"shared_album_section_people_owner_label": "Owner",
|
||||
"shared_album_section_people_title": "PEOPLE",
|
||||
"share_dialog_preparing": "Notiek sagatavošana...",
|
||||
"shared_link_app_bar_title": "Kopīgotas Saites",
|
||||
"shared_link_clipboard_copied_massage": "Ievietots starpliktuvē",
|
||||
"shared_link_clipboard_text": "Saite: {}\nParole: {}",
|
||||
"shared_link_create_app_bar_title": "Izveidot kopīgošanas saiti",
|
||||
"shared_link_create_error": "Kļūda izveidojot kopīgošanas saiti",
|
||||
"shared_link_create_info": "Ļaut jebkuram ar saiti apskatīt izvēlēto(-os) attēlu(-us)",
|
||||
"shared_link_create_submit_button": "Izveidot saiti",
|
||||
"shared_link_edit_allow_download": "Ļaut publiskiem lietotājiem lejupielādēt",
|
||||
"shared_link_edit_allow_upload": "Ļaut publiskiem lietotājiem augšupielādēt",
|
||||
"shared_link_edit_app_bar_title": "Rediģēt saiti",
|
||||
"shared_link_edit_change_expiry": "Izmainīt derīguma termiņu",
|
||||
"shared_link_edit_description": "Apraksts",
|
||||
"shared_link_edit_description_hint": "Ievadiet kopīgojuma aprakstu",
|
||||
"shared_link_edit_expire_after": "Derīguma termiņš beidzas pēc",
|
||||
"shared_link_edit_expire_after_option_day": "1 diena",
|
||||
"shared_link_edit_expire_after_option_days": "{} dienas",
|
||||
"shared_link_edit_expire_after_option_hour": "1 stunda",
|
||||
"shared_link_edit_expire_after_option_hours": "{} stundas",
|
||||
"shared_link_edit_expire_after_option_minute": "1 minūte",
|
||||
"shared_link_edit_expire_after_option_minutes": "{} minūtes",
|
||||
"shared_link_edit_expire_after_option_months": "{} mēneši",
|
||||
"shared_link_edit_expire_after_option_never": "Nekad",
|
||||
"shared_link_edit_expire_after_option_year": "{} gads",
|
||||
"shared_link_edit_password": "Parole",
|
||||
"shared_link_edit_password_hint": "Ierakstīt kopīgojuma paroli",
|
||||
"shared_link_edit_show_meta": "Rādīt metadatus",
|
||||
"shared_link_edit_submit_button": "Atjaunināt saiti",
|
||||
"shared_link_empty": "Jums nav nevienas kopīgotas saites",
|
||||
"shared_link_error_server_url_fetch": "Nevarēja ienest servera URL",
|
||||
"shared_link_expired": "Derīguma termiņš beidzās",
|
||||
"shared_link_expires_day": "Derīguma termiņš beigsies pēc {} dienas",
|
||||
"shared_link_expires_days": "Derīguma termiņš beigsies pēc {} dienām",
|
||||
"shared_link_expires_hour": "Derīguma termiņš beigsies pēc {} stundas",
|
||||
"shared_link_expires_hours": "Derīguma termiņš beigsies pēc {} stundām",
|
||||
"shared_link_expires_minute": "Derīguma termiņš beigsies pēc {} minūtes",
|
||||
"shared_link_expires_minutes": "Derīguma termiņš beidzas pēc {} minūtēm",
|
||||
"shared_link_expires_never": "Derīguma termiņš beigsies ∞",
|
||||
"shared_link_expires_second": "Derīguma termiņš beigsies pēc {} sekundes",
|
||||
"shared_link_expires_seconds": "Derīguma termiņš beidzas pēc {} sekundēm",
|
||||
"shared_link_individual_shared": "Individuāli kopīgots",
|
||||
"shared_link_info_chip_download": "Lejupielādēt",
|
||||
"shared_link_app_bar_title": "Shared Links",
|
||||
"shared_link_clipboard_copied_massage": "Copied to clipboard",
|
||||
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
|
||||
"shared_link_create_app_bar_title": "Create link to share",
|
||||
"shared_link_create_error": "Error while creating shared link",
|
||||
"shared_link_create_info": "Let anyone with the link see the selected photo(s)",
|
||||
"shared_link_create_submit_button": "Create link",
|
||||
"shared_link_edit_allow_download": "Allow public user to download",
|
||||
"shared_link_edit_allow_upload": "Allow public user to upload",
|
||||
"shared_link_edit_app_bar_title": "Edit link",
|
||||
"shared_link_edit_change_expiry": "Change expiration time",
|
||||
"shared_link_edit_description": "Description",
|
||||
"shared_link_edit_description_hint": "Enter the share description",
|
||||
"shared_link_edit_expire_after": "Expire after",
|
||||
"shared_link_edit_expire_after_option_day": "1 day",
|
||||
"shared_link_edit_expire_after_option_days": "{} days",
|
||||
"shared_link_edit_expire_after_option_hour": "1 hour",
|
||||
"shared_link_edit_expire_after_option_hours": "{} hours",
|
||||
"shared_link_edit_expire_after_option_minute": "1 minute",
|
||||
"shared_link_edit_expire_after_option_minutes": "{} minutes",
|
||||
"shared_link_edit_expire_after_option_months": "{} months",
|
||||
"shared_link_edit_expire_after_option_never": "Never",
|
||||
"shared_link_edit_expire_after_option_year": "{} year",
|
||||
"shared_link_edit_password": "Password",
|
||||
"shared_link_edit_password_hint": "Enter the share password",
|
||||
"shared_link_edit_show_meta": "Show metadata",
|
||||
"shared_link_edit_submit_button": "Update link",
|
||||
"shared_link_empty": "You don't have any shared links",
|
||||
"shared_link_error_server_url_fetch": "Cannot fetch the server url",
|
||||
"shared_link_expired": "Expired",
|
||||
"shared_link_expires_day": "Expires in {} day",
|
||||
"shared_link_expires_days": "Expires in {} days",
|
||||
"shared_link_expires_hour": "Expires in {} hour",
|
||||
"shared_link_expires_hours": "Expires in {} hours",
|
||||
"shared_link_expires_minute": "Expires in {} minute",
|
||||
"shared_link_expires_minutes": "Expires in {} minutes",
|
||||
"shared_link_expires_never": "Expires ∞",
|
||||
"shared_link_expires_second": "Expires in {} second",
|
||||
"shared_link_expires_seconds": "Expires in {} seconds",
|
||||
"shared_link_individual_shared": "Individual shared",
|
||||
"shared_link_info_chip_download": "Download",
|
||||
"shared_link_info_chip_metadata": "EXIF",
|
||||
"shared_link_info_chip_upload": "Augšupielādēt",
|
||||
"shared_link_manage_links": "Pārvaldīt Kopīgotās saites",
|
||||
"shared_link_public_album": "Publisks albums",
|
||||
"share_done": "Gatavs",
|
||||
"shared_link_info_chip_upload": "Upload",
|
||||
"shared_link_manage_links": "Manage Shared links",
|
||||
"shared_link_public_album": "Public album",
|
||||
"share_done": "Done",
|
||||
"share_invite": "Uzaicināt albumā",
|
||||
"sharing_page_album": "Kopīgotie albumi",
|
||||
"sharing_page_description": "Izveidojiet koplietojamus albumus, lai kopīgotu fotoattēlus un videoklipus ar Jūsu tīkla lietotājiem.",
|
||||
"sharing_page_empty_list": "TUKŠS SARAKSTS",
|
||||
"sharing_silver_appbar_create_shared_album": "Izveidot kopīgotu albumu",
|
||||
"sharing_silver_appbar_shared_links": "Kopīgotās saites",
|
||||
"sharing_silver_appbar_shared_links": "Shared links",
|
||||
"sharing_silver_appbar_share_partner": "Dalīties ar partneri",
|
||||
"tab_controller_nav_library": "Bibliotēka",
|
||||
"tab_controller_nav_photos": "Fotoattēli",
|
||||
@@ -495,30 +491,30 @@
|
||||
"theme_setting_theme_title": "Dizains",
|
||||
"theme_setting_three_stage_loading_subtitle": "Trīspakāpju ielāde var palielināt ielādēšanas veiktspēju, bet izraisa ievērojami lielāku tīkla noslodzi",
|
||||
"theme_setting_three_stage_loading_title": "Iespējot trīspakāpju ielādi",
|
||||
"translated_text_options": "Iestatījumi",
|
||||
"trash_page_delete": "Dzēst",
|
||||
"trash_page_delete_all": "Dzēst Visu",
|
||||
"trash_page_empty_trash_btn": "Iztukšot atkritni",
|
||||
"trash_page_empty_trash_dialog_content": "Vai vēlaties iztukšot savus izmestos aktīvus? Tie tiks neatgriezeniski izņemti no Immich",
|
||||
"translated_text_options": "Options",
|
||||
"trash_page_delete": "Delete",
|
||||
"trash_page_delete_all": "Delete All",
|
||||
"trash_page_empty_trash_btn": "Empty trash",
|
||||
"trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich",
|
||||
"trash_page_empty_trash_dialog_ok": "Ok",
|
||||
"trash_page_info": "Atkritnes vienumi tiks neatgriezeniski dzēsti pēc {} dienām",
|
||||
"trash_page_no_assets": "Atkritnē nav aktīvu",
|
||||
"trash_page_restore": "Atjaunot",
|
||||
"trash_page_restore_all": "Atjaunot Visu",
|
||||
"trash_page_select_assets_btn": "Atlasīt aktīvus",
|
||||
"trash_page_select_btn": "Atlasīt",
|
||||
"trash_page_title": "Atkritne ({})",
|
||||
"upload_dialog_cancel": "Atcelt",
|
||||
"upload_dialog_info": "Vai vēlaties veikt izvēlētā(-o) aktīva(-u) dublējumu uz servera?",
|
||||
"upload_dialog_ok": "Augšupielādēt",
|
||||
"upload_dialog_title": "Augšupielādēt Aktīvu",
|
||||
"trash_page_info": "Trashed items will be permanently deleted after {} days",
|
||||
"trash_page_no_assets": "No trashed assets",
|
||||
"trash_page_restore": "Restore",
|
||||
"trash_page_restore_all": "Restore All",
|
||||
"trash_page_select_assets_btn": "Select assets",
|
||||
"trash_page_select_btn": "Select",
|
||||
"trash_page_title": "Trash ({})",
|
||||
"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": "Atzīt",
|
||||
"version_announcement_overlay_release_notes": "informācija par laidienu",
|
||||
"version_announcement_overlay_text_1": "Sveiks draugs, ir jauns izlaidums no",
|
||||
"version_announcement_overlay_text_2": "lūdzu, veltiet laiku, lai apmeklētu",
|
||||
"version_announcement_overlay_text_3": " un pārliecinieties, vai docker-compose un .env iestatījumi ir atjaunināti, lai novērstu jebkādas nepareizas konfigurācijas, īpaši, ja izmantojat WatchTower vai mehānismu, kas automātiski veic servera lietojumprogrammas atjaunināšanu.",
|
||||
"version_announcement_overlay_title": "Pieejama jauna servera versija \uD83C\uDF89",
|
||||
"viewer_remove_from_stack": "Noņemt no Steka",
|
||||
"viewer_stack_use_as_main_asset": "Izmantot kā Galveno Aktīvu",
|
||||
"viewer_unstack": "At-Stekot"
|
||||
"viewer_remove_from_stack": "Remove from Stack",
|
||||
"viewer_stack_use_as_main_asset": "Use as Main Asset",
|
||||
"viewer_unstack": "Un-Stack"
|
||||
}
|
||||
@@ -102,7 +102,7 @@
|
||||
"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 backed up",
|
||||
"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",
|
||||
@@ -391,7 +391,6 @@
|
||||
"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_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
"backup_controller_page_background_configure_error": "Konfigurering av bakgrunnstjenesten feilet",
|
||||
"backup_controller_page_background_delay": "Forsink sikkerhetskopiering av nye objekter: {}",
|
||||
"backup_controller_page_background_description": "Skru på bakgrunnstjenesten for å automatisk sikkerhetskopiere alle nye objekter uten å måtte åpne appen",
|
||||
"backup_controller_page_background_is_off": "Automatisk sikkerhetskopiering i bakgrunnen er deaktivert",
|
||||
"backup_controller_page_background_is_off": "Automatisk sikkerhetskopiering i bakgrunnener deaktivert",
|
||||
"backup_controller_page_background_is_on": "Automatisk sikkerhetskopiering i bakgrunnen er aktivert",
|
||||
"backup_controller_page_background_turn_off": "Skru av bakgrunnstjenesten",
|
||||
"backup_controller_page_background_turn_on": "Skru på bakgrunnstjenesten",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Last originalbildet",
|
||||
"setting_image_viewer_preview_subtitle": "Aktiver for å laste et bilde av medium oppløsning. Deaktiver for å enten direkte laste inn originalen eller kun benytte miniatyrbilde.",
|
||||
"setting_image_viewer_preview_title": "Last forhåndsvisningsbilde",
|
||||
"setting_image_viewer_title": "Bilder",
|
||||
"setting_languages_apply": "Bekreft",
|
||||
"setting_languages_title": "Språk",
|
||||
"setting_notifications_notify_failures_grace_period": "Varsle om sikkerhetskopieringsfeil i bakgrunnen: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Vis status på sikkerhetskopiering i bakgrunnen",
|
||||
"setting_pages_app_bar_settings": "Innstillinger",
|
||||
"settings_require_restart": "Vennligst restart Immich for å aktivere denne innstillingen",
|
||||
"setting_video_viewer_looping_subtitle": "Aktiver for å automatisk loope en video i detaljeviseren.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videoer",
|
||||
"share_add": "Legg til",
|
||||
"share_add_photos": "Legg til bilder",
|
||||
"share_add_title": "Legg til tittel",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"advanced_settings_troubleshooting_subtitle": "Schakel extra functies voor probleemoplossing in ",
|
||||
"advanced_settings_troubleshooting_title": "Probleemoplossing",
|
||||
"album_info_card_backup_album_excluded": "UITGESLOTEN",
|
||||
"album_info_card_backup_album_included": "INBEGREPEN",
|
||||
"album_info_card_backup_album_included": "INGESLOTEN",
|
||||
"album_thumbnail_card_item": "1 item",
|
||||
"album_thumbnail_card_items": "{} items",
|
||||
"album_thumbnail_card_shared": " · Gedeeld",
|
||||
@@ -53,7 +53,7 @@
|
||||
"asset_viewer_settings_title": "Foto weergave",
|
||||
"backup_album_selection_page_albums_device": "Albums op apparaat ({})",
|
||||
"backup_album_selection_page_albums_tap": "Tik om in te voegen, dubbel tik om uit te sluiten",
|
||||
"backup_album_selection_page_assets_scatter": "Assets kunnen over verschillende albums verdeeld zijn, dus albums kunnen inbegrepen of uitgesloten zijn van het backup proces.",
|
||||
"backup_album_selection_page_assets_scatter": "Assets kunnen over verschillende albums verdeeld zijn, dus albums kunnen ingesloten of uitgesloten zijn van het backup proces.",
|
||||
"backup_album_selection_page_select_albums": "Albums selecteren",
|
||||
"backup_album_selection_page_selection_info": "Selectie info",
|
||||
"backup_album_selection_page_total_assets": "Totaal unieke assets",
|
||||
@@ -70,7 +70,7 @@
|
||||
"backup_controller_page_background_app_refresh_disabled_title": "Verversen op achtergrond uitgeschakeld",
|
||||
"backup_controller_page_background_app_refresh_enable_button_text": "Ga naar instellingen",
|
||||
"backup_controller_page_background_battery_info_link": "Laat zien hoe",
|
||||
"backup_controller_page_background_battery_info_message": "Voor de beste back-upervaring, schakel je alle batterijoptimalisaties uit omdat deze op-de-achtergrondactiviteiten van Immich beperken.\n\nAangezien dit apparaatspecifiek is, zoek de vereiste informatie op voor de fabrikant van je apparaat.",
|
||||
"backup_controller_page_background_battery_info_message": "Schakel voor de beste back-upervaring op de achtergrond alle batterijoptimalisaties uit, die de achtergrondactiviteit van Immich beperken.\n\nAangezien dit apparaatspecifiek is, zoek de vereiste informatie op voor de fabrikant van je apparaat.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Batterijoptimalisaties",
|
||||
"backup_controller_page_background_charging": "Alleen tijdens opladen",
|
||||
@@ -158,7 +158,7 @@
|
||||
"control_bottom_app_bar_share": "Delen",
|
||||
"control_bottom_app_bar_share_to": "Delen met",
|
||||
"control_bottom_app_bar_stack": "Stapel",
|
||||
"control_bottom_app_bar_trash_from_immich": "Naar prullenbak",
|
||||
"control_bottom_app_bar_trash_from_immich": "Verplaatsen naar prullenbak",
|
||||
"control_bottom_app_bar_unarchive": "Herstellen",
|
||||
"control_bottom_app_bar_unfavorite": "Onfavoriet",
|
||||
"control_bottom_app_bar_upload": "Uploaden",
|
||||
@@ -244,7 +244,7 @@
|
||||
"login_form_api_exception": "API fout. Controleer de server URL en probeer opnieuw.",
|
||||
"login_form_back_button_text": "Terug",
|
||||
"login_form_button_text": "Inloggen",
|
||||
"login_form_email_hint": "jouwemail@email.nl",
|
||||
"login_form_email_hint": "jouwemail@email.com",
|
||||
"login_form_endpoint_hint": "http://jouw-server-ip:poort/api",
|
||||
"login_form_endpoint_url": "Server-URL",
|
||||
"login_form_err_http": "Voer http:// of https:// in",
|
||||
@@ -350,7 +350,7 @@
|
||||
"search_filter_display_option_not_in_album": "Niet in album",
|
||||
"search_filter_location_city": "Stad",
|
||||
"search_filter_location_country": "Land",
|
||||
"search_filter_location_state": "Status",
|
||||
"search_filter_location_state": "Staat",
|
||||
"search_filter_media_type_all": "Alle",
|
||||
"search_filter_media_type_image": "Afbeelding",
|
||||
"search_filter_media_type_video": "Video",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Originele afbeelding laden",
|
||||
"setting_image_viewer_preview_subtitle": "Schakel in om een afbeelding met middelgrote resolutie te laden. Schakel uit om alleen het origineel direct te laden of alleen de thumbnail te gebruiken.",
|
||||
"setting_image_viewer_preview_title": "Voorbeeldafbeelding laden",
|
||||
"setting_image_viewer_title": "Afbeeldingen",
|
||||
"setting_languages_apply": "Toepassen",
|
||||
"setting_languages_title": "Taal",
|
||||
"setting_notifications_notify_failures_grace_period": "Fouten van back-up op de achtergrond melden: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Totale voortgang van achtergrondback-up tonen",
|
||||
"setting_pages_app_bar_settings": "Instellingen",
|
||||
"settings_require_restart": "Start Immich opnieuw op om deze instelling toe te passen",
|
||||
"setting_video_viewer_looping_subtitle": "Inschakelen om video's automatisch te herhalen in de detailweergave.",
|
||||
"setting_video_viewer_looping_title": "Herhalen",
|
||||
"setting_video_viewer_title": "Video's",
|
||||
"share_add": "Toevoegen",
|
||||
"share_add_photos": "Foto's toevoegen",
|
||||
"share_add_title": "Titel toevoegen",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"action_common_back": "Cofnij",
|
||||
"action_common_back": "Back",
|
||||
"action_common_cancel": "Anuluj",
|
||||
"action_common_clear": "Wyrzuść",
|
||||
"action_common_confirm": "Potwierdzać",
|
||||
"action_common_clear": "Clear",
|
||||
"action_common_confirm": "Confirm",
|
||||
"action_common_update": "Aktualizuj",
|
||||
"add_to_album_bottom_sheet_added": "Dodano do {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Już w {album}",
|
||||
@@ -22,7 +22,7 @@
|
||||
"album_thumbnail_card_shared": "Udostępniony",
|
||||
"album_thumbnail_owned": "Posiadany",
|
||||
"album_thumbnail_shared_by": "Udostępnione przez {}",
|
||||
"album_viewer_appbar_delete_confirm": "Czy na pewno chcesz usunąć ten album ze swojego konta?",
|
||||
"album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?",
|
||||
"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",
|
||||
@@ -115,7 +115,7 @@
|
||||
"backup_manual_in_progress": "Przesyłanie już trwa. Spróbuj po pewnym czasie",
|
||||
"backup_manual_success": "Sukces",
|
||||
"backup_manual_title": "Stan przesyłania",
|
||||
"backup_options_page_title": "Opcje kopi zapasowej",
|
||||
"backup_options_page_title": "Backup options",
|
||||
"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.",
|
||||
@@ -218,7 +218,7 @@
|
||||
"home_page_share_err_local": "Nie można udostępniać zasobów lokalnych za pośrednictwem linku, pomijajam",
|
||||
"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_started": "Pobieranie rozpoczęte",
|
||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "Pobieranie zakończone",
|
||||
"image_viewer_page_state_provider_share_error": "Udostępnij błąd",
|
||||
"library_page_albums": "Albumy",
|
||||
@@ -292,22 +292,22 @@
|
||||
"map_settings_theme_settings": "Map Theme",
|
||||
"map_zoom_to_see_photos": "Pomniejsz, aby zobaczyć zdjęcia",
|
||||
"memories_all_caught_up": "All caught up",
|
||||
"memories_check_back_tomorrow": "Wróć jutro po więcej wspomnień",
|
||||
"memories_start_over": "Zacznij od nowa",
|
||||
"memories_swipe_to_close": "Przesuń w górę, aby zamknąć",
|
||||
"memories_check_back_tomorrow": "Check back tomorrow for more memories",
|
||||
"memories_start_over": "Start Over",
|
||||
"memories_swipe_to_close": "Swipe up to close",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"motion_photos_page_title": "Zdjęcia ruchome",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "Nie można edytować daty zasobów tylko do odczytu, pomijanie",
|
||||
"multiselect_grid_edit_gps_err_read_only": "Nie można edytować lokalizacji zasobów tylko do odczytu, pomijanie",
|
||||
"no_assets_to_show": "Brak zasobów do pokazania",
|
||||
"no_assets_to_show": "No assets to show",
|
||||
"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_list_user_photos": "{user} zdjęcia",
|
||||
"partner_list_view_all": "Pokaż wszystkie",
|
||||
"partner_list_user_photos": "{user}'s photos",
|
||||
"partner_list_view_all": "View all",
|
||||
"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",
|
||||
@@ -342,7 +342,7 @@
|
||||
"recently_added_page_title": "Ostatnio Dodane",
|
||||
"scaffold_body_error_occurred": "Wystąpił błąd",
|
||||
"search_bar_hint": "Szukaj swoich zdjęć",
|
||||
"search_filter_apply": "Zastosuj filtr",
|
||||
"search_filter_apply": "Apply filter",
|
||||
"search_filter_camera_make": "Make",
|
||||
"search_filter_camera_model": "Model",
|
||||
"search_filter_display_option_archive": "Archiwum",
|
||||
@@ -391,7 +391,6 @@
|
||||
"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",
|
||||
"setting_image_viewer_title": "Zdjęcia",
|
||||
"setting_languages_apply": "Zastosuj",
|
||||
"setting_languages_title": "Języki",
|
||||
"setting_notifications_notify_failures_grace_period": "Powiadomienie o awariach kopii zapasowych w tle: {}",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "Pokaż całkowity postęp tworzenia kopii zapasowej w tle",
|
||||
"setting_pages_app_bar_settings": "Ustawienia",
|
||||
"settings_require_restart": "Aby zastosować to ustawienie, uruchom ponownie Immich",
|
||||
"setting_video_viewer_looping_subtitle": "Włącz automatyczne zapętlanie wideo w przeglądarce szczegółów.",
|
||||
"setting_video_viewer_looping_title": "Zapętlenie",
|
||||
"setting_video_viewer_title": "Filmy",
|
||||
"share_add": "Dodaj",
|
||||
"share_add_photos": "Dodaj zdjęcia",
|
||||
"share_add_title": "Dodaj tytuł",
|
||||
"share_assets_selected": "{} wybrano ",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "Utwórz album",
|
||||
"shared_album_activities_input_disable": "Komentarz jest wyłączony",
|
||||
"shared_album_activities_input_hint": "Powiedz coś",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Carregar imagem original",
|
||||
"setting_image_viewer_preview_subtitle": "Ative para carregar uma imagem de resolução média. Desative para carregar diretamente o original ou usar apenas a miniatura.",
|
||||
"setting_image_viewer_preview_title": "Carregar imagem de visualização",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notifique falhas de backup em segundo plano: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Mostrar progresso total do backup em segundo plano",
|
||||
"setting_pages_app_bar_settings": "Configurações",
|
||||
"settings_require_restart": "Reinicie o Immich para aplicar essa configuração",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Adicionar",
|
||||
"share_add_photos": "Adicionar fotos",
|
||||
"share_add_title": "Adicione um título",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Încarcă fotografia originală",
|
||||
"setting_image_viewer_preview_subtitle": "Activează pentru a încărca o imagine în rezoluție medie. Dezactivează pentru a încărca direct imaginea originală sau doar a utiliza miniatura.",
|
||||
"setting_image_viewer_preview_title": "Încarcă imaginea de previzualizare",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notificare eșuări backup în fundal: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Afișează progresul total al copiilor de siguranță în fundal",
|
||||
"setting_pages_app_bar_settings": "Setări",
|
||||
"settings_require_restart": "Te rugăm să repornești Immich pentru a aplica această setare",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Adaugă",
|
||||
"share_add_photos": "Adaugă fotografii",
|
||||
"share_add_title": "Adaugă un titlu",
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
"home_page_album_err_partner": "Пока не удается добавить объекты партнера в альбом, пропуск...",
|
||||
"home_page_archive_err_local": "Пока невозможно добавить локальные объекты в архив, пропускаем",
|
||||
"home_page_archive_err_partner": "Невозможно архивировать объекты партнера, пропуск...",
|
||||
"home_page_building_timeline": "Построение хронологии",
|
||||
"home_page_building_timeline": "Построение временной шкалы",
|
||||
"home_page_delete_err_partner": "Невозможно удалить объекты партнера, пропуск...",
|
||||
"home_page_delete_remote_err_local": "Локальные объект(ы) уже в процессе удаления с сервера, пропуск...",
|
||||
"home_page_favorite_err_local": "Пока не удается добавить в избранное локальные объекты, пропуск...",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Загружать исходное изображение",
|
||||
"setting_image_viewer_preview_subtitle": "Включите для загрузки изображения среднего разрешения.\nОтключите, чтобы загружать оригинал напрямую или использовать только миниатюру.",
|
||||
"setting_image_viewer_preview_title": "Загружать изображение для предварительного просмотра",
|
||||
"setting_image_viewer_title": "Изображения",
|
||||
"setting_languages_apply": "Применить",
|
||||
"setting_languages_title": "Язык",
|
||||
"setting_notifications_notify_failures_grace_period": "Уведомлять об ошибках фонового резервного копирования: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Показать общий прогресс фонового резервного копирования",
|
||||
"setting_pages_app_bar_settings": "Настройки",
|
||||
"settings_require_restart": "Пожалуйста, перезапустите приложение, чтобы изменения вступили в силу",
|
||||
"setting_video_viewer_looping_subtitle": "Включить циклическое воспроизведение видео",
|
||||
"setting_video_viewer_looping_title": "Циклическое воспроизведение",
|
||||
"setting_video_viewer_title": "Видео",
|
||||
"share_add": "Добавить",
|
||||
"share_add_photos": "Добавить фото",
|
||||
"share_add_title": "Добавить название",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Načítať pôvodný obrázok",
|
||||
"setting_image_viewer_preview_subtitle": "Povolením umožníte načítať obrázok so stredným rozlíšením. Zakážte, ak chcete priamo načítať originál alebo použiť iba miniatúru.",
|
||||
"setting_image_viewer_preview_title": "Načítať náhľad obrázka",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Oznámenie o zlyhaní zálohovania na pozadí: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Zobraziť celkový priebeh zálohovania na pozadí",
|
||||
"setting_pages_app_bar_settings": "Nastavenia",
|
||||
"settings_require_restart": "Na použitie tohto nastavenia reštartujte Immich",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Pridať",
|
||||
"share_add_photos": "Pridať fotografie",
|
||||
"share_add_title": "Pridať názov",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Naloži originalno sliko",
|
||||
"setting_image_viewer_preview_subtitle": "Omogoči nalaganje slike srednje ločljivosti. Onemogočite neposredno nalaganje izvirnika ali uporabo samo sličice.",
|
||||
"setting_image_viewer_preview_title": "Naloži predogled slike",
|
||||
"setting_image_viewer_title": "Slike",
|
||||
"setting_languages_apply": "Uporabi",
|
||||
"setting_languages_title": "Jeziki",
|
||||
"setting_notifications_notify_failures_grace_period": "Obvesti o napakah varnostnega kopiranja v ozadju: {}",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "Prikaži skupni napredek varnostnega kopiranja v ozadju",
|
||||
"setting_pages_app_bar_settings": "Nastavitve",
|
||||
"settings_require_restart": "Znova zaženite Immich, da uporabite to nastavitev",
|
||||
"setting_video_viewer_looping_subtitle": "Omogočite samodejno ponavljanje videoposnetka v pregledovalniku podrobnosti.",
|
||||
"setting_video_viewer_looping_title": "V zanki",
|
||||
"setting_video_viewer_title": "Videoposnetki",
|
||||
"share_add": "Dodaj",
|
||||
"share_add_photos": "Dodaj fotografije",
|
||||
"share_add_title": "Dodaj naslov",
|
||||
"share_assets_selected": "{} izbrano",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "Ustvari album",
|
||||
"shared_album_activities_input_disable": "Komentiranje je onemogočeno",
|
||||
"shared_album_activities_input_hint": "Reci kaj",
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
"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 backed up",
|
||||
"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",
|
||||
@@ -391,7 +391,6 @@
|
||||
"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_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Učitaj originalnu sliku",
|
||||
"setting_image_viewer_preview_subtitle": "Aktiviraj učitavanje slika u srednjoj rezoluciji. Deaktiviraj da se direktno učitava original, ili da se samo koristi minijatura.",
|
||||
"setting_image_viewer_preview_title": "Pregledaj sliku",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Neuspešne rezervne kopije: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Prikaži ukupan napredak pozadinskog bekapovanja.\n\n",
|
||||
"setting_pages_app_bar_settings": "Opcije",
|
||||
"settings_require_restart": "Restartujte Immich da primenite ovu promenu",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Dodaj",
|
||||
"share_add_photos": "Dodaj fotografije",
|
||||
"share_add_title": "Dodaj naslov",
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
"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 backed up",
|
||||
"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",
|
||||
@@ -391,7 +391,6 @@
|
||||
"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_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
{
|
||||
"action_common_back": "Bakåt",
|
||||
"action_common_cancel": "Avbryt",
|
||||
"action_common_clear": "Rensa",
|
||||
"action_common_confirm": "Bekräfta",
|
||||
"action_common_update": "Uppdatera",
|
||||
"add_to_album_bottom_sheet_added": "Tillagd till {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Redan i {album}",
|
||||
"advanced_settings_log_level_title": "Loggnivå: {}",
|
||||
"advanced_settings_prefer_remote_subtitle": "Vissa enheter är mycket långsamma på att ladda tumnaglar från resurser på enheten. Aktivera den här inställningen för att ladda bilder från servern istället.",
|
||||
"advanced_settings_prefer_remote_title": "Föredra bilder från servern",
|
||||
"advanced_settings_self_signed_ssl_subtitle": "Hoppar över SSL-certifikatverifiering för serverändpunkten. Krävs för självsignerade certifikat.",
|
||||
"advanced_settings_self_signed_ssl_title": "Tillåt självsignerade SSL-certifikat",
|
||||
"advanced_settings_tile_subtitle": "Avancerade användarinställningar",
|
||||
"action_common_back": "Back",
|
||||
"action_common_cancel": "Cancel",
|
||||
"action_common_clear": "Clear",
|
||||
"action_common_confirm": "Confirm",
|
||||
"action_common_update": "Update",
|
||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
||||
"advanced_settings_log_level_title": "Log level: {}",
|
||||
"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_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.",
|
||||
"advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates",
|
||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
||||
"advanced_settings_tile_title": "Avancerad",
|
||||
"advanced_settings_troubleshooting_subtitle": "Aktivera funktioner för felsökning",
|
||||
"advanced_settings_troubleshooting_title": "Felsökning",
|
||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
||||
"album_info_card_backup_album_excluded": "EXKLUDERAD",
|
||||
"album_info_card_backup_album_included": "INKLUDERAD",
|
||||
"album_thumbnail_card_item": "1 objekt",
|
||||
"album_thumbnail_card_items": "{} objekt",
|
||||
"album_thumbnail_card_shared": " · Delad",
|
||||
"album_thumbnail_owned": "Ägd",
|
||||
"album_thumbnail_card_shared": ". Delad",
|
||||
"album_thumbnail_owned": "Owned",
|
||||
"album_thumbnail_shared_by": "Delat av {}",
|
||||
"album_viewer_appbar_delete_confirm": "Är du säker på att du vill ta bort albumet från ditt konto?",
|
||||
"album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?",
|
||||
"album_viewer_appbar_share_delete": "Radera album",
|
||||
"album_viewer_appbar_share_err_delete": "Kunde inte radera album",
|
||||
"album_viewer_appbar_share_err_leave": "Kunde inte lämna album",
|
||||
@@ -30,27 +30,27 @@
|
||||
"album_viewer_appbar_share_err_title": "Kunde inte ändra albumtitel",
|
||||
"album_viewer_appbar_share_leave": "Lämna album",
|
||||
"album_viewer_appbar_share_remove": "Ta bort från album",
|
||||
"album_viewer_appbar_share_to": "Dela Till",
|
||||
"album_viewer_appbar_share_to": "Share To",
|
||||
"album_viewer_page_share_add_users": "Lägg till användare",
|
||||
"all_people_page_title": "Personer",
|
||||
"all_videos_page_title": "Videor",
|
||||
"app_bar_signout_dialog_content": "Är du säker på att du vill logga ut?",
|
||||
"app_bar_signout_dialog_ok": "Ja",
|
||||
"app_bar_signout_dialog_title": "Logga ut",
|
||||
"archive_page_no_archived_assets": "Inga arkiverade resurser hittade",
|
||||
"all_people_page_title": "People",
|
||||
"all_videos_page_title": "Videos",
|
||||
"app_bar_signout_dialog_content": "Are you sure you want to sign out?",
|
||||
"app_bar_signout_dialog_ok": "Yes",
|
||||
"app_bar_signout_dialog_title": "Sign out",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Arkivera ({})",
|
||||
"asset_action_delete_err_read_only": "Kan inte ta bort skrivskyddade resurser, hoppar över",
|
||||
"asset_action_share_err_offline": "Kan inte hämta offline-resurs(er), hoppar över",
|
||||
"asset_list_group_by_sub_title": "Gruppera på",
|
||||
"asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping",
|
||||
"asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping",
|
||||
"asset_list_group_by_sub_title": "Group by",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dynamisk layout",
|
||||
"asset_list_layout_settings_group_automatically": "Automatiskt",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Gruppera bilder efter",
|
||||
"asset_list_layout_settings_group_by_month": "Månad",
|
||||
"asset_list_layout_settings_group_by_month_day": "Månad + dag",
|
||||
"asset_list_layout_sub_title": "Layout",
|
||||
"asset_list_settings_subtitle": "Layoutinställningar för bildrutnät",
|
||||
"asset_list_settings_title": "Bildrutnät",
|
||||
"asset_viewer_settings_title": "Resursvisare",
|
||||
"asset_viewer_settings_title": "Asset Viewer",
|
||||
"backup_album_selection_page_albums_device": "Album på enhet ({})",
|
||||
"backup_album_selection_page_albums_tap": "Tryck en gång för att inkludera, tryck två gånger för att exkludera",
|
||||
"backup_album_selection_page_assets_scatter": "Objekt kan vara utspridda över flera album. Därför kan album inkluderas eller exkluderas under säkerhetskopieringsprocessen",
|
||||
@@ -66,8 +66,8 @@
|
||||
"backup_background_service_in_progress_notification": "Säkerhetskopierar dina foton och videor...",
|
||||
"backup_background_service_upload_failure_notification": "Kunde inte ladda upp {}",
|
||||
"backup_controller_page_albums": "Säkerhetskopiera album",
|
||||
"backup_controller_page_background_app_refresh_disabled_content": "Aktivera uppdatering i bakgrunden i Inställningar > Allmänt > Uppdatering I Bakgrunden för att använda säkerhetskopiering i bakgrunden.",
|
||||
"backup_controller_page_background_app_refresh_disabled_title": "Uppdatering i bakgrunden är avaktiverat",
|
||||
"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": "Gå till inställningar",
|
||||
"backup_controller_page_background_battery_info_link": "Visa mig hur",
|
||||
"backup_controller_page_background_battery_info_message": "För optimal säkerhetskopiering i bakgrunden bör du stänga av batterioptimering som begränsar bakgrundsaktivitet för Immich.\n\nEftersom detta är enhetsspecifikt så bör du söka instruktioner från din enhetstillverkare.",
|
||||
@@ -110,18 +110,18 @@
|
||||
"backup_controller_page_uploading_file_info": "Laddar upp filinformation",
|
||||
"backup_err_only_album": "Kan inte ta bort det enda albumet",
|
||||
"backup_info_card_assets": "objekt",
|
||||
"backup_manual_cancelled": "Avbrutet",
|
||||
"backup_manual_failed": "Misslyckades",
|
||||
"backup_manual_in_progress": "Uppladdning pågår redan. Försök igen om en liten stund",
|
||||
"backup_manual_success": "Klart",
|
||||
"backup_manual_title": "Uppladdningsstatus",
|
||||
"backup_options_page_title": "Säkerhetskopieringsinställningar",
|
||||
"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",
|
||||
"backup_options_page_title": "Backup options",
|
||||
"cache_settings_album_thumbnails": "Miniatyrbilder för bibliotek ({} bilder och videor)",
|
||||
"cache_settings_clear_cache_button": "Rensa cacheminnet",
|
||||
"cache_settings_clear_cache_button_title": "Rensar appens cacheminne. Detta kommer att avsevärt påverka appens prestanda tills cachen har byggts om.",
|
||||
"cache_settings_duplicated_assets_clear_button": "RENSA",
|
||||
"cache_settings_duplicated_assets_subtitle": "Foton och videor som är svartlistade av appen",
|
||||
"cache_settings_duplicated_assets_title": "Duplicerade Resurser ({})",
|
||||
"cache_settings_duplicated_assets_clear_button": "CLEAR",
|
||||
"cache_settings_duplicated_assets_subtitle": "Photos and videos that are black listed by the app",
|
||||
"cache_settings_duplicated_assets_title": "Duplicated Assets ({})",
|
||||
"cache_settings_image_cache_size": "Cacheminnets storlek ({} bilder och videor)",
|
||||
"cache_settings_statistics_album": "Miniatyrbilder för bibliotek",
|
||||
"cache_settings_statistics_assets": "{} bilder och videor ({})",
|
||||
@@ -131,37 +131,37 @@
|
||||
"cache_settings_statistics_title": "Cacheförbrukning",
|
||||
"cache_settings_subtitle": "Hantera cachebeteendet för Immich-appen.",
|
||||
"cache_settings_thumbnail_size": "Storlek på cacheminnet ({} bilder och videor)",
|
||||
"cache_settings_tile_subtitle": "Kontrollera beteende för lokal lagring",
|
||||
"cache_settings_tile_title": "Lokal Lagring",
|
||||
"cache_settings_tile_subtitle": "Control the local storage behaviour",
|
||||
"cache_settings_tile_title": "Local Storage",
|
||||
"cache_settings_title": "Cache Inställningar",
|
||||
"change_password_form_confirm_password": "Bekräfta lösenord",
|
||||
"change_password_form_description": "Hej {name},\n\nDet är antingen första gången du loggar in i systemet, eller så har det skett en förfrågan om återställning av ditt lösenord. Ange ditt nya lösenord nedan.",
|
||||
"change_password_form_description": "Hi {name},\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": "Nytt lösenord",
|
||||
"change_password_form_password_mismatch": "Lösenorden matchar inte",
|
||||
"change_password_form_reenter_new_password": "Ange Nytt Lösenord Igen",
|
||||
"common_add_to_album": "Lägg till, till album",
|
||||
"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": "Ändra lösenord",
|
||||
"common_create_new_album": "Skapa ett nytt album",
|
||||
"common_server_error": "Kontrollera din nätverksanslutning, se till att servern går att nå och att app- och server-versioner är kompatibla.",
|
||||
"common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.",
|
||||
"common_shared": "Delad",
|
||||
"control_bottom_app_bar_add_to_album": "Lägg till i album",
|
||||
"control_bottom_app_bar_album_info": "{} objekt",
|
||||
"control_bottom_app_bar_album_info_shared": "{} objekt • Delat",
|
||||
"control_bottom_app_bar_archive": "Arkivera",
|
||||
"control_bottom_app_bar_archive": "Archive",
|
||||
"control_bottom_app_bar_create_new_album": "Skapa nytt album",
|
||||
"control_bottom_app_bar_delete": "Radera",
|
||||
"control_bottom_app_bar_delete_from_immich": "Ta bort från Immich",
|
||||
"control_bottom_app_bar_delete_from_local": "Ta bort från enhet",
|
||||
"control_bottom_app_bar_edit_location": "Redigera plats",
|
||||
"control_bottom_app_bar_edit_time": "Redigera Datum & Tid",
|
||||
"control_bottom_app_bar_delete_from_immich": "Delete from Immich",
|
||||
"control_bottom_app_bar_delete_from_local": "Delete from device",
|
||||
"control_bottom_app_bar_edit_location": "Edit Location",
|
||||
"control_bottom_app_bar_edit_time": "Edit Date & Time",
|
||||
"control_bottom_app_bar_favorite": "Favorit",
|
||||
"control_bottom_app_bar_share": "Dela",
|
||||
"control_bottom_app_bar_share_to": "Dela Till",
|
||||
"control_bottom_app_bar_stack": "Stapel",
|
||||
"control_bottom_app_bar_trash_from_immich": "Flytta till Papperskorgen",
|
||||
"control_bottom_app_bar_unarchive": "Avarkivera",
|
||||
"control_bottom_app_bar_unfavorite": "Avfavorisera",
|
||||
"control_bottom_app_bar_upload": "Ladda Upp",
|
||||
"control_bottom_app_bar_share_to": "Share To",
|
||||
"control_bottom_app_bar_stack": "Stack",
|
||||
"control_bottom_app_bar_trash_from_immich": "Move to Trash",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"control_bottom_app_bar_unfavorite": "Unfavorite",
|
||||
"control_bottom_app_bar_upload": "Upload",
|
||||
"create_album_page_untitled": "Namnlös",
|
||||
"create_shared_album_page_create": "Skapa",
|
||||
"create_shared_album_page_share": "Dela",
|
||||
@@ -173,76 +173,76 @@
|
||||
"daily_title_text_date_year": "E, dd MMM, yyyy",
|
||||
"date_format": "E d. LLL y • hh:mm",
|
||||
"delete_dialog_alert": "Dessa objekt kommer att raderas permanent från Immich och din enhet",
|
||||
"delete_dialog_alert_local": "Dessa saker kommer att tas bort från din enhet men fortsatt vara tillgängliga på Immich-servern",
|
||||
"delete_dialog_alert_local_non_backed_up": "Några av sakerna har inte säkerhetskopierats till Immich och kommer att tas bort permanent från din enhet.",
|
||||
"delete_dialog_alert_remote": "Dessa saker kommer att tas bort permanent från Immich-servern",
|
||||
"delete_dialog_alert_local": "These items will be permanently removed from your device but still be available on the Immich server",
|
||||
"delete_dialog_alert_local_non_backed_up": "Some of the items aren't backed up to Immich and will be permanently removed from your device",
|
||||
"delete_dialog_alert_remote": "These items will be permanently deleted from the Immich server",
|
||||
"delete_dialog_cancel": "Avbryt",
|
||||
"delete_dialog_ok": "Radera",
|
||||
"delete_dialog_ok_force": "Ta Bort Ändå",
|
||||
"delete_dialog_ok_force": "Delete Anyway",
|
||||
"delete_dialog_title": "Radera permanent",
|
||||
"delete_local_dialog_ok_backed_up_only": "Ta Bara Bort Säkerhetskopierade",
|
||||
"delete_local_dialog_ok_force": "Ta Bort Ändå",
|
||||
"delete_shared_link_dialog_content": "Är du säker på att du vill ta bort den här delade länken?",
|
||||
"delete_shared_link_dialog_title": "Ta Bort Delad Länk",
|
||||
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only",
|
||||
"delete_local_dialog_ok_force": "Delete Anyway",
|
||||
"delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?",
|
||||
"delete_shared_link_dialog_title": "Delete Shared Link",
|
||||
"description_input_hint_text": "Lägg till beskrivning...",
|
||||
"description_input_submit_error": "Fel vid uppdatering av beskrivning, se loggen för fler detaljer",
|
||||
"edit_date_time_dialog_date_time": "Datum och Tid",
|
||||
"edit_date_time_dialog_timezone": "Tidszon",
|
||||
"edit_location_dialog_title": "Plats",
|
||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
||||
"edit_date_time_dialog_date_time": "Date and Time",
|
||||
"edit_date_time_dialog_timezone": "Timezone",
|
||||
"edit_location_dialog_title": "Location",
|
||||
"exif_bottom_sheet_description": "Lägg till beskrivning...",
|
||||
"exif_bottom_sheet_details": "DETALJER",
|
||||
"exif_bottom_sheet_location": "PLATS",
|
||||
"exif_bottom_sheet_location_add": "Lägg till plats",
|
||||
"exif_bottom_sheet_people": "PERSONER",
|
||||
"exif_bottom_sheet_person_add_person": "Lägg till namn",
|
||||
"exif_bottom_sheet_location_add": "Add a location",
|
||||
"exif_bottom_sheet_people": "PEOPLE",
|
||||
"exif_bottom_sheet_person_add_person": "Add name",
|
||||
"experimental_settings_new_asset_list_subtitle": "Under uppbyggnad",
|
||||
"experimental_settings_new_asset_list_title": "Aktivera experimentellt fotorutnät",
|
||||
"experimental_settings_subtitle": "Använd på egen risk!",
|
||||
"experimental_settings_title": "Experimentellt",
|
||||
"favorites_page_no_favorites": "Inga favoritresurser hittades",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Favoriter",
|
||||
"haptic_feedback_switch": "Aktivera haptisk feedback",
|
||||
"haptic_feedback_title": "Haptisk Feedback",
|
||||
"haptic_feedback_switch": "Enable haptic feedback",
|
||||
"haptic_feedback_title": "Haptic Feedback",
|
||||
"home_page_add_to_album_conflicts": "Lade till {added} foton och videor i albumet {album}. {failed} foton och videor finns redan i albumet.",
|
||||
"home_page_add_to_album_err_local": "Kan inte lägga till lokala resurser till album ännu, hoppar över",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
"home_page_add_to_album_success": "Lade till {added} foton och videor i albumet {album}.",
|
||||
"home_page_album_err_partner": "Kan inte lägga till partner-resurser till album ännu, hoppar över",
|
||||
"home_page_archive_err_local": "Kan inte arkivera lokala resurser ännu, hoppar över",
|
||||
"home_page_archive_err_partner": "Kan inte arkivera partner-resurs, hoppar över",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Bygger tidslinjen",
|
||||
"home_page_delete_err_partner": "Kan inte ta bort partner-resurs, hoppar över",
|
||||
"home_page_delete_remote_err_local": "Lokala resurser i urvalet för att ta bort från servern, hoppar över",
|
||||
"home_page_favorite_err_local": "Kan inte favorisera lokala resurser ännu, hoppar över",
|
||||
"home_page_favorite_err_partner": "Kan inte favorisera partner-resurser ännu, hoppar över",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Om det här är första gången du använder appen, välj ett eller flera backup-album så att tidslinjen kan fyllas med foton och videor från albumen.",
|
||||
"home_page_share_err_local": "Kan inte dela lokal resurs via länk, hoppar över",
|
||||
"home_page_upload_err_limit": "Kan bara ladda upp max 30 resurser åt gången, hoppar över",
|
||||
"image_viewer_page_state_provider_download_error": "Fel Vid Nedladdning",
|
||||
"image_viewer_page_state_provider_download_started": "Nedladdning Påbörjad",
|
||||
"image_viewer_page_state_provider_download_success": "Nedladdningen Lyckades",
|
||||
"image_viewer_page_state_provider_share_error": "Delningsfel",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"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_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
"image_viewer_page_state_provider_share_error": "Share Error",
|
||||
"library_page_albums": "Album",
|
||||
"library_page_archive": "Arkiv",
|
||||
"library_page_device_albums": "Album på Enheten",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_favorites": "Favoriter",
|
||||
"library_page_new_album": "Nytt album",
|
||||
"library_page_sharing": "Delas",
|
||||
"library_page_sort_asset_count": "Antal resurser",
|
||||
"library_page_sort_asset_count": "Number of assets",
|
||||
"library_page_sort_created": "Senast skapad",
|
||||
"library_page_sort_last_modified": "Senast ändrad",
|
||||
"library_page_sort_most_oldest_photo": "Äldsta foto",
|
||||
"library_page_sort_most_recent_photo": "Senaste foto",
|
||||
"library_page_sort_last_modified": "Last modified",
|
||||
"library_page_sort_most_oldest_photo": "Oldest photo",
|
||||
"library_page_sort_most_recent_photo": "Most recent photo",
|
||||
"library_page_sort_title": "Albumtitel",
|
||||
"location_picker_choose_on_map": "Välj på karta",
|
||||
"location_picker_latitude": "Latitud",
|
||||
"location_picker_latitude_error": "Ange en giltig latitud",
|
||||
"location_picker_latitude_hint": "Ange din latitud här",
|
||||
"location_picker_longitude": "Longitud",
|
||||
"location_picker_longitude_error": "Ange en giltig longitud",
|
||||
"location_picker_longitude_hint": "Ange din longitud här",
|
||||
"login_disabled": "Inloggning har inaktiverats",
|
||||
"login_form_api_exception": "API-undantag. Kontrollera server-URL:en och försök igen.",
|
||||
"login_form_back_button_text": "Bakåt",
|
||||
"location_picker_choose_on_map": "Choose on map",
|
||||
"location_picker_latitude": "Latitude",
|
||||
"location_picker_latitude_error": "Enter a valid latitude",
|
||||
"location_picker_latitude_hint": "Enter your latitude here",
|
||||
"location_picker_longitude": "Longitude",
|
||||
"location_picker_longitude_error": "Enter a valid longitude",
|
||||
"location_picker_longitude_hint": "Enter your longitude here",
|
||||
"login_disabled": "Login has been disabled",
|
||||
"login_form_api_exception": "API exception. Please check the server URL and try again.",
|
||||
"login_form_back_button_text": "Back",
|
||||
"login_form_button_text": "Logga in",
|
||||
"login_form_email_hint": "din.email@email.com",
|
||||
"login_form_endpoint_hint": "http://din-server-ip:port/api",
|
||||
@@ -255,145 +255,144 @@
|
||||
"login_form_failed_get_oauth_server_config": "Kunde inte logga in med OAuth. Kontrollera serverns webbadress",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth är inte tillgänglig på den här servern",
|
||||
"login_form_failed_login": "Kunde inte logga in. Kontrollera serverns webbadress, email och lösenord.",
|
||||
"login_form_handshake_exception": "Ett Undantag vid Handskakning med servern har skett. Aktivera stöd för självsignerade certifikat i inställningar om du använder ett självsignerat certifikat.",
|
||||
"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_label_email": "Email",
|
||||
"login_form_label_password": "Lösenord",
|
||||
"login_form_next_button": "Nästa",
|
||||
"login_form_password_hint": "lösenord",
|
||||
"login_form_save_login": "Håll mig inloggad",
|
||||
"login_form_server_empty": "Ange en server-URL.",
|
||||
"login_form_server_empty": "Enter a server URL.",
|
||||
"login_form_server_error": "Kunde inte ansluta till servern",
|
||||
"login_password_changed_error": "Ett fel uppstod vid uppdatering av ditt lösenord",
|
||||
"login_password_changed_success": "Uppdatering av lösenord lyckades",
|
||||
"map_assets_in_bound": "{} foto",
|
||||
"map_assets_in_bounds": "{} foton",
|
||||
"map_cannot_get_user_location": "Kan inte hämta användarens plats",
|
||||
"map_location_dialog_cancel": "Avbryt",
|
||||
"map_location_dialog_yes": "Ja",
|
||||
"map_location_picker_page_use_location": "Använd den här platsen",
|
||||
"map_location_service_disabled_content": "Platstjänst måste vara aktiverad för att visa resurser från din nuvarande plats. Vill du aktivera den nu?",
|
||||
"map_location_service_disabled_title": "Platstjänst inaktiverad",
|
||||
"map_no_assets_in_bounds": "Inga foton i området",
|
||||
"map_no_location_permission_content": "Platsrättighet är nödvändigt för att kunna visa resurser från din nuvarande plats. Vill du tillåta det nu?",
|
||||
"map_no_location_permission_title": "Platsrättighet nekad",
|
||||
"map_settings_dark_mode": "Mörkt tema",
|
||||
"map_settings_date_range_option_all": "Alla",
|
||||
"map_settings_date_range_option_day": "Senaste 24 timmarna",
|
||||
"map_settings_date_range_option_days": "Senaste {} dagarna",
|
||||
"map_settings_date_range_option_year": "Senaste året",
|
||||
"map_settings_date_range_option_years": "Senaste {} åren",
|
||||
"map_settings_dialog_cancel": "Avbryt",
|
||||
"map_settings_dialog_save": "Spara",
|
||||
"map_settings_dialog_title": "Kartinställningar",
|
||||
"map_settings_include_show_archived": "Inkludera Arkiverade",
|
||||
"map_settings_include_show_partners": "Inkludera Partners",
|
||||
"map_settings_only_relative_range": "Datumintervall",
|
||||
"map_settings_only_show_favorites": "Visa Endast Favoriter",
|
||||
"map_settings_theme_settings": "Kart-tema",
|
||||
"map_zoom_to_see_photos": "Zooma ut för att se foton",
|
||||
"memories_all_caught_up": "Du är ikapp",
|
||||
"memories_check_back_tomorrow": "Kom tillbaka imorgon för fler minnen",
|
||||
"memories_start_over": "Börja Om",
|
||||
"memories_swipe_to_close": "Svep upp för att stänga",
|
||||
"login_password_changed_error": "There was an error updating your password",
|
||||
"login_password_changed_success": "Password updated successfully",
|
||||
"map_assets_in_bound": "{} photo",
|
||||
"map_assets_in_bounds": "{} photos",
|
||||
"map_cannot_get_user_location": "Cannot get user's location",
|
||||
"map_location_dialog_cancel": "Cancel",
|
||||
"map_location_dialog_yes": "Yes",
|
||||
"map_location_picker_page_use_location": "Use this location",
|
||||
"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_location_service_disabled_title": "Location Service disabled",
|
||||
"map_no_assets_in_bounds": "No photos in this area",
|
||||
"map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?",
|
||||
"map_no_location_permission_title": "Location Permission denied",
|
||||
"map_settings_dark_mode": "Dark mode",
|
||||
"map_settings_date_range_option_all": "All",
|
||||
"map_settings_date_range_option_day": "Past 24 hours",
|
||||
"map_settings_date_range_option_days": "Past {} days",
|
||||
"map_settings_date_range_option_year": "Past year",
|
||||
"map_settings_date_range_option_years": "Past {} years",
|
||||
"map_settings_dialog_cancel": "Cancel",
|
||||
"map_settings_dialog_save": "Save",
|
||||
"map_settings_dialog_title": "Map Settings",
|
||||
"map_settings_include_show_archived": "Include Archived",
|
||||
"map_settings_include_show_partners": "Include Partners",
|
||||
"map_settings_only_relative_range": "Date range",
|
||||
"map_settings_only_show_favorites": "Show Favorite Only",
|
||||
"map_settings_theme_settings": "Map Theme",
|
||||
"map_zoom_to_see_photos": "Zoom out to see photos",
|
||||
"memories_all_caught_up": "All caught up",
|
||||
"memories_check_back_tomorrow": "Check back tomorrow for more memories",
|
||||
"memories_start_over": "Start Over",
|
||||
"memories_swipe_to_close": "Swipe up to close",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"motion_photos_page_title": "Rörelsefoton",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "Kan inte ändra datum på skrivskyddade resurser, hoppar över",
|
||||
"multiselect_grid_edit_gps_err_read_only": "Kan inte ändra plats på skrivskyddade resurser, hoppar över",
|
||||
"no_assets_to_show": "Inga resurser att visa",
|
||||
"motion_photos_page_title": "Motion Photos",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping",
|
||||
"multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping",
|
||||
"no_assets_to_show": "No assets to show",
|
||||
"notification_permission_dialog_cancel": "Avbryt",
|
||||
"notification_permission_dialog_content": "För att aktivera notiser, gå till Inställningar och välj tillåt",
|
||||
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
|
||||
"notification_permission_dialog_settings": "Inställningar",
|
||||
"notification_permission_list_tile_content": "Tillåt rättighet för att slå på notiser.",
|
||||
"notification_permission_list_tile_enable_button": "Aktivera Notiser",
|
||||
"notification_permission_list_tile_title": "Notisrättighet",
|
||||
"partner_list_user_photos": "{user}s foton",
|
||||
"partner_list_view_all": "Visa alla",
|
||||
"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_list_user_photos": "{user}'s photos",
|
||||
"partner_list_view_all": "View all",
|
||||
"partner_page_add_partner": "Lägg till partner",
|
||||
"partner_page_empty_message": "Dina foton är inte ännu delade med någon partner.",
|
||||
"partner_page_no_more_users": "Inga fler användare att lägga till",
|
||||
"partner_page_partner_add_failed": "Misslyckades med att lägga till partner",
|
||||
"partner_page_select_partner": "Välj partner",
|
||||
"partner_page_shared_to_title": "Delad till",
|
||||
"partner_page_stop_sharing_content": "{} kommer inte längre att komma åt dina foton.",
|
||||
"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": "Sluta dela dina foton?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Bakåt",
|
||||
"permission_onboarding_continue_anyway": "Fortsätt ändå",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
||||
"permission_onboarding_get_started": "Kom igång",
|
||||
"permission_onboarding_go_to_settings": "Gå till inställningar",
|
||||
"permission_onboarding_grant_permission": "Tillåt",
|
||||
"permission_onboarding_log_out": "Logga ut",
|
||||
"permission_onboarding_permission_denied": "Rättighet nekad. För att använda Immich, tillåt foto- och video-rättigheter i Inställningar.",
|
||||
"permission_onboarding_permission_granted": "Rättigheten beviljad! Du är klar.",
|
||||
"permission_onboarding_permission_limited": "Rättighet begränsad. För att låta Immich säkerhetskopiera och hantera hela ditt galleri, tillåt foto- och video-rättigheter i Inställningar.",
|
||||
"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 kräver tillstånd för att se dina foton och videor.",
|
||||
"preferences_settings_title": "Inställningar",
|
||||
"preferences_settings_title": "Preferences",
|
||||
"profile_drawer_app_logs": "Loggar",
|
||||
"profile_drawer_client_out_of_date_major": "Mobilappen är utdaterad. Uppdatera till senaste huvudversionen.",
|
||||
"profile_drawer_client_out_of_date_minor": "Mobilappen är utdaterad. Uppdatera till senaste mindre versionen.",
|
||||
"profile_drawer_client_out_of_date_major": "Mobile App is out of date. Please update to the latest major version.",
|
||||
"profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.",
|
||||
"profile_drawer_client_server_up_to_date": "Klient och server är uppdaterade",
|
||||
"profile_drawer_documentation": "Dokumentation",
|
||||
"profile_drawer_documentation": "Documentation",
|
||||
"profile_drawer_github": "GitHub",
|
||||
"profile_drawer_server_out_of_date_major": "Servern är utdaterad. Uppdatera till senaste huvudversionen.",
|
||||
"profile_drawer_server_out_of_date_minor": "Servern är utdaterad. Uppdatera till senaste mindre versionen.",
|
||||
"profile_drawer_server_out_of_date_major": "Server is out of date. Please update to the latest major version.",
|
||||
"profile_drawer_server_out_of_date_minor": "Server is out of date. Please update to the latest minor version.",
|
||||
"profile_drawer_settings": "Inställningar",
|
||||
"profile_drawer_sign_out": "Logga ut",
|
||||
"profile_drawer_trash": "Papperskorg",
|
||||
"profile_drawer_trash": "Trash",
|
||||
"recently_added_page_title": "Nyligen tillagda",
|
||||
"scaffold_body_error_occurred": "Fel uppstod",
|
||||
"scaffold_body_error_occurred": "Error occurred",
|
||||
"search_bar_hint": "Sök bland dina foton",
|
||||
"search_filter_apply": "Aktivera filter",
|
||||
"search_filter_camera_make": "Tillverkare",
|
||||
"search_filter_camera_model": "Modell",
|
||||
"search_filter_display_option_archive": "Arkiv",
|
||||
"search_filter_display_option_favorite": "Favorit",
|
||||
"search_filter_display_option_not_in_album": "Ej i album",
|
||||
"search_filter_location_city": "Stad",
|
||||
"search_filter_location_country": "Land",
|
||||
"search_filter_location_state": "Stat",
|
||||
"search_filter_media_type_all": "Alla",
|
||||
"search_filter_media_type_image": "Bild",
|
||||
"search_filter_media_type_video": "Videor",
|
||||
"search_filter_apply": "Apply filter",
|
||||
"search_filter_camera_make": "Make",
|
||||
"search_filter_camera_model": "Model",
|
||||
"search_filter_display_option_archive": "Archive",
|
||||
"search_filter_display_option_favorite": "Favorite",
|
||||
"search_filter_display_option_not_in_album": "Not in album",
|
||||
"search_filter_location_city": "City",
|
||||
"search_filter_location_country": "Country",
|
||||
"search_filter_location_state": "State",
|
||||
"search_filter_media_type_all": "All",
|
||||
"search_filter_media_type_image": "Image",
|
||||
"search_filter_media_type_video": "Video",
|
||||
"search_page_categories": "Kategorier",
|
||||
"search_page_favorites": "Favoriter",
|
||||
"search_page_motion_photos": "Rörelsefoton",
|
||||
"search_page_motion_photos": "Motion Photos",
|
||||
"search_page_no_objects": "Inga objekt är tillgängliga",
|
||||
"search_page_no_places": "Ingen platsinformation finns tillgänglig",
|
||||
"search_page_people": "Personer",
|
||||
"search_page_person_add_name_dialog_cancel": "Avbryt",
|
||||
"search_page_person_add_name_dialog_hint": "Namn",
|
||||
"search_page_person_add_name_dialog_save": "Spara",
|
||||
"search_page_person_add_name_dialog_title": "Lägg till ett namn",
|
||||
"search_page_person_add_name_subtitle": "Hitta dem snabbt på namn med sök",
|
||||
"search_page_person_add_name_title": "Lägg till ett namn",
|
||||
"search_page_person_edit_name": "Redigera namn",
|
||||
"search_page_people": "People",
|
||||
"search_page_person_add_name_dialog_cancel": "Cancel",
|
||||
"search_page_person_add_name_dialog_hint": "Name",
|
||||
"search_page_person_add_name_dialog_save": "Save",
|
||||
"search_page_person_add_name_dialog_title": "Add a name",
|
||||
"search_page_person_add_name_subtitle": "Find them fast by name with search",
|
||||
"search_page_person_add_name_title": "Add a name",
|
||||
"search_page_person_edit_name": "Edit name",
|
||||
"search_page_places": "Platser",
|
||||
"search_page_recently_added": "Nyligen tillagda",
|
||||
"search_page_screenshots": "Skärmdumpar",
|
||||
"search_page_screenshots": "Screenshots",
|
||||
"search_page_selfies": "Selfies",
|
||||
"search_page_things": "Saker",
|
||||
"search_page_videos": "Videor",
|
||||
"search_page_videos": "Videos",
|
||||
"search_page_view_all_button": "Visa alla",
|
||||
"search_page_your_activity": "Dina aktiviteter",
|
||||
"search_page_your_map": "Din Karta",
|
||||
"search_page_your_map": "Your Map",
|
||||
"search_result_page_new_search_hint": "Ny sökning",
|
||||
"search_suggestion_list_smart_search_hint_1": "Smartsök är aktiverat som standard, för att söka efter metadata, använd syntaxen",
|
||||
"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:ditt-sökord",
|
||||
"select_additional_user_for_sharing_page_suggestions": "Förslag",
|
||||
"select_user_for_sharing_page_err_album": "Kunde inte skapa nytt album",
|
||||
"select_user_for_sharing_page_share_suggestions": "Förslag",
|
||||
"server_info_box_app_version": "App version",
|
||||
"server_info_box_latest_release": "Senaste Version",
|
||||
"server_info_box_server_url": "Server-URL",
|
||||
"server_info_box_latest_release": "Latest Version",
|
||||
"server_info_box_server_url": "Server URL",
|
||||
"server_info_box_server_version": "Server version",
|
||||
"setting_image_viewer_help": "Detaljerad vy laddar miniatyrer först. Efter detta laddas den medelstora förhandsgranskningen av bilden (om detta är aktiverat), och visar slutligen originalet (om detta är aktiverat).",
|
||||
"setting_image_viewer_original_subtitle": "Aktivera för att ladda originalbilden i full storlek (stor!). Inaktivera för att minska dataanvändningen (både i nätverket och för enhetscache).",
|
||||
"setting_image_viewer_original_title": "Ladda originalbilden",
|
||||
"setting_image_viewer_preview_subtitle": "Aktivera för att ladda en mellanstor bild. Stäng av för att antingen ladda originalet direkt eller bara använda miniatyrbilden.",
|
||||
"setting_image_viewer_preview_title": "Ladda förhandsgranskning av bild",
|
||||
"setting_image_viewer_title": "Bilder",
|
||||
"setting_languages_apply": "Verkställ",
|
||||
"setting_languages_title": "Språk",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Rapportera säkerhetskopieringsfel i bakgrunden: {}",
|
||||
"setting_notifications_notify_hours": "{} timmar",
|
||||
"setting_notifications_notify_immediately": "omedelbart",
|
||||
@@ -408,78 +407,75 @@
|
||||
"setting_notifications_total_progress_title": "Visa totalt uppladdningsförlopp",
|
||||
"setting_pages_app_bar_settings": "Inställningar",
|
||||
"settings_require_restart": "Starta om Immich för att tillämpa den här inställningen",
|
||||
"setting_video_viewer_looping_subtitle": "Aktivera för att automatiskt loopa en video i detaljvisaren.",
|
||||
"setting_video_viewer_looping_title": "Loopar",
|
||||
"setting_video_viewer_title": "Videor",
|
||||
"share_add": "Lägg till",
|
||||
"share_add_photos": "Lägg till foton",
|
||||
"share_add_title": "Lägg till en titel",
|
||||
"share_assets_selected": "{} valda",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "Skapa album",
|
||||
"shared_album_activities_input_disable": "Kommentar är inaktiverad",
|
||||
"shared_album_activities_input_hint": "Säg något",
|
||||
"shared_album_activity_remove_content": "Vill du ta bort den här aktiviteten?",
|
||||
"shared_album_activity_remove_title": "Ta Bort Aktivitet",
|
||||
"shared_album_activity_setting_subtitle": "Låt andra svara",
|
||||
"shared_album_activity_setting_title": "Kommentarer & gillamarkeringar",
|
||||
"shared_album_section_people_action_error": "Fel vid lämnande/borttagning från album",
|
||||
"shared_album_section_people_action_leave": "Ta bort användare från album",
|
||||
"shared_album_section_people_action_remove_user": "Ta bort användare från album",
|
||||
"shared_album_section_people_owner_label": "Ägare",
|
||||
"shared_album_section_people_title": "PERSONER",
|
||||
"shared_album_activities_input_disable": "Comment is disabled",
|
||||
"shared_album_activities_input_hint": "Say something",
|
||||
"shared_album_activity_remove_content": "Do you want to delete this activity?",
|
||||
"shared_album_activity_remove_title": "Delete Activity",
|
||||
"shared_album_activity_setting_subtitle": "Let others respond",
|
||||
"shared_album_activity_setting_title": "Comments & likes",
|
||||
"shared_album_section_people_action_error": "Error leaving/removing from album",
|
||||
"shared_album_section_people_action_leave": "Remove user from album",
|
||||
"shared_album_section_people_action_remove_user": "Remove user from album",
|
||||
"shared_album_section_people_owner_label": "Owner",
|
||||
"shared_album_section_people_title": "PEOPLE",
|
||||
"share_dialog_preparing": "Förbereder...",
|
||||
"shared_link_app_bar_title": "Delade Länkar",
|
||||
"shared_link_clipboard_copied_massage": "Kopierad till urklipp",
|
||||
"shared_link_clipboard_text": "Länk: {}\nLösenord: {}",
|
||||
"shared_link_create_app_bar_title": "Skapa länk att dela",
|
||||
"shared_link_create_error": "Fel vid skapande av delad länk",
|
||||
"shared_link_create_info": "Tillåt alla med länken att se valda foton",
|
||||
"shared_link_create_submit_button": "Skapa länk",
|
||||
"shared_link_edit_allow_download": "Tillåt publik användare att ladda ner",
|
||||
"shared_link_edit_allow_upload": "Tillåt publik användare att ladda upp",
|
||||
"shared_link_edit_app_bar_title": "Redigera länk",
|
||||
"shared_link_edit_change_expiry": "Ändra utgångstid",
|
||||
"shared_link_edit_description": "Beskrivning",
|
||||
"shared_link_edit_description_hint": "Lägg till delnings-beskrivningen",
|
||||
"shared_link_edit_expire_after": "Går ut efter",
|
||||
"shared_link_edit_expire_after_option_day": "1 dag",
|
||||
"shared_link_edit_expire_after_option_days": "{} dagar",
|
||||
"shared_link_edit_expire_after_option_hour": "1 timme",
|
||||
"shared_link_edit_expire_after_option_hours": "{} timmar",
|
||||
"shared_link_edit_expire_after_option_minute": "1 minut",
|
||||
"shared_link_edit_expire_after_option_minutes": "{} minuter",
|
||||
"shared_link_edit_expire_after_option_months": "{} månader",
|
||||
"shared_link_edit_expire_after_option_never": "Aldrig",
|
||||
"shared_link_edit_expire_after_option_year": "{} år",
|
||||
"shared_link_edit_password": "Lösenord",
|
||||
"shared_link_edit_password_hint": "Ange delningslösenordet",
|
||||
"shared_link_edit_show_meta": "Visa metadata",
|
||||
"shared_link_edit_submit_button": "Uppdatera länk",
|
||||
"shared_link_empty": "Du har inga delade länkar",
|
||||
"shared_link_error_server_url_fetch": "Kan inte hämta server-urlen",
|
||||
"shared_link_expired": "Gått ut",
|
||||
"shared_link_expires_day": "Går ut om {} dag",
|
||||
"shared_link_expires_days": "Går ut om {} dagar",
|
||||
"shared_link_expires_hour": "Går ut om {} timme",
|
||||
"shared_link_expires_hours": "Går ut om {} timmar",
|
||||
"shared_link_expires_minute": "Går ut om {} minut",
|
||||
"shared_link_expires_minutes": "Går ut om {} minuter",
|
||||
"shared_link_expires_never": "Går ut ∞",
|
||||
"shared_link_expires_second": "Går ut om {} sekunder",
|
||||
"shared_link_expires_seconds": "Går ut om {} sekunder",
|
||||
"shared_link_individual_shared": "Individdelad",
|
||||
"shared_link_info_chip_download": "Ladda ner",
|
||||
"shared_link_app_bar_title": "Shared Links",
|
||||
"shared_link_clipboard_copied_massage": "Copied to clipboard",
|
||||
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
|
||||
"shared_link_create_app_bar_title": "Create link to share",
|
||||
"shared_link_create_error": "Error while creating shared link",
|
||||
"shared_link_create_info": "Let anyone with the link see the selected photo(s)",
|
||||
"shared_link_create_submit_button": "Create link",
|
||||
"shared_link_edit_allow_download": "Allow public user to download",
|
||||
"shared_link_edit_allow_upload": "Allow public user to upload",
|
||||
"shared_link_edit_app_bar_title": "Edit link",
|
||||
"shared_link_edit_change_expiry": "Change expiration time",
|
||||
"shared_link_edit_description": "Description",
|
||||
"shared_link_edit_description_hint": "Enter the share description",
|
||||
"shared_link_edit_expire_after": "Expire after",
|
||||
"shared_link_edit_expire_after_option_day": "1 day",
|
||||
"shared_link_edit_expire_after_option_days": "{} days",
|
||||
"shared_link_edit_expire_after_option_hour": "1 hour",
|
||||
"shared_link_edit_expire_after_option_hours": "{} hours",
|
||||
"shared_link_edit_expire_after_option_minute": "1 minute",
|
||||
"shared_link_edit_expire_after_option_minutes": "{} minutes",
|
||||
"shared_link_edit_expire_after_option_months": "{} months",
|
||||
"shared_link_edit_expire_after_option_never": "Never",
|
||||
"shared_link_edit_expire_after_option_year": "{} year",
|
||||
"shared_link_edit_password": "Password",
|
||||
"shared_link_edit_password_hint": "Enter the share password",
|
||||
"shared_link_edit_show_meta": "Show metadata",
|
||||
"shared_link_edit_submit_button": "Update link",
|
||||
"shared_link_empty": "You don't have any shared links",
|
||||
"shared_link_error_server_url_fetch": "Cannot fetch the server url",
|
||||
"shared_link_expired": "Expired",
|
||||
"shared_link_expires_day": "Expires in {} day",
|
||||
"shared_link_expires_days": "Expires in {} days",
|
||||
"shared_link_expires_hour": "Expires in {} hour",
|
||||
"shared_link_expires_hours": "Expires in {} hours",
|
||||
"shared_link_expires_minute": "Expires in {} minute",
|
||||
"shared_link_expires_minutes": "Expires in {} minutes",
|
||||
"shared_link_expires_never": "Expires ∞",
|
||||
"shared_link_expires_second": "Expires in {} second",
|
||||
"shared_link_expires_seconds": "Expires in {} seconds",
|
||||
"shared_link_individual_shared": "Individual shared",
|
||||
"shared_link_info_chip_download": "Download",
|
||||
"shared_link_info_chip_metadata": "EXIF",
|
||||
"shared_link_info_chip_upload": "Ladda upp",
|
||||
"shared_link_manage_links": "Hantera Delade länkar",
|
||||
"shared_link_public_album": "Publikt album",
|
||||
"share_done": "Klart",
|
||||
"shared_link_info_chip_upload": "Upload",
|
||||
"shared_link_manage_links": "Manage Shared links",
|
||||
"shared_link_public_album": "Public album",
|
||||
"share_done": "Done",
|
||||
"share_invite": "Bjuder in till album",
|
||||
"sharing_page_album": "Delade album",
|
||||
"sharing_page_description": "Skapa delade album för att dela foton och video med personer i ditt nätverk.",
|
||||
"sharing_page_empty_list": "TOM LISTA",
|
||||
"sharing_silver_appbar_create_shared_album": "Skapa delat album",
|
||||
"sharing_silver_appbar_shared_links": "Delada länkar",
|
||||
"sharing_silver_appbar_shared_links": "Shared links",
|
||||
"sharing_silver_appbar_share_partner": "Dela med partner",
|
||||
"tab_controller_nav_library": "Bibliotek",
|
||||
"tab_controller_nav_photos": "Bilder",
|
||||
@@ -495,30 +491,30 @@
|
||||
"theme_setting_theme_title": "Tema",
|
||||
"theme_setting_three_stage_loading_subtitle": "Trestegsladdning kan öka prestandan, men kan också leda till signifikant högre nätverksbelastning",
|
||||
"theme_setting_three_stage_loading_title": "Aktivera trestegsladdning",
|
||||
"translated_text_options": "Val",
|
||||
"trash_page_delete": "Ta Bort",
|
||||
"trash_page_delete_all": "Ta Bort Alla",
|
||||
"trash_page_empty_trash_btn": "Töm papperskorg",
|
||||
"trash_page_empty_trash_dialog_content": "Vill du ta bort dina slängda resurser? De kommer att tas bort permanent från Immich",
|
||||
"translated_text_options": "Options",
|
||||
"trash_page_delete": "Delete",
|
||||
"trash_page_delete_all": "Delete All",
|
||||
"trash_page_empty_trash_btn": "Empty trash",
|
||||
"trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich",
|
||||
"trash_page_empty_trash_dialog_ok": "Ok",
|
||||
"trash_page_info": "Saker i papperskorgen tas bort permanent efter {} dagar",
|
||||
"trash_page_no_assets": "Inga slängda resurser",
|
||||
"trash_page_restore": "Återställ",
|
||||
"trash_page_restore_all": "Återställ Alla",
|
||||
"trash_page_select_assets_btn": "Välj resurser",
|
||||
"trash_page_select_btn": "Välj",
|
||||
"trash_page_title": "Papperskorg ({})",
|
||||
"upload_dialog_cancel": "Avbryt",
|
||||
"upload_dialog_info": "Vill du säkerhetskopiera de valda resurserna till servern?",
|
||||
"upload_dialog_ok": "Ladda Upp",
|
||||
"upload_dialog_title": "Ladda Upp Resurs",
|
||||
"trash_page_info": "Trashed items will be permanently deleted after {} days",
|
||||
"trash_page_no_assets": "No trashed assets",
|
||||
"trash_page_restore": "Restore",
|
||||
"trash_page_restore_all": "Restore All",
|
||||
"trash_page_select_assets_btn": "Select assets",
|
||||
"trash_page_select_btn": "Select",
|
||||
"trash_page_title": "Trash ({})",
|
||||
"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": "Bekräfta",
|
||||
"version_announcement_overlay_release_notes": "versionsinformation",
|
||||
"version_announcement_overlay_text_1": "Hej vännen, det finns en ny version av",
|
||||
"version_announcement_overlay_text_2": ". Ta gärna din tid att besöka ",
|
||||
"version_announcement_overlay_text_3": " för att se till att din docker-compose och .env-fil är uppdaterad för att undvika felkonfiguration, speciellt om du använder WatchTower eller liknande mekanism som automatiskt uppdaterar din container",
|
||||
"version_announcement_overlay_title": "Ny serverversion finns tillgänglig \uD83C\uDF89",
|
||||
"viewer_remove_from_stack": "Ta bort från Stapeln",
|
||||
"viewer_stack_use_as_main_asset": "Använd som Huvudresurs",
|
||||
"viewer_unstack": "Stapla Av"
|
||||
"version_announcement_overlay_title": "Ny serverversion är tillgänglig \uD83C\uDF89",
|
||||
"viewer_remove_from_stack": "Remove from Stack",
|
||||
"viewer_stack_use_as_main_asset": "Use as Main Asset",
|
||||
"viewer_unstack": "Un-Stack"
|
||||
}
|
||||
@@ -142,7 +142,7 @@
|
||||
"common_add_to_album": "เพิ่มเข้าอัลบั้ม",
|
||||
"common_change_password": "เปลี่ยนรหัสผ่าน",
|
||||
"common_create_new_album": "สร้างอัลบั้มใหม่",
|
||||
"common_server_error": "กรุณาตรวจสอบการเชื่อมต่ออินเทอร์เน็ต ให้แน่ใจว่าเซิร์ฟเวอร์สามารถเข้าถึงได้ และเวอร์ชันแอพกับเซิร์ฟเวอร์เข้ากันได้",
|
||||
"common_server_error": "กรุณาตรวจสอบการเชื่อมต่ออินเทอร์เน็ต ให้แน่ใจว่าเซิร์ฟเวอร์สามารถเข้าถึงได้ และเวอร์ชั่นแอพกับเซิร์ฟเวอร์เข้ากันได้",
|
||||
"common_shared": "แชร์",
|
||||
"control_bottom_app_bar_add_to_album": "เพิ่มลงอัลบั้ม",
|
||||
"control_bottom_app_bar_album_info": "{} รายการ",
|
||||
@@ -201,8 +201,8 @@
|
||||
"experimental_settings_title": "ทดลอง",
|
||||
"favorites_page_no_favorites": "ไม่พบทรัพยากรในรายการโปรด",
|
||||
"favorites_page_title": "รายการโปรด",
|
||||
"haptic_feedback_switch": "เปิดการตอบสนองแบบสัมผัส",
|
||||
"haptic_feedback_title": "การตอบสนองแบบสัมผัส",
|
||||
"haptic_feedback_switch": "Enable haptic feedback",
|
||||
"haptic_feedback_title": "Haptic Feedback",
|
||||
"home_page_add_to_album_conflicts": "เพิ่ม {added} ทรัพยากรเข้าอัลบั้ม {album}. {failed} ทรัพยากรอยู่ในอัลบั้มอยู่แล้ว",
|
||||
"home_page_add_to_album_err_local": " ไม่สามารถเพิ่มทรัพยากรบนเครื่องเข้าอัลบั้ม กำลังข้าม",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
@@ -218,7 +218,7 @@
|
||||
"home_page_share_err_local": "ไม่สามารถแชร์ผ่านลิงค์ได้ กำลังข้าม",
|
||||
"home_page_upload_err_limit": "สามารถอัพโหลดได้มากสุดครั้งละ 30 ทรัพยากร กำลังข้าม",
|
||||
"image_viewer_page_state_provider_download_error": "ดาวน์โหลดผิดพลาด",
|
||||
"image_viewer_page_state_provider_download_started": "ดาวน์โหลดเริ่มต้น",
|
||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "ดาวน์โหลดสำเร็จ",
|
||||
"image_viewer_page_state_provider_share_error": "แชร์ผิดพลาด",
|
||||
"library_page_albums": "Albums",
|
||||
@@ -265,8 +265,8 @@
|
||||
"login_form_server_error": "ไม่สามารถติดต่อกับเซิร์ฟเวอร์",
|
||||
"login_password_changed_error": "เกิดข้อผิดพลาดขณะเปลี่ยนรหัสผ่าน",
|
||||
"login_password_changed_success": "เปลี่ยนรหัสผ่านสำเร็จ",
|
||||
"map_assets_in_bound": "{} รูปภาพ",
|
||||
"map_assets_in_bounds": "{} รูปภาพ",
|
||||
"map_assets_in_bound": "{} photo",
|
||||
"map_assets_in_bounds": "{} photos",
|
||||
"map_cannot_get_user_location": "ไม่สามารถหาตำแหน่งผู้ใช้งานได้",
|
||||
"map_location_dialog_cancel": "ยกเลิก",
|
||||
"map_location_dialog_yes": "ใช่",
|
||||
@@ -298,8 +298,8 @@
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"motion_photos_page_title": "ภาพเคลื่อนไหว",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping",
|
||||
"multiselect_grid_edit_gps_err_read_only": "ไม่สามารถแก้",
|
||||
"no_assets_to_show": "ไม่มีทรัพยากรให้แสดง",
|
||||
"multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping",
|
||||
"no_assets_to_show": "No assets to show",
|
||||
"notification_permission_dialog_cancel": "ยกเลิก",
|
||||
"notification_permission_dialog_content": "เพื่อเปิดการแจ้งเตือน เข้าตั้งค่าแล้วกดอนุญาต",
|
||||
"notification_permission_dialog_settings": "ตั้งค่า",
|
||||
@@ -382,18 +382,17 @@
|
||||
"select_additional_user_for_sharing_page_suggestions": "ข้อเสนอแนะ",
|
||||
"select_user_for_sharing_page_err_album": "สร้างอัลบั้มล้มเหลว",
|
||||
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
||||
"server_info_box_app_version": "เวอร์ชันแอพ",
|
||||
"server_info_box_app_version": "เวอร์ชั่นแอพ",
|
||||
"server_info_box_latest_release": "เวอร์ชันล่าสุด",
|
||||
"server_info_box_server_url": "URL เซิร์ฟเวอร์",
|
||||
"server_info_box_server_version": "เวอร์ชันเซิร์ฟเวอร์",
|
||||
"server_info_box_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": "โหลดรูปต้นฉบับ",
|
||||
"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": "โหลดรูปภาพตัวอย่าง",
|
||||
"setting_image_viewer_title": "รูปภาพ",
|
||||
"setting_languages_apply": "บันทึก",
|
||||
"setting_languages_title": "ภาษา",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "แจ้งการสำรองข้อมูลในเบื้องหลังล้มเหลว: {}",
|
||||
"setting_notifications_notify_hours": "{} ชั่วโมง",
|
||||
"setting_notifications_notify_immediately": "immediately",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "แสดงสถานะการสำรองข้อมูลในเบื้องหลังทั้งหมด",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "กรุณารีสตาร์ท Immmich เพื่อใช้การตั้งค่า",
|
||||
"setting_video_viewer_looping_subtitle": "เปิดเพื่อให้วิดีโอวนลูปในที่ดูรายละเอียด",
|
||||
"setting_video_viewer_looping_title": "วนลูป",
|
||||
"setting_video_viewer_title": "วิดีโอ",
|
||||
"share_add": "เพิ่ม",
|
||||
"share_add_photos": "เพิ่มรูปภาพ",
|
||||
"share_add_title": "เพิ่มชื่อ",
|
||||
"share_assets_selected": "{} ถูกเลือก",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "สร้างอัลบั้ม",
|
||||
"shared_album_activities_input_disable": "คอมเมนต์ถูกปิด",
|
||||
"shared_album_activities_input_hint": "พูดอะไรสักอย่าง",
|
||||
@@ -448,9 +444,9 @@
|
||||
"shared_link_edit_expire_after_option_hours": "{} hours",
|
||||
"shared_link_edit_expire_after_option_minute": "1 minute",
|
||||
"shared_link_edit_expire_after_option_minutes": "{} minutes",
|
||||
"shared_link_edit_expire_after_option_months": "{} เดือน",
|
||||
"shared_link_edit_expire_after_option_months": "{} months",
|
||||
"shared_link_edit_expire_after_option_never": "ไม่กำหนด",
|
||||
"shared_link_edit_expire_after_option_year": "{} ปี",
|
||||
"shared_link_edit_expire_after_option_year": "{} year",
|
||||
"shared_link_edit_password": "รหัสผ่าน",
|
||||
"shared_link_edit_password_hint": "กรอกรหัสผ่านแชร์",
|
||||
"shared_link_edit_show_meta": "แสดง metadata",
|
||||
@@ -476,7 +472,7 @@
|
||||
"share_done": "เสร็จ",
|
||||
"share_invite": "เชิญเข้าอัลบั้ม",
|
||||
"sharing_page_album": "อัลบั้มที่แชร์",
|
||||
"sharing_page_description": "สร้างอัลบั้มที่แชร์เพื่อแชร์รูปภาพและวิดีโอให้กับคนบนเครือข่ายคุณ",
|
||||
"sharing_page_description": "สร้างอัลบั้มที่แชร์เพื่อแชร์รูปภาพและวิดีโอให้กับคนบนเครื่อข่ายคุณ",
|
||||
"sharing_page_empty_list": "รายการว่างเปล่า",
|
||||
"sharing_silver_appbar_create_shared_album": "สร้างอัลบั้มแชร์",
|
||||
"sharing_silver_appbar_shared_links": "ลิงก์ที่แชร์",
|
||||
@@ -512,12 +508,12 @@
|
||||
"upload_dialog_info": "คุณต้องการอัพโหลดทรัพยากรดังกล่าวบนเซิร์ฟเวอร์หรือไม่?",
|
||||
"upload_dialog_ok": "อัปโหลด",
|
||||
"upload_dialog_title": "อัปโหลดทรัพยากร",
|
||||
"version_announcement_overlay_ack": "รับทราบ",
|
||||
"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": "มีเวอร์ชันใหม่สำหรับเซิร์ฟเวอร์ \uD83C\uDF89",
|
||||
"version_announcement_overlay_title": "มีเวอร์ชั่นใหม่สำหรับเซิร์ฟเวอร์ \uD83C\uDF89",
|
||||
"viewer_remove_from_stack": "เอาออกจากที่ซ้อน",
|
||||
"viewer_stack_use_as_main_asset": "ใช้เป็นทรัพยากรหลัก",
|
||||
"viewer_unstack": "หยุดซ้อน"
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Завантажувати оригінальне зображення",
|
||||
"setting_image_viewer_preview_subtitle": "Увімкніть для завантаження зображень середньої роздільної здатності.\nВимкніть для безпосереднього завантаження оригіналу або використовувати лише мініатюру.",
|
||||
"setting_image_viewer_preview_title": "Завантажувати зображення попереднього перегляду",
|
||||
"setting_image_viewer_title": "Зображення",
|
||||
"setting_languages_apply": "Застосувати",
|
||||
"setting_languages_title": "Мова",
|
||||
"setting_notifications_notify_failures_grace_period": "Повідомити про помилки фонового резервного копіювання: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Показати загальний хід фонового резервного копіювання",
|
||||
"setting_pages_app_bar_settings": "Налаштування",
|
||||
"settings_require_restart": "Перезавантажте програму для застосування цього налаштування",
|
||||
"setting_video_viewer_looping_subtitle": "Увімкнути циклічне відтворення відео",
|
||||
"setting_video_viewer_looping_title": "Циклічне відтворення",
|
||||
"setting_video_viewer_title": "Відео",
|
||||
"share_add": "Додати",
|
||||
"share_add_photos": "Додати знімки",
|
||||
"share_add_title": "Додати назву",
|
||||
|
||||
@@ -218,7 +218,7 @@
|
||||
"home_page_share_err_local": "Không thể chia sẻ ảnh cục bộ qua liên kết, bỏ qua",
|
||||
"home_page_upload_err_limit": "Chỉ có thể tải lên tối đa 30 ảnh cùng một lúc, bỏ qua",
|
||||
"image_viewer_page_state_provider_download_error": "Tải xuống không thành công",
|
||||
"image_viewer_page_state_provider_download_started": "Đã bắt đầu tải xuống",
|
||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "Tải xuống thành công",
|
||||
"image_viewer_page_state_provider_share_error": "Chia sẻ không thành công",
|
||||
"library_page_albums": "Album",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Tải ảnh gốc",
|
||||
"setting_image_viewer_preview_subtitle": "Bật để tải ảnh độ phân giải trung bình. Tắt để tải trực tiếp ảnh gốc hoặc chỉ sử dụng hình thu nhỏ.",
|
||||
"setting_image_viewer_preview_title": "Tải ảnh xem trước",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Áp dụng",
|
||||
"setting_languages_title": "Ngôn ngữ",
|
||||
"setting_notifications_notify_failures_grace_period": "Thông báo sao lưu nền thất bại: {}",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "Hiện thị toàn bộ sao lưu nền đang thực hiện",
|
||||
"setting_pages_app_bar_settings": "Cài đặt",
|
||||
"settings_require_restart": "Vui lòng khởi động lại Immich để áp dụng cài đặt này",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Thêm",
|
||||
"share_add_photos": "Thêm ảnh",
|
||||
"share_add_title": "Thêm tiêu đề",
|
||||
"share_assets_selected": "{} đã chọn",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "Tạo album",
|
||||
"shared_album_activities_input_disable": "Nhận xét hiện đã tắt",
|
||||
"shared_album_activities_input_hint": "Nói điều gì đó",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "加载原图",
|
||||
"setting_image_viewer_preview_subtitle": "启用以加载中等质量的图像,禁用以加载原图或缩略图。",
|
||||
"setting_image_viewer_preview_title": "加载预览图",
|
||||
"setting_image_viewer_title": "图片",
|
||||
"setting_languages_apply": "应用",
|
||||
"setting_languages_title": "语言",
|
||||
"setting_notifications_notify_failures_grace_period": "后台备份失败通知:{}",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "显示后台备份总进度",
|
||||
"setting_pages_app_bar_settings": "设置",
|
||||
"settings_require_restart": "请重启 Immich 以使设置生效",
|
||||
"setting_video_viewer_looping_subtitle": "对播放窗口中的视频开启循环播放。",
|
||||
"setting_video_viewer_looping_title": "循环播放",
|
||||
"setting_video_viewer_title": "视频",
|
||||
"share_add": "添加",
|
||||
"share_add_photos": "添加项目",
|
||||
"share_add_title": "添加标题",
|
||||
"share_assets_selected": "{} 已选择",
|
||||
"share_assets_selected": "{}已选择",
|
||||
"share_create_album": "创建相册",
|
||||
"shared_album_activities_input_disable": "评论已禁用",
|
||||
"shared_album_activities_input_hint": "说些什么",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "加载原图",
|
||||
"setting_image_viewer_preview_subtitle": "启用以加载中等质量的图像,禁用以加载原图或缩略图。",
|
||||
"setting_image_viewer_preview_title": "加载预览图",
|
||||
"setting_image_viewer_title": "图片",
|
||||
"setting_languages_apply": "应用",
|
||||
"setting_languages_title": "语言",
|
||||
"setting_notifications_notify_failures_grace_period": "后台备份失败通知:{}",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "显示后台备份总进度",
|
||||
"setting_pages_app_bar_settings": "设置",
|
||||
"settings_require_restart": "请重启 Immich 以使设置生效",
|
||||
"setting_video_viewer_looping_subtitle": "对播放窗口中的视频开启循环播放。",
|
||||
"setting_video_viewer_looping_title": "循环播放",
|
||||
"setting_video_viewer_title": "视频",
|
||||
"share_add": "添加",
|
||||
"share_add_photos": "添加项目",
|
||||
"share_add_title": "添加标题",
|
||||
"share_assets_selected": "{} 已选择",
|
||||
"share_assets_selected": "{}已选择",
|
||||
"share_create_album": "创建相册",
|
||||
"shared_album_activities_input_disable": "评论已禁用",
|
||||
"shared_album_activities_input_hint": "说些什么",
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
"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 backed up",
|
||||
"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",
|
||||
@@ -391,7 +391,6 @@
|
||||
"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_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
|
||||
@@ -19,7 +19,7 @@ platform :ios do
|
||||
desc "iOS Beta"
|
||||
lane :beta do
|
||||
increment_version_number(
|
||||
version_number: "1.106.0"
|
||||
version_number: "1.105.1"
|
||||
)
|
||||
increment_build_number(
|
||||
build_number: latest_testflight_build_number + 1,
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/widgets/forms/login/login_form.dart';
|
||||
import 'package:immich_mobile/widgets/forms/login_form.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ class AssetService {
|
||||
const int chunkSize = 10000;
|
||||
try {
|
||||
final List<Asset> allAssets = [];
|
||||
DateTime? lastCreationDate;
|
||||
String? lastId;
|
||||
// will break on error or once all assets are loaded
|
||||
while (true) {
|
||||
@@ -108,17 +109,15 @@ class AssetService {
|
||||
limit: chunkSize,
|
||||
updatedUntil: until,
|
||||
lastId: lastId,
|
||||
lastCreationDate: lastCreationDate,
|
||||
userId: user.id,
|
||||
);
|
||||
log.fine("Requesting $chunkSize assets from $lastId");
|
||||
final List<AssetResponseDto>? assets =
|
||||
await _apiService.syncApi.getFullSyncForUser(dto);
|
||||
if (assets == null) return null;
|
||||
log.fine(
|
||||
"Received ${assets.length} assets from ${assets.firstOrNull?.id} to ${assets.lastOrNull?.id}",
|
||||
);
|
||||
allAssets.addAll(assets.map(Asset.remote));
|
||||
if (assets.length != chunkSize) break;
|
||||
if (assets.isEmpty) break;
|
||||
lastCreationDate = assets.last.fileCreatedAt;
|
||||
lastId = assets.last.id;
|
||||
}
|
||||
return allAssets;
|
||||
|
||||
@@ -73,7 +73,7 @@ String getThumbnailUrlForRemoteId(
|
||||
final String id, {
|
||||
AssetMediaSize type = AssetMediaSize.thumbnail,
|
||||
}) {
|
||||
return '${Store.get(StoreKey.serverEndpoint)}/assets/$id/thumbnail?size=${type.value}';
|
||||
return '${Store.get(StoreKey.serverEndpoint)}/assets/$id/thumbnail?format=${type.value}';
|
||||
}
|
||||
|
||||
String getFaceThumbnailUrl(final String personId) {
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
String? getVersionCompatibilityMessage(
|
||||
int appMajor,
|
||||
int appMinor,
|
||||
int serverMajor,
|
||||
int serverMinor,
|
||||
) {
|
||||
if (serverMajor != appMajor) {
|
||||
return 'Your app major version is not compatible with the server!';
|
||||
}
|
||||
|
||||
// Add latest compat info up top
|
||||
if (serverMinor < 106 && appMinor >= 106) {
|
||||
return 'Your app minor version is not compatible with the server! Please update your server to version v1.106.0 or newer to login';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class EmailInput extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final FocusNode? focusNode;
|
||||
final Function()? onSubmit;
|
||||
|
||||
const EmailInput({
|
||||
super.key,
|
||||
required this.controller,
|
||||
this.focusNode,
|
||||
this.onSubmit,
|
||||
});
|
||||
|
||||
String? _validateInput(String? email) {
|
||||
if (email == null || email == '') return null;
|
||||
if (email.endsWith(' ')) return 'login_form_err_trailing_whitespace'.tr();
|
||||
if (email.startsWith(' ')) return 'login_form_err_leading_whitespace'.tr();
|
||||
if (email.contains(' ') || !email.contains('@')) {
|
||||
return 'login_form_err_invalid_email'.tr();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextFormField(
|
||||
autofocus: true,
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'login_form_label_email'.tr(),
|
||||
border: const OutlineInputBorder(),
|
||||
hintText: 'login_form_email_hint'.tr(),
|
||||
hintStyle: const TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
validator: _validateInput,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
autofillHints: const [AutofillHints.email],
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
onFieldSubmitted: (_) => onSubmit?.call(),
|
||||
focusNode: focusNode,
|
||||
textInputAction: TextInputAction.next,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LoadingIcon extends StatelessWidget {
|
||||
const LoadingIcon({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.only(top: 18.0),
|
||||
child: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: FittedBox(
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||