Compare commits

..

1 Commits

Author SHA1 Message Date
Alex Tran
ad3e92fff0 feat(server): license verification 2024-06-05 01:56:37 -05:00
292 changed files with 2951 additions and 28810 deletions

View File

@@ -4,7 +4,6 @@
design/
docker/
!docker/scripts
docs/
e2e/
fastlane/

23
.github/labeler.yml vendored
View File

@@ -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/**

View File

@@ -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

View File

@@ -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 }}

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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
View File

@@ -47,14 +47,14 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.106.1",
"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"
}
},

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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']

View File

@@ -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"

View File

@@ -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" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

View File

@@ -10,59 +10,6 @@ Viewing and modifying the system settings is restricted to the Administrator.
You can always return to the default settings by clicking the `Reset to default` button.
:::
## Authentication Settings
Manage password, OAuth, and other authentication settings
### OAuth Authentication
Immich supports OAuth Authentication. Read more about this feature and its configuration [here](/docs/administration/oauth).
### Password Authentication
The administrator can choose to disable login with username and password for the entire instance. This means that **no one**, including the system administrator, will be able to log using this method. If [OAuth Authentication](/docs/administration/oauth) is also disabled, no users will be able to login using **any** method. Changing this setting does not affect existing sessions, just new login attempts.
:::tip
You can always use the [Server CLI](/docs/administration/server-commands) to re-enable password login.
:::
## Image Settings (thumbnails and previews)
- Thumbnails - Used in the main timeline.
- Previews - Used in the asset viewer.
By default Immich creates 3 thumbnails for each asset,
Blurred (thumbhash) , Small - thumbnails (webp) , and Large - previews (jpeg/webp), using these settings you can change the quality for the thumbnails and previews files that are created.
**Thumbnail format**
Allows you to choose the type of format you want for the Thumbnail images, Webp produces smaller files than jpeg, but is slower to encode.
:::tip
You can read in detail about the advantages and disadvantages of using webp over jpeg on [Adobe's website](https://www.adobe.com/creativecloud/file-types/image/raster/webp-file.html)
:::
**Thumbnail resolution**
Used when viewing groups of photos (main timeline, album view, etc.). Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness.
**Preview format**
Allows you to choose the type of format you want for the Preview images, Webp produces smaller files than jpeg, but is slower to encode.
**Preview resolution**
Used when viewing a single photo and for machine learning. Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness.
**Quality**
Image quality from 1-100. Higher is better for quality but produces larger files, this option affects the Preview and Thumbnail images.
**Prefer wide gamut**
Use Display P3 for thumbnails. This better preserves the vibrance of images with wide colorspaces, but images may appear differently on old devices with an old browser version. sRGB images are kept as sRGB to avoid color shifts.
**Prefer embedded preview**
Use embedded previews in RAW photos as the input to image processing when available. This can produce more accurate colors for some images, but the quality of the preview is camera-dependent and the image may have more compression artifacts.
:::tip
The default resolution for Large thumbnails can be lowered from 1440p (default) to 1080p or 720p to save storage space.
:::
## Job Settings
Using these settings, you can determine the amount of work that will run concurrently for each task in microservices. Some tasks can be set to higher values on computers with powerful hardware and storage with good I/O capabilities.
@@ -72,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.
@@ -145,9 +87,17 @@ The map can be adjusted via [OpenMapTiles](https://openmaptiles.org/styles/) for
Immich supports [Reverse Geocoding](/docs/features/reverse-geocoding) using data from the [GeoNames](https://www.geonames.org/) geographical database.
## Notification Settings
## OAuth Authentication
SMTP server setup, for user creation notifications, new albums, etc. More information can be found [here](/docs/administration/email-notification)
Immich supports OAuth Authentication. Read more about this feature and its configuration [here](/docs/administration/oauth).
## Password Authentication
The administrator can choose to disable login with username and password for the entire instance. This means that **no one**, including the system administrator, will be able to log using this method. If [OAuth Authentication](/docs/administration/oauth) is also disabled, no users will be able to login using **any** method. Changing this setting does not affect existing sessions, just new login attempts.
:::tip
You can always use the [Server CLI](/docs/administration/server-commands) to re-enable password login.
:::
## Server Settings
@@ -175,6 +125,27 @@ p {
}
```
## Thumbnail Settings
By default Immich creates 3 thumbnails for each asset,
Blurred (thumbhash) , Small (webp) , and Large (jpeg), using these settings you can change the quality for the thumbnail files that are created.
**Small thumbnail resolution**
Used when viewing groups of photos (main timeline, album view, etc.). Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness.
**Large thumbnail resolution**
Used when viewing a single photo and for machine learning. Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness.
**Quality**
Thumbnail quality from 1-100. Higher is better for quality but produces larger files.
**Prefer wide gamut**
Use display p3 for thumbnails. This better preserves the vibrance of images with wide color spaces, but images may appear differently on old devices with an old browser version. Srgb images are kept as srgb to avoid color shifts.
:::tip
The default resolution for Large thumbnails can be lowered from 1440p (default) to 1080p or 720p to save storage space.
:::
## Trash Settings
In the system administrator's option to set a trash for deleted files, these files will remain in the trash until the deletion date 30 days (default) or as defined by the system administrator.

View File

@@ -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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -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" />

View File

@@ -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.

View File

@@ -94,10 +94,6 @@ const config = {
srcDark: 'img/immich-logo-inline-dark.png',
},
items: [
{
type: 'custom-versionSwitcher',
position: 'right',
},
{
to: '/docs/overview/introduction',
position: 'right',

View File

@@ -1,59 +0,0 @@
import '@docusaurus/theme-classic/lib/theme/Unlisted/index';
import { useWindowSize } from '@docusaurus/theme-common';
import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';
import React, { useEffect, useState } from 'react';
export default function VersionSwitcher(): JSX.Element {
const [versions, setVersions] = useState([]);
const [label, setLabel] = useState('Versions');
const windowSize = useWindowSize();
useEffect(() => {
async function getVersions() {
try {
let baseUrl = 'https://immich.app';
if (window.location.origin === 'http://localhost:3005') {
baseUrl = window.location.origin;
}
const response = await fetch(`${baseUrl}/archived-versions.json`);
const archiveVersions = await response.json();
const allVersions = [
{ label: 'Next', url: 'https://main.preview.immich.app' },
{ label: 'Latest', url: 'https://immich.app' },
...archiveVersions,
];
setVersions(allVersions);
const activeVersion = allVersions.find((version) => new URL(version.url).origin === window.location.origin);
if (activeVersion) {
setLabel(activeVersion.label);
}
} catch (error) {
console.error('Failed to fetch versions', error);
}
}
if (versions.length === 0) {
getVersions();
}
}, []);
return (
versions.length > 0 && (
<DropdownNavbarItem
className="navbar__item"
label={label}
mobile={windowSize === 'mobile'}
items={versions.map(({ label, url }) => ({
label,
to: url,
target: '_self',
}))}
/>
)
);
}

View File

@@ -63,14 +63,12 @@ import {
mdiVectorCombine,
mdiVideo,
mdiWeb,
mdiContentDuplicate,
} from '@mdi/js';
import Layout from '@theme/Layout';
import React from 'react';
import { Item, Timeline } from '../components/timeline';
const releases = {
'v1.106.0': new Date(2024, 5, 11),
'v1.104.0': new Date(2024, 4, 13),
'v1.103.0': new Date(2024, 3, 29),
'v1.102.0': new Date(2024, 3, 15),
@@ -218,19 +216,13 @@ const roadmap: Item[] = [
];
const milestones: Item[] = [
withRelease({
icon: mdiContentDuplicate,
title: 'Similar image detection',
description: 'Detect duplicate assets that arent exactly identical',
release: 'v1.106.0',
}),
withRelease({
icon: mdiVectorCombine,
title: 'Container consolidation',
description:
'The microservices container can be run as a worker within the server image, allowing us to remove it from the default stack.',
release: 'v1.106.0',
}),
// withRelease({
// icon: mdiVectorCombine,
// title: 'Container consolidation',
// description:
// 'The microservices container can be run as a worker within the server image, allowing us to remove it from the default stack.',
// release: 'v1.106.0',
// }),
withRelease({
icon: mdiPencil,
iconColor: 'saddlebrown',

View File

@@ -1,7 +0,0 @@
import ComponentTypes from '@theme-original/NavbarItem/ComponentTypes';
import VersionSwitcher from '@site/src/components/version-switcher';
export default {
...ComponentTypes,
'custom-versionSwitcher': VersionSwitcher,
};

View File

@@ -1,10 +0,0 @@
[
{
"label": "1.106.1",
"url": "https://1.106.1.archive.immich.app"
},
{
"label": "v1.105.1",
"url": "https://v1.105.1.archive.immich.app/"
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -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
View File

@@ -1,12 +1,12 @@
{
"name": "immich-e2e",
"version": "1.106.1",
"version": "1.105.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich-e2e",
"version": "1.106.1",
"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.1",
"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"
}
},

View File

@@ -1,6 +1,6 @@
{
"name": "immich-e2e",
"version": "1.106.1",
"version": "1.105.1",
"description": "",
"main": "index.js",
"type": "module",

View File

@@ -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 () => {

View File

@@ -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),
},
};

View File

@@ -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 });

View File

@@ -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);
});
});

View File

@@ -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,

View File

@@ -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)

View File

@@ -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}")

View File

@@ -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

View File

@@ -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

View 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),
}

View File

@@ -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),
}

View File

@@ -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)}

View File

@@ -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

View 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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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")

View File

@@ -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"])

View File

@@ -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)

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "machine-learning"
version = "1.106.1"
version = "1.105.1"
description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
readme = "README.md"

View File

@@ -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
]
}

View File

@@ -1,17 +0,0 @@
#! /usr/bin/env node
const { readFileSync, writeFileSync } = require('node:fs');
const lastVersion = process.argv[2];
if (!lastVersion) {
console.log('Usage: archive-version.js <version>');
process.exit(1);
}
const filename = './docs/static/archived-versions.json';
const oldVersions = JSON.parse(readFileSync(filename));
const newVersions = [
{ label: lastVersion, url: `https://${lastVersion}.archive.immich.app` },
...oldVersions,
];
writeFileSync(filename, JSON.stringify(newVersions, null, 2) + '\n');

View File

@@ -83,6 +83,4 @@ sed -i "s/version_number: \"$CURRENT_SERVER\"$/version_number: \"$NEXT_SERVER\"/
sed -i "s/\"android\.injected\.version\.code\" => $CURRENT_MOBILE,/\"android\.injected\.version\.code\" => $NEXT_MOBILE,/" mobile/android/fastlane/Fastfile
sed -i "s/^version: $CURRENT_SERVER+$CURRENT_MOBILE$/version: $NEXT_SERVER+$NEXT_MOBILE/" mobile/pubspec.yaml
./misc/release/archive-version.js "$NEXT_SERVER"
echo "IMMICH_VERSION=v$NEXT_SERVER" >>"$GITHUB_ENV"

View File

@@ -35,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 141,
"android.injected.version.name" => "1.106.1",
"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')

View File

@@ -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": "رفع",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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": "הגב/י משהו",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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 ",

View File

@@ -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": "タイトルを追加",

View File

@@ -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": "새 앨범제목",

View File

@@ -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",

View File

@@ -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": "",
"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": "",
"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"
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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ś",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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": "Добавить название",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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"
}

View File

@@ -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": "หยุดซ้อน"

View File

@@ -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": "Додати назву",

View File

@@ -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ì đó",

View File

@@ -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": "说些什么",

View File

@@ -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": "说些什么",

View File

@@ -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",

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