1
0
forked from Cutlery/immich

merge main

This commit is contained in:
martabal 2024-03-08 22:50:34 +01:00
commit 121e9f1f1c
No known key found for this signature in database
GPG Key ID: C00196E3148A52BD
352 changed files with 7365 additions and 6805 deletions

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.1.0
uses: docker/build-push-action@v5.2.0
with:
file: cli/Dockerfile
platforms: linux/amd64,linux/arm64

View File

@ -121,7 +121,7 @@ jobs:
fi
- name: Build and push image
uses: docker/build-push-action@v5.1.0
uses: docker/build-push-action@v5.2.0
with:
context: ${{ matrix.context }}
file: ${{ matrix.file }}

2067
cli/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,6 @@
],
"devDependencies": {
"@immich/sdk": "file:../open-api/typescript-sdk",
"@testcontainers/postgresql": "^10.7.1",
"@types/byte-size": "^8.1.0",
"@types/cli-progress": "^3.11.0",
"@types/lodash-es": "^4.17.12",

View File

@ -66,8 +66,8 @@ class Asset {
assetData: new File([await fs.openAsBlob(this.path)], basename(this.path)),
deviceAssetId: this.deviceAssetId,
deviceId: 'CLI',
fileCreatedAt: this.fileCreatedAt,
fileModifiedAt: this.fileModifiedAt,
fileCreatedAt: this.fileCreatedAt.toISOString(),
fileModifiedAt: this.fileModifiedAt.toISOString(),
isFavorite: String(false),
};
const formData = new FormData();

View File

@ -3,6 +3,7 @@ import { access, constants, mkdir, readFile, unlink, writeFile } from 'node:fs/p
import path from 'node:path';
import yaml from 'yaml';
import { ImmichApi } from './api.service';
class LoginError extends Error {
constructor(message: string) {
super(message);
@ -14,14 +15,12 @@ class LoginError extends Error {
}
export class SessionService {
readonly configDirectory!: string;
readonly authPath!: string;
constructor(configDirectory: string) {
this.configDirectory = configDirectory;
this.authPath = path.join(configDirectory, '/auth.yml');
private get authPath() {
return path.join(this.configDirectory, '/auth.yml');
}
constructor(private configDirectory: string) {}
async connect(): Promise<ImmichApi> {
let instanceUrl = process.env.IMMICH_INSTANCE_URL;
let apiKey = process.env.IMMICH_API_KEY;
@ -48,6 +47,8 @@ export class SessionService {
}
}
instanceUrl = await this.resolveApiEndpoint(instanceUrl);
const api = new ImmichApi(instanceUrl, apiKey);
const pingResponse = await api.pingServer().catch((error) => {
@ -62,7 +63,9 @@ export class SessionService {
}
async login(instanceUrl: string, apiKey: string): Promise<ImmichApi> {
console.log('Logging in...');
console.log(`Logging in to ${instanceUrl}`);
instanceUrl = await this.resolveApiEndpoint(instanceUrl);
const api = new ImmichApi(instanceUrl, apiKey);
@ -83,7 +86,7 @@ export class SessionService {
await writeFile(this.authPath, yaml.stringify({ instanceUrl, apiKey }), { mode: 0o600 });
console.log('Wrote auth info to ' + this.authPath);
console.log(`Wrote auth info to ${this.authPath}`);
return api;
}
@ -98,4 +101,18 @@ export class SessionService {
console.log('Successfully logged out');
}
private async resolveApiEndpoint(instanceUrl: string): Promise<string> {
const wellKnownUrl = new URL('.well-known/immich', instanceUrl);
try {
const wellKnown = await fetch(wellKnownUrl).then((response) => response.json());
const endpoint = new URL(wellKnown.api.endpoint, instanceUrl).toString();
if (endpoint !== instanceUrl) {
console.debug(`Discovered API at ${endpoint}`);
}
return endpoint;
} catch {
return instanceUrl;
}
}
}

View File

@ -38,7 +38,7 @@ Note: Either a manual or scheduled library scan must have been performed to iden
In all above scan methods, Immich will check if any files are missing. This can happen if files are deleted, or if they are on a storage location that is currently unavailable, like a network drive that is not mounted, or a USB drive that has been unplugged. In order to prevent accidental deletion of assets, Immich will not immediately delete an asset from the library if the file is missing. Instead, the asset will be internally marked as offline and will still be visible in the main timeline. If the file is moved back to its original location and the library is scanned again, the asset will be restored.
Finally, files can be deleted from Immich via the `Remove Offline Files` job. This job can be found by the three dots menu for the associated external storage that was configured under user account settings > libraries (the same location described at [create external libraries](#create-external-libraries)). When this job is run, any assets marked as offline will then be removed from Immich. Run this job whenever files have been deleted from the file system and you want to remove them from Immich.
Finally, files can be deleted from Immich via the `Remove Offline Files` job. This job can be found by the three dots menu for the associated external storage that was configured under Administration > Libraries (the same location described at [create external libraries](#create-external-libraries)). When this job is run, any assets marked as offline will then be removed from Immich. Run this job whenever files have been deleted from the file system and you want to remove them from Immich.
### Import Paths
@ -50,8 +50,6 @@ If the import paths are edited in a way that an external file is no longer in an
Sometimes, an external library will not scan correctly. This can happen if immich_server or immich_microservices can't access the files. Here are some things to check:
- Is the external path set correctly? Each import path must be contained in the external path.
- Make sure the external path does not contain spaces
- In the docker-compose file, are the volumes mounted correctly?
- Are the volumes identical between the `server` and `microservices` container?
- Are the import paths set correctly, and do they match the path set in docker-compose file?
@ -61,18 +59,6 @@ Sometimes, an external library will not scan correctly. This can happen if immic
To validate that Immich can reach your external library, start a shell inside the container. Run `docker exec -it immich_microservices /bin/bash` to a bash shell. If your import path is `/data/import/photos`, check it with `ls /data/import/photos`. Do the same check for the `immich_server` container. If you cannot access this directory in both the `microservices` and `server` containers, Immich won't be able to import files.
### Security Considerations
:::caution
Please read and understand this section before setting external paths, as there are important security considerations.
:::
For security purposes, each Immich user is disallowed to add external files by default. This is to prevent devastating [path traversal attacks](https://owasp.org/www-community/attacks/Path_Traversal). An admin can allow individual users to use external path feature via the `external path` setting found in the admin panel. Without the external path restriction, a user can add any image or video file on the Immich host filesystem to be imported into Immich, potentially allowing sensitive data to be accessed. If you are running Immich as root in your Docker setup (which is the default), all external file reads are done with root privileges. This is particularly dangerous if the Immich host is a shared server.
With the `external path` set, a user is restricted to accessing external files to files or directories within that path. The Immich admin should still be careful not set the external path too generously. For example, `user1` wants to read their photos in to `/home/user1`. A lazy admin sets that user's external path to `/home/` since it "gets the job done". However, that user will then be able to read all photos in `/home/user2/private-photos`, too! Please set the external path as specific as possible. If multiple folders must be added, do this using the docker volume mount feature described below.
### Exclusion Patterns
By default, all files in the import paths will be added to the library. If there are files that should not be added, exclusion patterns can be used to exclude them. Exclusion patterns are glob patterns are matched against the full file path. If a file matches an exclusion pattern, it will not be added to the library. Exclusion patterns can be added in the Scan Settings page for each library. Under the hood, Immich uses the [glob](https://www.npmjs.com/package/glob) package to match patterns, so please refer to [their documentation](https://github.com/isaacs/node-glob#glob-primer) to see what patterns are supported.
@ -90,6 +76,16 @@ This feature - currently hidden in the config file - is considered experimental
If your photos are on a network drive, automatic file watching likely won't work. In that case, you will have to rely on a periodic library refresh to pull in your changes.
#### Troubleshooting
If you encounter an `ENOSPC` error, you need to increase your file watcher limit. In sysctl, this key is called `fs.inotify.max_user_watched` and has a default value of 8192. Increase this number to a suitable value greater than the number of files you will be watching. Note that Immich has to watch all files in your import paths including any ignored files.
```
ERROR [LibraryService] Library watcher for library c69faf55-f96d-4aa0-b83b-2d80cbc27d98 encountered error: Error: ENOSPC: System limit for number of file watchers reached, watch '/media/photo.jpg'
```
In rare cases, the library watcher can hang, preventing Immich from starting up. In this case, disable the library watcher in the configuration file. If the watcher is enabled from within Immich, the app must be started without the microservices. Disable the microservices in the docker compose file, start Immich, disable the library watcher in the admin settings, close Immich, re-enable the microservices, and then Immich can be started normally.
### Nightly job
There is an automatic job that's run once a day and refreshes all modified files in all libraries as well as cleans up any libraries stuck in deletion.
@ -135,27 +131,13 @@ The `ro` flag at the end only gives read-only access to the volumes. While Immic
_Remember to bring the container `docker compose down/up` to register the changes. Make sure you can see the mounted path in the container._
:::
### Set External Path
Only an admin can do this.
- Navigate to `Administration > Users` page on the web.
- Click on the user edit button.
- Set `/mnt/media` to be the external path. This folder will only contain the three folders that we want to import, so nothing else can be accessed.
:::note
Spaces in the internal path aren't currently supported.
You must import it as:
`..:/mnt/media/my-media:ro`
instead of
`..:/mnt/media/my media:ro`
:::
### Create External Libraries
- Click on your user name in the top right corner -> Account Settings
- Click on Libraries
These actions must be performed by the Immich administrator.
- Click on Administration -> Libraries
- Click on Create External Library
- Select which user owns the library, this can not be changed later
- Click the drop-down menu on the newly created library
- Click on Rename Library and rename it to "Christmas Trip"
- Click Edit Import Paths
@ -166,7 +148,7 @@ NOTE: We have to use the `/mnt/media/christmas-trip` path and not the `/mnt/nas/
Next, we'll add an exclusion pattern to filter out raw files.
- Click the drop-down menu on the newly christmas library
- Click the drop-down menu on the newly-created Christmas library
- Click on Manage
- Click on Scan Settings
- Click on Add Exclusion Pattern

View File

@ -13,7 +13,7 @@ Run `docker exec -it immich_postgres psql immich <DB_USERNAME>` to connect to th
## Assets
:::note
The `"originalFileName"` column is the name of the uploaded file _without_ the extension.
The `"originalFileName"` column is the name of the file at time of upload, including the extension.
:::
```sql title="Find by original filename"
@ -40,6 +40,10 @@ SELECT * FROM "assets" where "livePhotoVideoId" IS NOT NULL;
SELECT "assets".* FROM "exif" LEFT JOIN "assets" ON "assets"."id" = "exif"."assetId" WHERE "exif"."assetId" IS NULL;
```
```sql title="size < 100,000 bytes, smallest to largest"
SELECT * FROM "assets" JOIN "exif" ON "assets"."id" = "exif"."assetId" WHERE "exif"."fileSizeInByte" < 100000 ORDER BY "exif"."fileSizeInByte" ASC;
```
```sql title="Without thumbnails"
SELECT * FROM "assets" WHERE "assets"."resizePath" IS NULL OR "assets"."webpPath" IS NULL;
```

View File

@ -28,6 +28,10 @@ On my computer, for example, I use this path:
EXTERNAL_PATH=/home/tenino/photos
```
:::info EXTERNAL_PATH design
The design choice to put the EXTERNAL_PATH into .env rather than put two copies of the absolute path in the yml file in order to make everything easier, so if you have two copies of the same path that have to be kept in sync, then someday later when you move the data, update only one of the paths, without everything will break mysteriously.
:::
Restart Immich.
```
@ -35,47 +39,26 @@ docker compose down
docker compose up -d
```
# Set the External Path
# Create the library
In the Immich web UI:
- click the **Administration** link in the upper right corner.
<img src={require('./img/administration-link.png').default} width="50%" title="Administration link" />
- Select the **Users** tab
<img src={require('./img/users-tab.png').default} width="50%" title="Users tab" />
- Select the **External Libraries** tab
<img src={require('./img/external-libraries.png').default} width="50%" title="External Libraries tab" />
- Select the **pencil** next to your user ID
<img src={require('./img/pencil.png').default} width="50%" title="Pencil" />
- Click the **Create Library** button
<img src={require('./img/create-external-library.png').default} width="50%" title="Create Library button" />
- Fill in the **External Path** field with `/usr/src/app/external`
<img src={require('./img/external-path.png').default} width="50%" title="External Path field" />
Notice this matches the path _inside the container_ where we mounted your photos.
The purpose of the external path field is for administrators who have multiple users
on their Immich instance. It lets you prevent other authorized users from
navigating to your external library.
# Import the library
In the Immich web UI:
- Click your user avatar in the upper-right corner (circle with your initials)
<img src={require('./img/user-avatar.png').default} width="50%" title="User avatar" />
- Click **Account Settings**
<img src={require('./img/account-settings.png').default} width="50%" title="Account Settings button" />
- Click to expand **Libraries**
<img src={require('./img/libraries-dropdown.png').default} width="50%" title="Libraries dropdown" />
- Click the **Create External Library** button
<img src={require('./img/create-external-library-button.png').default} width="50%" title="Create External Library button" />
- In the dialog, select which user should own the new library
<img src={require('./img/library-owner.png').default} width="50%" title="Library owner diaglog" />
- Click the three-dots menu and select **Edit Import Paths**
<img src={require('./img/edit-import-paths.png').default} width="50%" title="Edit Import Paths menu option" />
- Click \*_Add path_
- Click Add path
<img src={require('./img/add-path-button.png').default} width="50%" title="Add Path button" />
- Enter **/usr/src/app/external** as the path and click Add

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -128,6 +128,9 @@ The default configuration looks like this:
"theme": {
"customCss": ""
},
"user": {
"deleteDelay": 7
},
"library": {
"scan": {
"enabled": true,

View File

@ -125,7 +125,7 @@ Redis (Sentinel) URL example JSON before encoding:
## Machine Learning
| Variable | Description | Default | Services |
| :----------------------------------------------- | :----------------------------------------------------------------- | :-----------------: | :--------------- |
| :----------------------------------------------- | :------------------------------------------------------------------- | :-----------------: | :--------------- |
| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning |
| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning |
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning |
@ -134,6 +134,8 @@ Redis (Sentinel) URL example JSON before encoding:
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
| `MACHINE_LEARNING_WORKERS`<sup>\*2</sup> | Number of worker processes to spawn | `1` | machine learning |
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` | machine learning |
| `MACHINE_LEARNING_PRELOAD__CLIP` | Name of a CLIP model to be preloaded and kept in cache | | machine learning |
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION` | Name of a facial recognition model to be preloaded and kept in cache | | machine learning |
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.

View File

@ -28,6 +28,10 @@ Or before beginning app installation, [create the datasets](https://www.truenas.
Immich requires seven datasets: **library**, **pgBackup**, **pgData**, **profile**, **thumbs**, **uploads**, and **video**.
You can organize these as one parent with seven child datasets, for example `mnt/tank/immich/library`, `mnt/tank/immich/pgBackup`, and so on.
:::info Permissions
The **pgData** dataset must be owned by the user `netdata` (UID 999) for postgres to start. The other datasets must be owned by the user `root` (UID 0) or a group that includes the user `root` (UID 0) for immich to have the necessary permissions.
:::
## Installing the Immich Application
To install the **Immich** application, go to **Apps**, click **Discover Apps**, either begin typing Immich into the search field or scroll down to locate the **Immich** application widget.

View File

@ -12231,7 +12231,7 @@
"mime-format": "2.0.0",
"mime-types": "2.1.27",
"postman-url-encoder": "2.1.3",
"sanitize-html": "^2.11.0",
"sanitize-html": "^2.12.1",
"semver": "^7.5.4",
"uuid": "3.4.0"
}
@ -14762,9 +14762,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sanitize-html": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.11.0.tgz",
"integrity": "sha512-BG68EDHRaGKqlsNjJ2xUB7gpInPA8gVx/mvjO743hZaeMCZ2DwzW7xvsqZ+KNU4QKwj86HJ3uu2liISf2qBBUA==",
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.12.1.tgz",
"integrity": "sha512-Plh+JAn0UVDpBRP/xEjsk+xDCoOvMBwQUf/K+/cBAVuTbtX8bj2VB7S1sL1dssVpykqp0/KPSesHrqXtokVBpA==",
"dependencies": {
"deepmerge": "^4.2.2",
"escape-string-regexp": "^4.0.0",

31
e2e/package-lock.json generated
View File

@ -49,7 +49,6 @@
},
"devDependencies": {
"@immich/sdk": "file:../open-api/typescript-sdk",
"@testcontainers/postgresql": "^10.7.1",
"@types/byte-size": "^8.1.0",
"@types/cli-progress": "^3.11.0",
"@types/lodash-es": "^4.17.12",
@ -80,7 +79,7 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.97.0",
"version": "1.98.0",
"dev": true,
"license": "GNU Affero General Public License version 3",
"devDependencies": {
@ -924,12 +923,12 @@
}
},
"node_modules/@playwright/test": {
"version": "1.41.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz",
"integrity": "sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==",
"version": "1.42.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.1.tgz",
"integrity": "sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==",
"dev": true,
"dependencies": {
"playwright": "1.41.2"
"playwright": "1.42.1"
},
"bin": {
"playwright": "cli.js"
@ -1156,9 +1155,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.11.20",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz",
"integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==",
"version": "20.11.24",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz",
"integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
@ -3870,12 +3869,12 @@
}
},
"node_modules/playwright": {
"version": "1.41.2",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz",
"integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==",
"version": "1.42.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz",
"integrity": "sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==",
"dev": true,
"dependencies": {
"playwright-core": "1.41.2"
"playwright-core": "1.42.1"
},
"bin": {
"playwright": "cli.js"
@ -3888,9 +3887,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.41.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz",
"integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==",
"version": "1.42.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz",
"integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==",
"dev": true,
"bin": {
"playwright-core": "cli.js"

View File

@ -5,7 +5,8 @@
"main": "index.js",
"type": "module",
"scripts": {
"test": "vitest --config vitest.config.ts",
"test": "vitest --run",
"test:watch": "vitest",
"test:web": "npx playwright test",
"start:web": "npx playwright test --ui",
"format": "prettier --check .",

View File

@ -9,7 +9,7 @@ import {
} from '@immich/sdk';
import { createUserDto, uuidDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { apiUtils, app, asBearerAuth, dbUtils } from 'src/utils';
import { app, asBearerAuth, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
@ -23,12 +23,11 @@ describe('/activity', () => {
create({ activityCreateDto: dto }, { headers: asBearerAuth(accessToken || admin.accessToken) });
beforeAll(async () => {
apiUtils.setup();
await dbUtils.reset();
await utils.resetDatabase();
admin = await apiUtils.adminSetup();
nonOwner = await apiUtils.userSetup(admin.accessToken, createUserDto.user1);
asset = await apiUtils.createAsset(admin.accessToken);
admin = await utils.adminSetup();
nonOwner = await utils.userSetup(admin.accessToken, createUserDto.user1);
asset = await utils.createAsset(admin.accessToken);
album = await createAlbum(
{
createAlbumDto: {
@ -42,7 +41,7 @@ describe('/activity', () => {
});
beforeEach(async () => {
await dbUtils.reset(['activity']);
await utils.resetDatabase(['activity']);
});
describe('GET /activity', () => {

View File

@ -7,7 +7,7 @@ import {
} from '@immich/sdk';
import { createUserDto, uuidDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { apiUtils, app, asBearerAuth, dbUtils } from 'src/utils';
import { app, asBearerAuth, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
@ -29,49 +29,48 @@ describe('/album', () => {
let user3: LoginResponseDto; // deleted
beforeAll(async () => {
apiUtils.setup();
await dbUtils.reset();
await utils.resetDatabase();
admin = await apiUtils.adminSetup();
admin = await utils.adminSetup();
[user1, user2, user3] = await Promise.all([
apiUtils.userSetup(admin.accessToken, createUserDto.user1),
apiUtils.userSetup(admin.accessToken, createUserDto.user2),
apiUtils.userSetup(admin.accessToken, createUserDto.user3),
utils.userSetup(admin.accessToken, createUserDto.user1),
utils.userSetup(admin.accessToken, createUserDto.user2),
utils.userSetup(admin.accessToken, createUserDto.user3),
]);
[user1Asset1, user1Asset2] = await Promise.all([
apiUtils.createAsset(user1.accessToken, { isFavorite: true }),
apiUtils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken, { isFavorite: true }),
utils.createAsset(user1.accessToken),
]);
const albums = await Promise.all([
// user 1
apiUtils.createAlbum(user1.accessToken, {
utils.createAlbum(user1.accessToken, {
albumName: user1SharedUser,
sharedWithUserIds: [user2.userId],
assetIds: [user1Asset1.id],
}),
apiUtils.createAlbum(user1.accessToken, {
utils.createAlbum(user1.accessToken, {
albumName: user1SharedLink,
assetIds: [user1Asset1.id],
}),
apiUtils.createAlbum(user1.accessToken, {
utils.createAlbum(user1.accessToken, {
albumName: user1NotShared,
assetIds: [user1Asset1.id, user1Asset2.id],
}),
// user 2
apiUtils.createAlbum(user2.accessToken, {
utils.createAlbum(user2.accessToken, {
albumName: user2SharedUser,
sharedWithUserIds: [user1.userId],
assetIds: [user1Asset1.id],
}),
apiUtils.createAlbum(user2.accessToken, { albumName: user2SharedLink }),
apiUtils.createAlbum(user2.accessToken, { albumName: user2NotShared }),
utils.createAlbum(user2.accessToken, { albumName: user2SharedLink }),
utils.createAlbum(user2.accessToken, { albumName: user2NotShared }),
// user 3
apiUtils.createAlbum(user3.accessToken, {
utils.createAlbum(user3.accessToken, {
albumName: 'Deleted',
sharedWithUserIds: [user1.userId],
}),
@ -82,12 +81,12 @@ describe('/album', () => {
await Promise.all([
// add shared link to user1SharedLink album
apiUtils.createSharedLink(user1.accessToken, {
utils.createSharedLink(user1.accessToken, {
type: SharedLinkType.Album,
albumId: user1Albums[1].id,
}),
// add shared link to user2SharedLink album
apiUtils.createSharedLink(user2.accessToken, {
utils.createSharedLink(user2.accessToken, {
type: SharedLinkType.Album,
albumId: user2Albums[1].id,
}),
@ -366,7 +365,7 @@ describe('/album', () => {
});
it('should be able to add own asset to own album', async () => {
const asset = await apiUtils.createAsset(user1.accessToken);
const asset = await utils.createAsset(user1.accessToken);
const { status, body } = await request(app)
.put(`/album/${user1Albums[0].id}/assets`)
.set('Authorization', `Bearer ${user1.accessToken}`)
@ -377,7 +376,7 @@ describe('/album', () => {
});
it('should be able to add own asset to shared album', async () => {
const asset = await apiUtils.createAsset(user1.accessToken);
const asset = await utils.createAsset(user1.accessToken);
const { status, body } = await request(app)
.put(`/album/${user2Albums[0].id}/assets`)
.set('Authorization', `Bearer ${user1.accessToken}`)
@ -398,7 +397,7 @@ describe('/album', () => {
});
it('should update an album', async () => {
const album = await apiUtils.createAlbum(user1.accessToken, {
const album = await utils.createAlbum(user1.accessToken, {
albumName: 'New album',
});
const { status, body } = await request(app)
@ -485,7 +484,7 @@ describe('/album', () => {
let album: AlbumResponseDto;
beforeEach(async () => {
album = await apiUtils.createAlbum(user1.accessToken, {
album = await utils.createAlbum(user1.accessToken, {
albumName: 'testAlbum',
});
});

View File

@ -7,13 +7,12 @@ import {
} from '@immich/sdk';
import { exiftool } from 'exiftool-vendored';
import { DateTime } from 'luxon';
import { createHash } from 'node:crypto';
import { readFile, writeFile } from 'node:fs/promises';
import { basename, join } from 'node:path';
import { Socket } from 'socket.io-client';
import { createUserDto, uuidDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { apiUtils, app, dbUtils, tempDir, testAssetDir, wsUtils } from 'src/utils';
import { app, tempDir, testAssetDir, utils } from 'src/utils';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
@ -21,8 +20,6 @@ const TEN_TIMES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const locationAssetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`;
const sha1 = (bytes: Buffer) => createHash('sha1').update(bytes).digest('base64');
const readTags = async (bytes: Buffer, filename: string) => {
const filepath = join(tempDir, filename);
await writeFile(filepath, bytes);
@ -47,42 +44,41 @@ describe('/asset', () => {
let ws: Socket;
beforeAll(async () => {
apiUtils.setup();
await dbUtils.reset();
admin = await apiUtils.adminSetup({ onboarding: false });
await utils.resetDatabase();
admin = await utils.adminSetup({ onboarding: false });
[ws, user1, user2, userStats] = await Promise.all([
wsUtils.connect(admin.accessToken),
apiUtils.userSetup(admin.accessToken, createUserDto.user1),
apiUtils.userSetup(admin.accessToken, createUserDto.user2),
apiUtils.userSetup(admin.accessToken, createUserDto.user3),
utils.connectWebsocket(admin.accessToken),
utils.userSetup(admin.accessToken, createUserDto.user1),
utils.userSetup(admin.accessToken, createUserDto.user2),
utils.userSetup(admin.accessToken, createUserDto.user3),
]);
// asset location
assetLocation = await apiUtils.createAsset(admin.accessToken, {
assetLocation = await utils.createAsset(admin.accessToken, {
assetData: {
filename: 'thompson-springs.jpg',
bytes: await readFile(locationAssetFilepath),
},
});
await wsUtils.waitForEvent({ event: 'upload', assetId: assetLocation.id });
await utils.waitForWebsocketEvent({ event: 'upload', assetId: assetLocation.id });
user1Assets = await Promise.all([
apiUtils.createAsset(user1.accessToken),
apiUtils.createAsset(user1.accessToken),
apiUtils.createAsset(user1.accessToken, {
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken, {
isFavorite: true,
isReadOnly: true,
fileCreatedAt: yesterday.toISO(),
fileModifiedAt: yesterday.toISO(),
assetData: { filename: 'example.mp4' },
}),
apiUtils.createAsset(user1.accessToken),
apiUtils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
user2Assets = await Promise.all([apiUtils.createAsset(user2.accessToken)]);
user2Assets = await Promise.all([utils.createAsset(user2.accessToken)]);
for (const asset of [...user1Assets, ...user2Assets]) {
expect(asset.duplicate).toBe(false);
@ -90,27 +86,27 @@ describe('/asset', () => {
await Promise.all([
// stats
apiUtils.createAsset(userStats.accessToken),
apiUtils.createAsset(userStats.accessToken, { isFavorite: true }),
apiUtils.createAsset(userStats.accessToken, { isArchived: true }),
apiUtils.createAsset(userStats.accessToken, {
utils.createAsset(userStats.accessToken),
utils.createAsset(userStats.accessToken, { isFavorite: true }),
utils.createAsset(userStats.accessToken, { isArchived: true }),
utils.createAsset(userStats.accessToken, {
isArchived: true,
isFavorite: true,
assetData: { filename: 'example.mp4' },
}),
]);
const person1 = await apiUtils.createPerson(user1.accessToken, {
const person1 = await utils.createPerson(user1.accessToken, {
name: 'Test Person',
});
await dbUtils.createFace({
await utils.createFace({
assetId: user1Assets[0].id,
personId: person1.id,
});
}, 30_000);
afterAll(() => {
wsUtils.disconnect(ws);
utils.disconnectWebsocket(ws);
});
describe('GET /asset/:id', () => {
@ -145,7 +141,7 @@ describe('/asset', () => {
});
it('should work with a shared link', async () => {
const sharedLink = await apiUtils.createSharedLink(user1.accessToken, {
const sharedLink = await utils.createSharedLink(user1.accessToken, {
type: SharedLinkType.Individual,
assetIds: [user1Assets[0].id],
});
@ -175,7 +171,7 @@ describe('/asset', () => {
],
});
const sharedLink = await apiUtils.createSharedLink(user1.accessToken, {
const sharedLink = await utils.createSharedLink(user1.accessToken, {
type: SharedLinkType.Individual,
assetIds: [user1Assets[0].id],
});
@ -247,12 +243,12 @@ describe('/asset', () => {
describe('GET /asset/random', () => {
beforeAll(async () => {
await Promise.all([
apiUtils.createAsset(user1.accessToken),
apiUtils.createAsset(user1.accessToken),
apiUtils.createAsset(user1.accessToken),
apiUtils.createAsset(user1.accessToken),
apiUtils.createAsset(user1.accessToken),
apiUtils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
});
@ -335,7 +331,7 @@ describe('/asset', () => {
});
it('should favorite an asset', async () => {
const before = await apiUtils.getAssetInfo(user1.accessToken, user1Assets[0].id);
const before = await utils.getAssetInfo(user1.accessToken, user1Assets[0].id);
expect(before.isFavorite).toBe(false);
const { status, body } = await request(app)
@ -347,7 +343,7 @@ describe('/asset', () => {
});
it('should archive an asset', async () => {
const before = await apiUtils.getAssetInfo(user1.accessToken, user1Assets[0].id);
const before = await utils.getAssetInfo(user1.accessToken, user1Assets[0].id);
expect(before.isArchived).toBe(false);
const { status, body } = await request(app)
@ -475,9 +471,9 @@ describe('/asset', () => {
});
it('should move an asset to the trash', async () => {
const { id: assetId } = await apiUtils.createAsset(admin.accessToken);
const { id: assetId } = await utils.createAsset(admin.accessToken);
const before = await apiUtils.getAssetInfo(admin.accessToken, assetId);
const before = await utils.getAssetInfo(admin.accessToken, assetId);
expect(before.isTrashed).toBe(false);
const { status } = await request(app)
@ -486,7 +482,7 @@ describe('/asset', () => {
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(204);
const after = await apiUtils.getAssetInfo(admin.accessToken, assetId);
const after = await utils.getAssetInfo(admin.accessToken, assetId);
expect(after.isTrashed).toBe(true);
});
});
@ -497,7 +493,7 @@ describe('/asset', () => {
input: 'formats/jpg/el_torcal_rocks.jpg',
expected: {
type: AssetTypeEnum.Image,
originalFileName: 'el_torcal_rocks',
originalFileName: 'el_torcal_rocks.jpg',
resized: true,
exifInfo: {
dateTimeOriginal: '2012-08-05T11:39:59.000Z',
@ -521,7 +517,7 @@ describe('/asset', () => {
input: 'formats/heic/IMG_2682.heic',
expected: {
type: AssetTypeEnum.Image,
originalFileName: 'IMG_2682',
originalFileName: 'IMG_2682.heic',
resized: true,
fileCreatedAt: '2019-03-21T16:04:22.348Z',
exifInfo: {
@ -546,7 +542,7 @@ describe('/asset', () => {
input: 'formats/png/density_plot.png',
expected: {
type: AssetTypeEnum.Image,
originalFileName: 'density_plot',
originalFileName: 'density_plot.png',
resized: true,
exifInfo: {
exifImageWidth: 800,
@ -561,7 +557,7 @@ describe('/asset', () => {
input: 'formats/raw/Nikon/D80/glarus.nef',
expected: {
type: AssetTypeEnum.Image,
originalFileName: 'glarus',
originalFileName: 'glarus.nef',
resized: true,
fileCreatedAt: '2010-07-20T17:27:12.000Z',
exifInfo: {
@ -583,7 +579,7 @@ describe('/asset', () => {
input: 'formats/raw/Nikon/D700/philadelphia.nef',
expected: {
type: AssetTypeEnum.Image,
originalFileName: 'philadelphia',
originalFileName: 'philadelphia.nef',
resized: true,
fileCreatedAt: '2016-09-22T22:10:29.060Z',
exifInfo: {
@ -607,15 +603,15 @@ describe('/asset', () => {
for (const { input, expected } of tests) {
it(`should generate a thumbnail for ${input}`, async () => {
const filepath = join(testAssetDir, input);
const { id, duplicate } = await apiUtils.createAsset(admin.accessToken, {
const { id, duplicate } = await utils.createAsset(admin.accessToken, {
assetData: { bytes: await readFile(filepath), filename: basename(filepath) },
});
expect(duplicate).toBe(false);
await wsUtils.waitForEvent({ event: 'upload', assetId: id });
await utils.waitForWebsocketEvent({ event: 'upload', assetId: id });
const asset = await apiUtils.getAssetInfo(admin.accessToken, id);
const asset = await utils.getAssetInfo(admin.accessToken, id);
expect(asset.exifInfo).toBeDefined();
expect(asset.exifInfo).toMatchObject(expected.exifInfo);
@ -625,7 +621,7 @@ describe('/asset', () => {
it('should handle a duplicate', async () => {
const filepath = 'formats/jpeg/el_torcal_rocks.jpeg';
const { duplicate } = await apiUtils.createAsset(admin.accessToken, {
const { duplicate } = await utils.createAsset(admin.accessToken, {
assetData: {
bytes: await readFile(join(testAssetDir, filepath)),
filename: basename(filepath),
@ -657,21 +653,21 @@ describe('/asset', () => {
for (const { filepath, checksum } of motionTests) {
it(`should extract motionphoto video from ${filepath}`, async () => {
const response = await apiUtils.createAsset(admin.accessToken, {
const response = await utils.createAsset(admin.accessToken, {
assetData: {
bytes: await readFile(join(testAssetDir, filepath)),
filename: basename(filepath),
},
});
await wsUtils.waitForEvent({ event: 'upload', assetId: response.id });
await utils.waitForWebsocketEvent({ event: 'upload', assetId: response.id });
expect(response.duplicate).toBe(false);
const asset = await apiUtils.getAssetInfo(admin.accessToken, response.id);
const asset = await utils.getAssetInfo(admin.accessToken, response.id);
expect(asset.livePhotoVideoId).toBeDefined();
const video = await apiUtils.getAssetInfo(admin.accessToken, asset.livePhotoVideoId as string);
const video = await utils.getAssetInfo(admin.accessToken, asset.livePhotoVideoId as string);
expect(video.checksum).toStrictEqual(checksum);
});
}
@ -690,7 +686,7 @@ describe('/asset', () => {
.get(`/asset/thumbnail/${assetLocation.id}?format=WEBP`)
.set('Authorization', `Bearer ${admin.accessToken}`);
await wsUtils.waitForEvent({
await utils.waitForWebsocketEvent({
event: 'upload',
assetId: assetLocation.id,
});
@ -736,11 +732,11 @@ describe('/asset', () => {
expect(body).toBeDefined();
expect(type).toBe('image/jpeg');
const asset = await apiUtils.getAssetInfo(admin.accessToken, assetLocation.id);
const asset = await utils.getAssetInfo(admin.accessToken, assetLocation.id);
const original = await readFile(locationAssetFilepath);
const originalChecksum = sha1(original);
const downloadChecksum = sha1(body);
const originalChecksum = utils.sha1(original);
const downloadChecksum = utils.sha1(body);
expect(originalChecksum).toBe(downloadChecksum);
expect(downloadChecksum).toBe(asset.checksum);

View File

@ -1,24 +1,23 @@
import { deleteAssets, getAuditFiles, updateAsset, type LoginResponseDto } from '@immich/sdk';
import { apiUtils, asBearerAuth, dbUtils, fileUtils } from 'src/utils';
import { asBearerAuth, utils } from 'src/utils';
import { beforeAll, describe, expect, it } from 'vitest';
describe('/audit', () => {
let admin: LoginResponseDto;
beforeAll(async () => {
apiUtils.setup();
await dbUtils.reset();
await fileUtils.reset();
await utils.resetDatabase();
await utils.resetFilesystem();
admin = await apiUtils.adminSetup();
admin = await utils.adminSetup();
});
describe('GET :/file-report', () => {
it('excludes assets without issues from report', async () => {
const [trashedAsset, archivedAsset] = await Promise.all([
apiUtils.createAsset(admin.accessToken),
apiUtils.createAsset(admin.accessToken),
apiUtils.createAsset(admin.accessToken),
utils.createAsset(admin.accessToken),
utils.createAsset(admin.accessToken),
utils.createAsset(admin.accessToken),
]);
await Promise.all([

View File

@ -1,19 +1,15 @@
import { LoginResponseDto, getAuthDevices, login, signUpAdmin } from '@immich/sdk';
import { loginDto, signupDto, uuidDto } from 'src/fixtures';
import { deviceDto, errorDto, loginResponseDto, signupResponseDto } from 'src/responses';
import { apiUtils, app, asBearerAuth, dbUtils } from 'src/utils';
import { app, asBearerAuth, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
import { beforeEach, describe, expect, it } from 'vitest';
const { name, email, password } = signupDto.admin;
describe(`/auth/admin-sign-up`, () => {
beforeAll(() => {
apiUtils.setup();
});
beforeEach(async () => {
await dbUtils.reset();
await utils.resetDatabase();
});
describe('POST /auth/admin-sign-up', () => {
@ -84,7 +80,7 @@ describe('/auth/*', () => {
let admin: LoginResponseDto;
beforeEach(async () => {
await dbUtils.reset();
await utils.resetDatabase();
await signUpAdmin({ signUpDto: signupDto.admin });
admin = await login({ loginCredentialDto: loginDto.admin });
});

View File

@ -1,18 +1,19 @@
import { AssetFileUploadResponseDto, LoginResponseDto } from '@immich/sdk';
import { readFile, writeFile } from 'node:fs/promises';
import { errorDto } from 'src/responses';
import { apiUtils, app, dbUtils } from 'src/utils';
import { app, tempDir, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, describe, expect, it } from 'vitest';
describe('/download', () => {
let admin: LoginResponseDto;
let asset1: AssetFileUploadResponseDto;
let asset2: AssetFileUploadResponseDto;
beforeAll(async () => {
apiUtils.setup();
await dbUtils.reset();
admin = await apiUtils.adminSetup();
asset1 = await apiUtils.createAsset(admin.accessToken);
await utils.resetDatabase();
admin = await utils.adminSetup();
[asset1, asset2] = await Promise.all([utils.createAsset(admin.accessToken), utils.createAsset(admin.accessToken)]);
});
describe('POST /download/info', () => {
@ -40,6 +41,39 @@ describe('/download', () => {
});
});
describe('POST /download/archive', () => {
it('should require authentication', async () => {
const { status, body } = await request(app)
.post(`/download/archive`)
.send({ assetIds: [asset1.id, asset2.id] });
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should download an archive', async () => {
const { status, body } = await request(app)
.post('/download/archive')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ assetIds: [asset1.id, asset2.id] });
expect(status).toBe(200);
expect(body instanceof Buffer).toBe(true);
await writeFile(`${tempDir}/archive.zip`, body);
await utils.unzip(`${tempDir}/archive.zip`, `${tempDir}/archive`);
const files = [
{ filename: 'example.png', id: asset1.id },
{ filename: 'example+1.png', id: asset2.id },
];
for (const { id, filename } of files) {
const bytes = await readFile(`${tempDir}/archive/${filename}`);
const asset = await utils.getAssetInfo(admin.accessToken, id);
expect(utils.sha1(bytes)).toBe(asset.checksum);
}
});
});
describe('POST /download/asset/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post(`/download/asset/${asset1.id}`);

View File

@ -1,7 +1,7 @@
import { LibraryResponseDto, LibraryType, LoginResponseDto, getAllLibraries } from '@immich/sdk';
import { userDto, uuidDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { apiUtils, app, asBearerAuth, dbUtils, testAssetDirInternal } from 'src/utils';
import { app, asBearerAuth, testAssetDirInternal, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, describe, expect, it } from 'vitest';
@ -11,11 +11,10 @@ describe('/library', () => {
let library: LibraryResponseDto;
beforeAll(async () => {
apiUtils.setup();
await dbUtils.reset();
admin = await apiUtils.adminSetup();
user = await apiUtils.userSetup(admin.accessToken, userDto.user1);
library = await apiUtils.createLibrary(admin.accessToken, { type: LibraryType.External });
await utils.resetDatabase();
admin = await utils.adminSetup();
user = await utils.userSetup(admin.accessToken, userDto.user1);
library = await utils.createLibrary(admin.accessToken, { type: LibraryType.External });
});
describe('GET /library', () => {
@ -303,7 +302,7 @@ describe('/library', () => {
});
it('should get library by id', async () => {
const library = await apiUtils.createLibrary(admin.accessToken, { type: LibraryType.External });
const library = await utils.createLibrary(admin.accessToken, { type: LibraryType.External });
const { status, body } = await request(app)
.get(`/library/${library.id}`)
@ -359,7 +358,7 @@ describe('/library', () => {
});
it('should delete an external library', async () => {
const library = await apiUtils.createLibrary(admin.accessToken, { type: LibraryType.External });
const library = await utils.createLibrary(admin.accessToken, { type: LibraryType.External });
const { status, body } = await request(app)
.delete(`/library/${library.id}`)
@ -415,14 +414,14 @@ describe('/library', () => {
});
it('should pass with no import paths', async () => {
const response = await apiUtils.validateLibrary(admin.accessToken, library.id, { importPaths: [] });
const response = await utils.validateLibrary(admin.accessToken, library.id, { importPaths: [] });
expect(response.importPaths).toEqual([]);
});
it('should fail if path does not exist', async () => {
const pathToTest = `${testAssetDirInternal}/does/not/exist`;
const response = await apiUtils.validateLibrary(admin.accessToken, library.id, {
const response = await utils.validateLibrary(admin.accessToken, library.id, {
importPaths: [pathToTest],
});
@ -439,7 +438,7 @@ describe('/library', () => {
it('should fail if path is a file', async () => {
const pathToTest = `${testAssetDirInternal}/albums/nature/el_torcal_rocks.jpg`;
const response = await apiUtils.validateLibrary(admin.accessToken, library.id, {
const response = await utils.validateLibrary(admin.accessToken, library.id, {
importPaths: [pathToTest],
});

View File

@ -1,16 +1,12 @@
import { errorDto } from 'src/responses';
import { apiUtils, app, dbUtils } from 'src/utils';
import { app, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
import { beforeAll, describe, expect, it } from 'vitest';
describe(`/oauth`, () => {
beforeAll(() => {
apiUtils.setup();
});
beforeEach(async () => {
await dbUtils.reset();
await apiUtils.adminSetup();
beforeAll(async () => {
await utils.resetDatabase();
await utils.adminSetup();
});
describe('POST /oauth/authorize', () => {

View File

@ -1,7 +1,7 @@
import { LoginResponseDto, createPartner } from '@immich/sdk';
import { createUserDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { apiUtils, app, asBearerAuth, dbUtils } from 'src/utils';
import { app, asBearerAuth, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, describe, expect, it } from 'vitest';
@ -12,15 +12,14 @@ describe('/partner', () => {
let user3: LoginResponseDto;
beforeAll(async () => {
apiUtils.setup();
await dbUtils.reset();
await utils.resetDatabase();
admin = await apiUtils.adminSetup();
admin = await utils.adminSetup();
[user1, user2, user3] = await Promise.all([
apiUtils.userSetup(admin.accessToken, createUserDto.user1),
apiUtils.userSetup(admin.accessToken, createUserDto.user2),
apiUtils.userSetup(admin.accessToken, createUserDto.user3),
utils.userSetup(admin.accessToken, createUserDto.user1),
utils.userSetup(admin.accessToken, createUserDto.user2),
utils.userSetup(admin.accessToken, createUserDto.user3),
]);
await Promise.all([

View File

@ -1,39 +1,49 @@
import { LoginResponseDto, PersonResponseDto } from '@immich/sdk';
import { uuidDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { apiUtils, app, dbUtils } from 'src/utils';
import { app, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
describe('/activity', () => {
const invalidBirthday = [
{ birthDate: 'false', response: 'birthDate must be a date string' },
{ birthDate: '123567', response: 'birthDate must be a date string' },
{ birthDate: 123_567, response: 'birthDate must be a date string' },
{ birthDate: new Date(9999, 0, 0).toISOString(), response: ['Birth date cannot be in the future'] },
];
describe('/person', () => {
let admin: LoginResponseDto;
let visiblePerson: PersonResponseDto;
let hiddenPerson: PersonResponseDto;
let multipleAssetsPerson: PersonResponseDto;
beforeAll(async () => {
apiUtils.setup();
await dbUtils.reset();
admin = await apiUtils.adminSetup();
});
await utils.resetDatabase();
admin = await utils.adminSetup();
beforeEach(async () => {
await dbUtils.reset(['person']);
[visiblePerson, hiddenPerson] = await Promise.all([
apiUtils.createPerson(admin.accessToken, {
[visiblePerson, hiddenPerson, multipleAssetsPerson] = await Promise.all([
utils.createPerson(admin.accessToken, {
name: 'visible_person',
}),
apiUtils.createPerson(admin.accessToken, {
utils.createPerson(admin.accessToken, {
name: 'hidden_person',
isHidden: true,
}),
utils.createPerson(admin.accessToken, {
name: 'multiple_assets_person',
}),
]);
const asset = await apiUtils.createAsset(admin.accessToken);
const asset1 = await utils.createAsset(admin.accessToken);
const asset2 = await utils.createAsset(admin.accessToken);
await Promise.all([
dbUtils.createFace({ assetId: asset.id, personId: visiblePerson.id }),
dbUtils.createFace({ assetId: asset.id, personId: hiddenPerson.id }),
utils.createFace({ assetId: asset1.id, personId: visiblePerson.id }),
utils.createFace({ assetId: asset1.id, personId: hiddenPerson.id }),
utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }),
utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }),
utils.createFace({ assetId: asset2.id, personId: multipleAssetsPerson.id }),
]);
});
@ -55,9 +65,10 @@ describe('/activity', () => {
expect(status).toBe(200);
expect(body).toEqual({
total: 2,
total: 3,
hidden: 1,
people: [
expect.objectContaining({ name: 'multiple_assets_person' }),
expect.objectContaining({ name: 'visible_person' }),
expect.objectContaining({ name: 'hidden_person' }),
],
@ -69,9 +80,12 @@ describe('/activity', () => {
expect(status).toBe(200);
expect(body).toEqual({
total: 2,
total: 3,
hidden: 1,
people: [expect.objectContaining({ name: 'visible_person' })],
people: [
expect.objectContaining({ name: 'multiple_assets_person' }),
expect.objectContaining({ name: 'visible_person' }),
],
});
});
});
@ -103,6 +117,68 @@ describe('/activity', () => {
});
});
describe('GET /person/:id/statistics', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get(`/person/${multipleAssetsPerson.id}/statistics`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should throw error if person with id does not exist', async () => {
const { status, body } = await request(app)
.get(`/person/${uuidDto.notFound}/statistics`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest());
});
it('should return the correct number of assets', async () => {
const { status, body } = await request(app)
.get(`/person/${multipleAssetsPerson.id}/statistics`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual(expect.objectContaining({ assets: 2 }));
});
});
describe('POST /person', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post(`/person`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
for (const { birthDate, response } of invalidBirthday) {
it(`should not accept an invalid birth date [${birthDate}]`, async () => {
const { status, body } = await request(app)
.post(`/person`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ birthDate });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(response));
});
}
it('should create a person', async () => {
const { status, body } = await request(app)
.post(`/person`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({
name: 'New Person',
birthDate: '1990-01-01T05:00:00.000Z',
});
expect(status).toBe(201);
expect(body).toMatchObject({
id: expect.any(String),
name: 'New Person',
birthDate: '1990-01-01T05:00:00.000Z',
});
});
});
describe('PUT /person/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).put(`/person/${uuidDto.notFound}`);
@ -125,24 +201,16 @@ describe('/activity', () => {
});
}
it('should not accept invalid birth dates', async () => {
for (const { birthDate, response } of [
{ birthDate: false, response: 'Not found or no person.write access' },
{ birthDate: 'false', response: ['birthDate must be a Date instance'] },
{
birthDate: '123567',
response: 'Not found or no person.write access',
},
{ birthDate: 123_567, response: 'Not found or no person.write access' },
]) {
for (const { birthDate, response } of invalidBirthday) {
it(`should not accept an invalid birth date [${birthDate}]`, async () => {
const { status, body } = await request(app)
.put(`/person/${uuidDto.notFound}`)
.put(`/person/${visiblePerson.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ birthDate });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(response));
}
});
}
it('should update a date of birth', async () => {
const { status, body } = await request(app)
@ -154,15 +222,8 @@ describe('/activity', () => {
});
it('should clear a date of birth', async () => {
// TODO ironically this uses the update endpoint to create the person
const person = await apiUtils.createPerson(admin.accessToken, {
birthDate: new Date('1990-01-01').toISOString(),
});
expect(person.birthDate).toBeDefined();
const { status, body } = await request(app)
.put(`/person/${person.id}`)
.put(`/person/${visiblePerson.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ birthDate: null });
expect(status).toBe(200);

View File

@ -0,0 +1,224 @@
import { AssetFileUploadResponseDto, LoginResponseDto } from '@immich/sdk';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { Socket } from 'socket.io-client';
import { errorDto } from 'src/responses';
import { app, testAssetDir, utils } from 'src/utils';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
const albums = { total: 0, count: 0, items: [], facets: [] };
describe('/search', () => {
let admin: LoginResponseDto;
let assetFalcon: AssetFileUploadResponseDto;
let assetDenali: AssetFileUploadResponseDto;
let websocket: Socket;
beforeAll(async () => {
await utils.resetDatabase();
admin = await utils.adminSetup();
websocket = await utils.connectWebsocket(admin.accessToken);
const files: string[] = [
'/albums/nature/prairie_falcon.jpg',
'/formats/webp/denali.webp',
'/formats/raw/Nikon/D700/philadelphia.nef',
'/albums/nature/orychophragmus_violaceus.jpg',
'/albums/nature/notocactus_minimus.jpg',
'/albums/nature/silver_fir.jpg',
'/albums/nature/tanners_ridge.jpg',
'/albums/nature/cyclamen_persicum.jpg',
'/albums/nature/polemonium_reptans.jpg',
'/albums/nature/wood_anemones.jpg',
'/formats/heic/IMG_2682.heic',
'/formats/jpg/el_torcal_rocks.jpg',
'/formats/png/density_plot.png',
'/formats/motionphoto/Samsung One UI 6.jpg',
'/formats/motionphoto/Samsung One UI 6.heic',
'/formats/motionphoto/Samsung One UI 5.jpg',
'/formats/raw/Nikon/D80/glarus.nef',
'/metadata/gps-position/thompson-springs.jpg',
];
const assets: AssetFileUploadResponseDto[] = [];
for (const filename of files) {
const bytes = await readFile(join(testAssetDir, filename));
assets.push(
await utils.createAsset(admin.accessToken, {
deviceAssetId: `test-${filename}`,
assetData: { bytes, filename },
}),
);
}
for (const asset of assets) {
await utils.waitForWebsocketEvent({ event: 'upload', assetId: asset.id });
}
[assetFalcon, assetDenali] = assets;
});
afterAll(async () => {
await utils.disconnectWebsocket(websocket);
});
describe('POST /search/metadata', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post('/search/metadata');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should search by camera make', async () => {
const { status, body } = await request(app)
.post('/search/metadata')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ make: 'Canon' });
expect(status).toBe(200);
expect(body).toEqual({
albums,
assets: {
count: 2,
items: expect.arrayContaining([
expect.objectContaining({ id: assetDenali.id }),
expect.objectContaining({ id: assetFalcon.id }),
]),
facets: [],
nextPage: null,
total: 2,
},
});
});
it('should search by camera model', async () => {
const { status, body } = await request(app)
.post('/search/metadata')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ model: 'Canon EOS 7D' });
expect(status).toBe(200);
expect(body).toEqual({
albums,
assets: {
count: 1,
items: [expect.objectContaining({ id: assetDenali.id })],
facets: [],
nextPage: null,
total: 1,
},
});
});
});
describe('POST /search/smart', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post('/search/smart');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
});
describe('GET /search/explore', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/search/explore');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should get explore data', async () => {
const { status, body } = await request(app)
.get('/search/explore')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual([
{ fieldName: 'exifInfo.city', items: [] },
{ fieldName: 'smartInfo.tags', items: [] },
]);
});
});
describe('GET /search/places', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/search/places');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should get places', async () => {
const { status, body } = await request(app)
.get('/search/places?name=Paris')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(Array.isArray(body)).toBe(true);
expect(body.length).toBeGreaterThan(10);
});
});
describe('GET /search/suggestions', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/search/suggestions');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should get suggestions for country', async () => {
const { status, body } = await request(app)
.get('/search/suggestions?type=country')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(body).toEqual(['United States of America']);
expect(status).toBe(200);
});
it('should get suggestions for state', async () => {
const { status, body } = await request(app)
.get('/search/suggestions?type=state')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(body).toEqual(['Douglas County, Nebraska', 'Mesa County, Colorado']);
expect(status).toBe(200);
});
it('should get suggestions for city', async () => {
const { status, body } = await request(app)
.get('/search/suggestions?type=city')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(body).toEqual(['Palisade', 'Ralston']);
expect(status).toBe(200);
});
it('should get suggestions for camera make', async () => {
const { status, body } = await request(app)
.get('/search/suggestions?type=camera-make')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(body).toEqual([
'Apple',
'Canon',
'FUJIFILM',
'NIKON CORPORATION',
'PENTAX Corporation',
'samsung',
'SONY',
]);
expect(status).toBe(200);
});
it('should get suggestions for camera model', async () => {
const { status, body } = await request(app)
.get('/search/suggestions?type=camera-model')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(body).toEqual([
'Canon EOS 7D',
'Canon EOS R5',
'DSLR-A550',
'FinePix S3Pro',
'iPhone 7',
'NIKON D700',
'NIKON D750',
'NIKON D80',
'PENTAX K10D',
'SM-F711N',
'SM-S906U',
'SM-T970',
]);
expect(status).toBe(200);
});
});
});

View File

@ -1,7 +1,7 @@
import { LoginResponseDto, getServerConfig } from '@immich/sdk';
import { createUserDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { apiUtils, app, dbUtils } from 'src/utils';
import { app, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, describe, expect, it } from 'vitest';
@ -10,10 +10,9 @@ describe('/server-info', () => {
let nonAdmin: LoginResponseDto;
beforeAll(async () => {
apiUtils.setup();
await dbUtils.reset();
admin = await apiUtils.adminSetup({ onboarding: false });
nonAdmin = await apiUtils.userSetup(admin.accessToken, createUserDto.user1);
await utils.resetDatabase();
admin = await utils.adminSetup({ onboarding: false });
nonAdmin = await utils.userSetup(admin.accessToken, createUserDto.user1);
});
describe('GET /server-info', () => {
@ -88,6 +87,7 @@ describe('/server-info', () => {
loginPageMessage: '',
oauthButtonText: 'Login with OAuth',
trashDays: 30,
userDeleteDelay: 7,
isInitialized: true,
externalDomain: '',
isOnboarded: false,

View File

@ -9,7 +9,7 @@ import {
} from '@immich/sdk';
import { createUserDto, uuidDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { apiUtils, app, asBearerAuth, dbUtils } from 'src/utils';
import { app, asBearerAuth, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, describe, expect, it } from 'vitest';
@ -30,20 +30,16 @@ describe('/shared-link', () => {
let linkWithoutMetadata: SharedLinkResponseDto;
beforeAll(async () => {
apiUtils.setup();
await dbUtils.reset();
await utils.resetDatabase();
admin = await apiUtils.adminSetup();
admin = await utils.adminSetup();
[user1, user2] = await Promise.all([
apiUtils.userSetup(admin.accessToken, createUserDto.user1),
apiUtils.userSetup(admin.accessToken, createUserDto.user2),
utils.userSetup(admin.accessToken, createUserDto.user1),
utils.userSetup(admin.accessToken, createUserDto.user2),
]);
[asset1, asset2] = await Promise.all([
apiUtils.createAsset(user1.accessToken),
apiUtils.createAsset(user1.accessToken),
]);
[asset1, asset2] = await Promise.all([utils.createAsset(user1.accessToken), utils.createAsset(user1.accessToken)]);
[album, deletedAlbum, metadataAlbum] = await Promise.all([
createAlbum({ createAlbumDto: { albumName: 'album' } }, { headers: asBearerAuth(user1.accessToken) }),
@ -61,29 +57,29 @@ describe('/shared-link', () => {
[linkWithDeletedAlbum, linkWithAlbum, linkWithAssets, linkWithPassword, linkWithMetadata, linkWithoutMetadata] =
await Promise.all([
apiUtils.createSharedLink(user2.accessToken, {
utils.createSharedLink(user2.accessToken, {
type: SharedLinkType.Album,
albumId: deletedAlbum.id,
}),
apiUtils.createSharedLink(user1.accessToken, {
utils.createSharedLink(user1.accessToken, {
type: SharedLinkType.Album,
albumId: album.id,
}),
apiUtils.createSharedLink(user1.accessToken, {
utils.createSharedLink(user1.accessToken, {
type: SharedLinkType.Individual,
assetIds: [asset1.id],
}),
apiUtils.createSharedLink(user1.accessToken, {
utils.createSharedLink(user1.accessToken, {
type: SharedLinkType.Album,
albumId: album.id,
password: 'foo',
}),
apiUtils.createSharedLink(user1.accessToken, {
utils.createSharedLink(user1.accessToken, {
type: SharedLinkType.Album,
albumId: metadataAlbum.id,
showMetadata: true,
}),
apiUtils.createSharedLink(user1.accessToken, {
utils.createSharedLink(user1.accessToken, {
type: SharedLinkType.Album,
albumId: metadataAlbum.id,
showMetadata: false,
@ -194,7 +190,7 @@ describe('/shared-link', () => {
expect(body.assets).toHaveLength(1);
expect(body.assets[0]).toEqual(
expect.objectContaining({
originalFileName: 'example',
originalFileName: 'example.png',
localDateTime: expect.any(String),
fileCreatedAt: expect.any(String),
exifInfo: expect.any(Object),

View File

@ -1,7 +1,7 @@
import { LoginResponseDto } from '@immich/sdk';
import { createUserDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { apiUtils, app, dbUtils } from 'src/utils';
import { app, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, describe, expect, it } from 'vitest';
@ -10,10 +10,9 @@ describe('/system-config', () => {
let nonAdmin: LoginResponseDto;
beforeAll(async () => {
apiUtils.setup();
await dbUtils.reset();
admin = await apiUtils.adminSetup();
nonAdmin = await apiUtils.userSetup(admin.accessToken, createUserDto.user1);
await utils.resetDatabase();
admin = await utils.adminSetup();
nonAdmin = await utils.userSetup(admin.accessToken, createUserDto.user1);
});
describe('GET /system-config/map/style.json', () => {

View File

@ -1,7 +1,7 @@
import { LoginResponseDto, getAllAssets } from '@immich/sdk';
import { Socket } from 'socket.io-client';
import { errorDto } from 'src/responses';
import { apiUtils, app, asBearerAuth, dbUtils, wsUtils } from 'src/utils';
import { app, asBearerAuth, utils } from 'src/utils';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
@ -10,14 +10,13 @@ describe('/trash', () => {
let ws: Socket;
beforeAll(async () => {
apiUtils.setup();
await dbUtils.reset();
admin = await apiUtils.adminSetup({ onboarding: false });
ws = await wsUtils.connect(admin.accessToken);
await utils.resetDatabase();
admin = await utils.adminSetup({ onboarding: false });
ws = await utils.connectWebsocket(admin.accessToken);
});
afterAll(() => {
wsUtils.disconnect(ws);
utils.disconnectWebsocket(ws);
});
describe('POST /trash/empty', () => {
@ -29,8 +28,8 @@ describe('/trash', () => {
});
it('should empty the trash', async () => {
const { id: assetId } = await apiUtils.createAsset(admin.accessToken);
await apiUtils.deleteAssets(admin.accessToken, [assetId]);
const { id: assetId } = await utils.createAsset(admin.accessToken);
await utils.deleteAssets(admin.accessToken, [assetId]);
const before = await getAllAssets({}, { headers: asBearerAuth(admin.accessToken) });
@ -39,7 +38,7 @@ describe('/trash', () => {
const { status } = await request(app).post('/trash/empty').set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(204);
await wsUtils.waitForEvent({ event: 'delete', assetId });
await utils.waitForWebsocketEvent({ event: 'delete', assetId });
const after = await getAllAssets({}, { headers: asBearerAuth(admin.accessToken) });
expect(after.length).toBe(0);
@ -55,16 +54,16 @@ describe('/trash', () => {
});
it('should restore all trashed assets', async () => {
const { id: assetId } = await apiUtils.createAsset(admin.accessToken);
await apiUtils.deleteAssets(admin.accessToken, [assetId]);
const { id: assetId } = await utils.createAsset(admin.accessToken);
await utils.deleteAssets(admin.accessToken, [assetId]);
const before = await apiUtils.getAssetInfo(admin.accessToken, assetId);
const before = await utils.getAssetInfo(admin.accessToken, assetId);
expect(before.isTrashed).toBe(true);
const { status } = await request(app).post('/trash/restore').set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(204);
const after = await apiUtils.getAssetInfo(admin.accessToken, assetId);
const after = await utils.getAssetInfo(admin.accessToken, assetId);
expect(after.isTrashed).toBe(false);
});
});
@ -78,10 +77,10 @@ describe('/trash', () => {
});
it('should restore a trashed asset by id', async () => {
const { id: assetId } = await apiUtils.createAsset(admin.accessToken);
await apiUtils.deleteAssets(admin.accessToken, [assetId]);
const { id: assetId } = await utils.createAsset(admin.accessToken);
await utils.deleteAssets(admin.accessToken, [assetId]);
const before = await apiUtils.getAssetInfo(admin.accessToken, assetId);
const before = await utils.getAssetInfo(admin.accessToken, assetId);
expect(before.isTrashed).toBe(true);
const { status } = await request(app)
@ -90,7 +89,7 @@ describe('/trash', () => {
.send({ ids: [assetId] });
expect(status).toBe(204);
const after = await apiUtils.getAssetInfo(admin.accessToken, assetId);
const after = await utils.getAssetInfo(admin.accessToken, assetId);
expect(after.isTrashed).toBe(false);
});
});

View File

@ -1,7 +1,7 @@
import { LoginResponseDto, deleteUser, getUserById } from '@immich/sdk';
import { createUserDto, userDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { apiUtils, app, asBearerAuth, dbUtils } from 'src/utils';
import { app, asBearerAuth, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, describe, expect, it } from 'vitest';
@ -12,14 +12,13 @@ describe('/server-info', () => {
let nonAdmin: LoginResponseDto;
beforeAll(async () => {
apiUtils.setup();
await dbUtils.reset();
admin = await apiUtils.adminSetup({ onboarding: false });
await utils.resetDatabase();
admin = await utils.adminSetup({ onboarding: false });
[deletedUser, nonAdmin, userToDelete] = await Promise.all([
apiUtils.userSetup(admin.accessToken, createUserDto.user1),
apiUtils.userSetup(admin.accessToken, createUserDto.user2),
apiUtils.userSetup(admin.accessToken, createUserDto.user3),
utils.userSetup(admin.accessToken, createUserDto.user1),
utils.userSetup(admin.accessToken, createUserDto.user2),
utils.userSetup(admin.accessToken, createUserDto.user3),
]);
await deleteUser({ id: deletedUser.userId }, { headers: asBearerAuth(admin.accessToken) });

View File

@ -1,14 +1,10 @@
import { stat } from 'node:fs/promises';
import { apiUtils, app, dbUtils, immichCli } from 'src/utils';
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
import { app, immichCli, utils } from 'src/utils';
import { beforeEach, describe, expect, it } from 'vitest';
describe(`immich login-key`, () => {
beforeAll(() => {
apiUtils.setup();
});
beforeEach(async () => {
await dbUtils.reset();
await utils.resetDatabase();
});
it('should require a url', async () => {
@ -29,12 +25,12 @@ describe(`immich login-key`, () => {
expect(exitCode).toBe(1);
});
it('should login', async () => {
const admin = await apiUtils.adminSetup();
const key = await apiUtils.createApiKey(admin.accessToken);
it('should login and save auth.yml with 600', async () => {
const admin = await utils.adminSetup();
const key = await utils.createApiKey(admin.accessToken);
const { stdout, stderr, exitCode } = await immichCli(['login-key', app, `${key.secret}`]);
expect(stdout.split('\n')).toEqual([
'Logging in...',
'Logging in to http://127.0.0.1:2283/api',
'Logged in as admin@immich.cloud',
'Wrote auth info to /tmp/immich/auth.yml',
]);
@ -45,4 +41,18 @@ describe(`immich login-key`, () => {
const mode = (stats.mode & 0o777).toString(8);
expect(mode).toEqual('600');
});
it('should login without /api in the url', async () => {
const admin = await utils.adminSetup();
const key = await utils.createApiKey(admin.accessToken);
const { stdout, stderr, exitCode } = await immichCli(['login-key', app.replaceAll('/api', ''), `${key.secret}`]);
expect(stdout.split('\n')).toEqual([
'Logging in to http://127.0.0.1:2283',
'Discovered API at http://127.0.0.1:2283/api',
'Logged in as admin@immich.cloud',
'Wrote auth info to /tmp/immich/auth.yml',
]);
expect(stderr).toBe('');
expect(exitCode).toBe(0);
});
});

View File

@ -1,11 +1,10 @@
import { apiUtils, cliUtils, dbUtils, immichCli } from 'src/utils';
import { immichCli, utils } from 'src/utils';
import { beforeAll, describe, expect, it } from 'vitest';
describe(`immich server-info`, () => {
beforeAll(async () => {
apiUtils.setup();
await dbUtils.reset();
await cliUtils.login();
await utils.resetDatabase();
await utils.cliLogin();
});
it('should return the server info', async () => {

View File

@ -1,19 +1,18 @@
import { getAllAlbums, getAllAssets } from '@immich/sdk';
import { mkdir, readdir, rm, symlink } from 'node:fs/promises';
import { apiUtils, asKeyAuth, cliUtils, dbUtils, immichCli, testAssetDir } from 'src/utils';
import { asKeyAuth, immichCli, testAssetDir, utils } from 'src/utils';
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
describe(`immich upload`, () => {
let key: string;
beforeAll(async () => {
apiUtils.setup();
await dbUtils.reset();
key = await cliUtils.login();
await utils.resetDatabase();
key = await utils.cliLogin();
});
beforeEach(async () => {
await dbUtils.reset(['assets', 'albums']);
await utils.resetDatabase(['assets', 'albums']);
});
describe('immich upload --recursive', () => {

View File

@ -1,14 +1,10 @@
import { readFileSync } from 'node:fs';
import { apiUtils, immichCli } from 'src/utils';
import { beforeAll, describe, expect, it } from 'vitest';
import { immichCli } from 'src/utils';
import { describe, expect, it } from 'vitest';
const pkg = JSON.parse(readFileSync('../cli/package.json', 'utf8'));
describe(`immich --version`, () => {
beforeAll(() => {
apiUtils.setup();
});
describe('immich --version', () => {
it('should print the cli version', async () => {
const { stdout, stderr, exitCode } = await immichCli(['--version']);

View File

@ -1,8 +1,16 @@
import { exec, spawn } from 'node:child_process';
import { setTimeout } from 'node:timers';
export default async () => {
let _resolve: () => unknown;
const ready = new Promise<void>((resolve) => (_resolve = resolve));
let _reject: (error: Error) => unknown;
const ready = new Promise<void>((resolve, reject) => {
_resolve = resolve;
_reject = reject;
});
const timeout = setTimeout(() => _reject(new Error('Timeout starting e2e environment')), 60_000);
const child = spawn('docker', ['compose', 'up'], { stdio: 'pipe' });
@ -17,6 +25,7 @@ export default async () => {
child.stderr.on('data', (data) => console.log(data.toString()));
await ready;
clearTimeout(timeout);
return async () => {
await new Promise<void>((resolve) => exec('docker compose down', () => resolve()));

View File

@ -5,7 +5,7 @@ import {
CreateAssetDto,
CreateLibraryDto,
CreateUserDto,
PersonUpdateDto,
PersonCreateDto,
SharedLinkCreateDto,
ValidateLibraryDto,
createAlbum,
@ -20,12 +20,12 @@ import {
login,
setAdminOnboarding,
signUpAdmin,
updatePerson,
validate,
} from '@immich/sdk';
import { BrowserContext } from '@playwright/test';
import { exec, spawn } from 'node:child_process';
import { access } from 'node:fs/promises';
import { createHash } from 'node:crypto';
import { existsSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { promisify } from 'node:util';
@ -35,75 +35,71 @@ import { loginDto, signupDto } from 'src/fixtures';
import { makeRandomImage } from 'src/generators';
import request from 'supertest';
const execPromise = promisify(exec);
type CliResponse = { stdout: string; stderr: string; exitCode: number | null };
type EventType = 'upload' | 'delete';
type WaitOptions = { event: EventType; assetId: string; timeout?: number };
type AdminSetupOptions = { onboarding?: boolean };
type AssetData = { bytes?: Buffer; filename: string };
export const app = 'http://127.0.0.1:2283/api';
const directoryExists = (directory: string) =>
access(directory)
.then(() => true)
.catch(() => false);
const dbUrl = 'postgres://postgres:postgres@127.0.0.1:5433/immich';
const baseUrl = 'http://127.0.0.1:2283';
export const app = `${baseUrl}/api`;
// TODO move test assets into e2e/assets
export const testAssetDir = path.resolve(`./../server/test/assets/`);
export const testAssetDirInternal = '/data/assets';
export const tempDir = tmpdir();
const serverContainerName = 'immich-e2e-server';
const mediaDir = '/usr/src/app/upload';
const dirs = [
`"${mediaDir}/thumbs"`,
`"${mediaDir}/upload"`,
`"${mediaDir}/library"`,
`"${mediaDir}/encoded-video"`,
].join(' ');
if (!(await directoryExists(`${testAssetDir}/albums`))) {
throw new Error(
`Test assets not found. Please checkout https://github.com/immich-app/test-assets into ${testAssetDir} before testing`,
);
}
export const asBearerAuth = (accessToken: string) => ({
Authorization: `Bearer ${accessToken}`,
});
export const asBearerAuth = (accessToken: string) => ({ Authorization: `Bearer ${accessToken}` });
export const asKeyAuth = (key: string) => ({ 'x-api-key': key });
export const immichCli = async (args: string[]) => {
let _resolve: (value: CliResponse) => void;
const deferred = new Promise<CliResponse>((resolve) => (_resolve = resolve));
const _args = ['node_modules/.bin/immich', '-d', `/${tempDir}/immich/`, ...args];
const child = spawn('node', _args, {
stdio: 'pipe',
});
let stdout = '';
let stderr = '';
child.stdout.on('data', (data) => (stdout += data.toString()));
child.stderr.on('data', (data) => (stderr += data.toString()));
child.on('exit', (exitCode) => {
_resolve({
stdout: stdout.trim(),
stderr: stderr.trim(),
exitCode,
});
});
return deferred;
};
let client: pg.Client | null = null;
export const fileUtils = {
reset: async () => {
await execPromise(`docker exec -i "${serverContainerName}" /bin/bash -c "rm -rf ${dirs} && mkdir ${dirs}"`);
},
const events: Record<EventType, Set<string>> = {
upload: new Set<string>(),
delete: new Set<string>(),
};
export const dbUtils = {
createFace: async ({ assetId, personId }: { assetId: string; personId: string }) => {
if (!client) {
return;
const callbacks: Record<string, () => void> = {};
const execPromise = promisify(exec);
const onEvent = ({ event, assetId }: { event: EventType; assetId: string }) => {
events[event].add(assetId);
const callback = callbacks[assetId];
if (callback) {
callback();
delete callbacks[assetId];
}
};
const vector = Array.from({ length: 512 }, Math.random);
const embedding = `[${vector.join(',')}]`;
await client.query('INSERT INTO asset_faces ("assetId", "personId", "embedding") VALUES ($1, $2, $3)', [
assetId,
personId,
embedding,
]);
},
setPersonThumbnail: async (personId: string) => {
if (!client) {
return;
}
await client.query(`UPDATE "person" set "thumbnailPath" = '/my/awesome/thumbnail.jpg' where "id" = $1`, [personId]);
},
reset: async (tables?: string[]) => {
export const utils = {
resetDatabase: async (tables?: string[]) => {
try {
if (!client) {
client = new pg.Client('postgres://postgres:postgres@127.0.0.1:5433/immich');
client = new pg.Client(dbUrl);
await client.connect();
}
@ -129,83 +125,27 @@ export const dbUtils = {
throw error;
}
},
teardown: async () => {
try {
if (client) {
await client.end();
client = null;
}
} catch (error) {
console.error('Failed to teardown database', error);
throw error;
}
resetFilesystem: async () => {
const mediaInternal = '/usr/src/app/upload';
const dirs = [
`"${mediaInternal}/thumbs"`,
`"${mediaInternal}/upload"`,
`"${mediaInternal}/library"`,
`"${mediaInternal}/encoded-video"`,
].join(' ');
await execPromise(`docker exec -i "immich-e2e-server" /bin/bash -c "rm -rf ${dirs} && mkdir ${dirs}"`);
},
};
export interface CliResponse {
stdout: string;
stderr: string;
exitCode: number | null;
}
export const immichCli = async (args: string[]) => {
let _resolve: (value: CliResponse) => void;
const deferred = new Promise<CliResponse>((resolve) => (_resolve = resolve));
const _args = ['node_modules/.bin/immich', '-d', '/tmp/immich/', ...args];
const child = spawn('node', _args, {
stdio: 'pipe',
});
unzip: async (input: string, output: string) => {
await execPromise(`unzip -o -d "${output}" "${input}"`);
},
let stdout = '';
let stderr = '';
sha1: (bytes: Buffer) => createHash('sha1').update(bytes).digest('base64'),
child.stdout.on('data', (data) => (stdout += data.toString()));
child.stderr.on('data', (data) => (stderr += data.toString()));
child.on('exit', (exitCode) => {
_resolve({
stdout: stdout.trim(),
stderr: stderr.trim(),
exitCode,
});
});
return deferred;
};
export interface AdminSetupOptions {
onboarding?: boolean;
}
export enum SocketEvent {
UPLOAD = 'upload',
DELETE = 'delete',
}
export type EventType = 'upload' | 'delete';
export interface WaitOptions {
event: EventType;
assetId: string;
timeout?: number;
}
const events: Record<EventType, Set<string>> = {
upload: new Set<string>(),
delete: new Set<string>(),
};
const callbacks: Record<string, () => void> = {};
const onEvent = ({ event, assetId }: { event: EventType; assetId: string }) => {
events[event].add(assetId);
const callback = callbacks[assetId];
if (callback) {
callback();
delete callbacks[assetId];
}
};
export const wsUtils = {
connect: async (accessToken: string) => {
const websocket = io('http://127.0.0.1:2283', {
connectWebsocket: async (accessToken: string) => {
const websocket = io(baseUrl, {
path: '/api/socket.io',
transports: ['websocket'],
extraHeaders: { Authorization: `Bearer ${accessToken}` },
@ -221,7 +161,8 @@ export const wsUtils = {
.connect();
});
},
disconnect: (ws: Socket) => {
disconnectWebsocket: (ws: Socket) => {
if (ws?.connected) {
ws.disconnect();
}
@ -230,14 +171,16 @@ export const wsUtils = {
set.clear();
}
},
waitForEvent: async ({ event, assetId, timeout: ms }: WaitOptions): Promise<void> => {
waitForWebsocketEvent: async ({ event, assetId, timeout: ms }: WaitOptions): Promise<void> => {
console.log(`Waiting for ${event} [${assetId}]`);
const set = events[event];
if (set.has(assetId)) {
return;
}
return new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error(`Timed out waiting for ${event} event`)), ms || 5000);
const timeout = setTimeout(() => reject(new Error(`Timed out waiting for ${event} event`)), ms || 10_000);
callbacks[assetId] = () => {
clearTimeout(timeout);
@ -245,12 +188,8 @@ export const wsUtils = {
};
});
},
};
type AssetData = { bytes?: Buffer; filename: string };
export const apiUtils = {
setup: () => {
setApiEndpoint: () => {
defaults.baseUrl = app;
},
@ -264,17 +203,21 @@ export const apiUtils = {
}
return response;
},
userSetup: async (accessToken: string, dto: CreateUserDto) => {
await createUser({ createUserDto: dto }, { headers: asBearerAuth(accessToken) });
return login({
loginCredentialDto: { email: dto.email, password: dto.password },
});
},
createApiKey: (accessToken: string) => {
return createApiKey({ apiKeyCreateDto: { name: 'e2e' } }, { headers: asBearerAuth(accessToken) });
},
createAlbum: (accessToken: string, dto: CreateAlbumDto) =>
createAlbum({ createAlbumDto: dto }, { headers: asBearerAuth(accessToken) }),
createAsset: async (
accessToken: string,
dto?: Partial<Omit<CreateAssetDto, 'assetData'>> & { assetData?: AssetData },
@ -290,6 +233,10 @@ export const apiUtils = {
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)
.post(`/asset/upload`)
.attach('assetData', assetData, filename)
@ -303,38 +250,51 @@ export const apiUtils = {
return body as AssetFileUploadResponseDto;
},
getAssetInfo: (accessToken: string, id: string) => getAssetInfo({ id }, { headers: asBearerAuth(accessToken) }),
deleteAssets: (accessToken: string, ids: string[]) =>
deleteAssets({ assetBulkDeleteDto: { ids } }, { headers: asBearerAuth(accessToken) }),
createPerson: async (accessToken: string, dto?: PersonUpdateDto) => {
// TODO fix createPerson to accept a body
const person = await createPerson({ headers: asBearerAuth(accessToken) });
await dbUtils.setPersonThumbnail(person.id);
if (!dto) {
createPerson: async (accessToken: string, dto?: PersonCreateDto) => {
const person = await createPerson({ personCreateDto: dto || {} }, { headers: asBearerAuth(accessToken) });
await utils.setPersonThumbnail(person.id);
return person;
},
createFace: async ({ assetId, personId }: { assetId: string; personId: string }) => {
if (!client) {
return;
}
return updatePerson({ id: person.id, personUpdateDto: dto }, { headers: asBearerAuth(accessToken) });
const vector = Array.from({ length: 512 }, Math.random);
const embedding = `[${vector.join(',')}]`;
await client.query('INSERT INTO asset_faces ("assetId", "personId", "embedding") VALUES ($1, $2, $3)', [
assetId,
personId,
embedding,
]);
},
setPersonThumbnail: async (personId: string) => {
if (!client) {
return;
}
await client.query(`UPDATE "person" set "thumbnailPath" = '/my/awesome/thumbnail.jpg' where "id" = $1`, [personId]);
},
createSharedLink: (accessToken: string, dto: SharedLinkCreateDto) =>
createSharedLink({ sharedLinkCreateDto: dto }, { headers: asBearerAuth(accessToken) }),
createLibrary: (accessToken: string, dto: CreateLibraryDto) =>
createLibrary({ createLibraryDto: dto }, { headers: asBearerAuth(accessToken) }),
validateLibrary: (accessToken: string, id: string, dto: ValidateLibraryDto) =>
validate({ id, validateLibraryDto: dto }, { headers: asBearerAuth(accessToken) }),
};
export const cliUtils = {
login: async () => {
const admin = await apiUtils.adminSetup();
const key = await apiUtils.createApiKey(admin.accessToken);
await immichCli(['login-key', app, `${key.secret}`]);
return key.secret;
},
};
export const webUtils = {
setAuthCookies: async (context: BrowserContext, accessToken: string) =>
await context.addCookies([
{
@ -368,4 +328,19 @@ export const webUtils = {
sameSite: 'Lax',
},
]),
cliLogin: async () => {
const admin = await utils.adminSetup();
const key = await utils.createApiKey(admin.accessToken);
await immichCli(['login-key', app, `${key.secret}`]);
return key.secret;
},
};
utils.setApiEndpoint();
if (!existsSync(`${testAssetDir}/albums`)) {
throw new Error(
`Test assets not found. Please checkout https://github.com/immich-app/test-assets into ${testAssetDir} before testing`,
);
}

View File

@ -1,17 +1,13 @@
import { expect, test } from '@playwright/test';
import { apiUtils, dbUtils, webUtils } from 'src/utils';
import { utils } from 'src/utils';
test.describe('Registration', () => {
test.beforeAll(() => {
apiUtils.setup();
utils.setApiEndpoint();
});
test.beforeEach(async () => {
await dbUtils.reset();
});
test.afterAll(async () => {
await dbUtils.teardown();
await utils.resetDatabase();
});
test('admin registration', async ({ page }) => {
@ -45,8 +41,8 @@ test.describe('Registration', () => {
});
test('user registration', async ({ context, page }) => {
const admin = await apiUtils.adminSetup();
await webUtils.setAuthCookies(context, admin.accessToken);
const admin = await utils.adminSetup();
await utils.setAuthCookies(context, admin.accessToken);
// create user
await page.goto('/admin/user-management');

View File

@ -7,7 +7,7 @@ import {
createAlbum,
} from '@immich/sdk';
import { test } from '@playwright/test';
import { apiUtils, asBearerAuth, dbUtils } from 'src/utils';
import { asBearerAuth, utils } from 'src/utils';
test.describe('Shared Links', () => {
let admin: LoginResponseDto;
@ -17,10 +17,10 @@ test.describe('Shared Links', () => {
let sharedLinkPassword: SharedLinkResponseDto;
test.beforeAll(async () => {
apiUtils.setup();
await dbUtils.reset();
admin = await apiUtils.adminSetup();
asset = await apiUtils.createAsset(admin.accessToken);
utils.setApiEndpoint();
await utils.resetDatabase();
admin = await utils.adminSetup();
asset = await utils.createAsset(admin.accessToken);
album = await createAlbum(
{
createAlbumDto: {
@ -30,21 +30,17 @@ test.describe('Shared Links', () => {
},
{ headers: asBearerAuth(admin.accessToken) },
);
sharedLink = await apiUtils.createSharedLink(admin.accessToken, {
sharedLink = await utils.createSharedLink(admin.accessToken, {
type: SharedLinkType.Album,
albumId: album.id,
});
sharedLinkPassword = await apiUtils.createSharedLink(admin.accessToken, {
sharedLinkPassword = await utils.createSharedLink(admin.accessToken, {
type: SharedLinkType.Album,
albumId: album.id,
password: 'test-password',
});
});
test.afterAll(async () => {
await dbUtils.teardown();
});
test('download from a shared link', async ({ page }) => {
await page.goto(`/share/${sharedLink.key}`);
await page.getByRole('heading', { name: 'Test Album' }).waitFor();

View File

@ -12,6 +12,7 @@ export default defineConfig({
test: {
include: ['src/{api,cli}/specs/*.e2e-spec.ts'],
globalSetup,
testTimeout: 10_000,
poolOptions: {
threads: {
singleThread: true,

View File

@ -167,6 +167,10 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
# VS Code
.vscode
*.onnx
*.zip
core

View File

@ -39,7 +39,7 @@ FROM python:3.11-slim-bookworm@sha256:ce81dc539f0aedc9114cae640f8352fad83d37461c
FROM openvino/ubuntu22_runtime:2023.1.0@sha256:002842a9005ba01543b7169ff6f14ecbec82287f09c4d1dd37717f0a8e8754a7 as prod-openvino
USER root
FROM nvidia/cuda:12.2.2-cudnn8-runtime-ubuntu22.04@sha256:8b51b1fe922964d73c482a267b5b519e990d90bf744ec7a40419923737caff6d as prod-cuda
FROM nvidia/cuda:12.2.2-cudnn8-runtime-ubuntu22.04@sha256:2d913b09e6be8387e1a10976933642c73c840c0b735f0bf3c28d97fc9bc422e0 as prod-cuda
COPY --from=builder-cuda /usr/local/bin/python3 /usr/local/bin/python3
COPY --from=builder-cuda /usr/local/lib/python3.11 /usr/local/lib/python3.11

View File

@ -6,7 +6,7 @@ from pathlib import Path
from socket import socket
from gunicorn.arbiter import Arbiter
from pydantic import BaseSettings
from pydantic import BaseModel, BaseSettings
from rich.console import Console
from rich.logging import RichHandler
from uvicorn import Server
@ -15,6 +15,11 @@ from uvicorn.workers import UvicornWorker
from .schemas import ModelType
class PreloadModelData(BaseModel):
clip: str | None
facial_recognition: str | None
class Settings(BaseSettings):
cache_folder: str = "/cache"
model_ttl: int = 300
@ -27,10 +32,12 @@ class Settings(BaseSettings):
model_inter_op_threads: int = 0
model_intra_op_threads: int = 0
ann: bool = True
preload: PreloadModelData | None = None
class Config:
env_prefix = "MACHINE_LEARNING_"
case_sensitive = False
env_nested_delimiter = "__"
class LogSettings(BaseSettings):

View File

@ -17,7 +17,7 @@ from starlette.formparsers import MultiPartParser
from app.models.base import InferenceModel
from .config import log, settings
from .config import PreloadModelData, log, settings
from .models.cache import ModelCache
from .schemas import (
MessageResponse,
@ -27,7 +27,7 @@ from .schemas import (
MultiPartParser.max_file_size = 2**26 # spools to disk if payload is 64 MiB or larger
model_cache = ModelCache(ttl=settings.model_ttl, revalidate=settings.model_ttl > 0)
model_cache = ModelCache(revalidate=settings.model_ttl > 0)
thread_pool: ThreadPoolExecutor | None = None
lock = threading.Lock()
active_requests = 0
@ -51,6 +51,8 @@ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
log.info(f"Initialized request thread pool with {settings.request_threads} threads.")
if settings.model_ttl > 0 and settings.model_ttl_poll_s > 0:
asyncio.ensure_future(idle_shutdown_task())
if settings.preload is not None:
await preload_models(settings.preload)
yield
finally:
log.handlers.clear()
@ -61,6 +63,14 @@ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
gc.collect()
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]:
global active_requests, last_called
active_requests += 1
@ -103,7 +113,7 @@ async def predict(
except orjson.JSONDecodeError:
raise HTTPException(400, f"Invalid options JSON: {options}")
model = await load(await model_cache.get(model_name, model_type, **kwargs))
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)

View File

@ -2,7 +2,7 @@ from typing import Any
from aiocache.backends.memory import SimpleMemoryCache
from aiocache.lock import OptimisticLock
from aiocache.plugins import BasePlugin, TimingPlugin
from aiocache.plugins import TimingPlugin
from app.models import from_model_type
@ -15,28 +15,25 @@ class ModelCache:
def __init__(
self,
ttl: float | None = None,
revalidate: bool = False,
timeout: int | None = None,
profiling: bool = False,
) -> None:
"""
Args:
ttl: Unloads model after this duration. Disabled if None. Defaults to None.
revalidate: Resets TTL on cache hit. Useful to keep models in memory while active. Defaults to False.
timeout: Maximum allowed time for model to load. Disabled if None. Defaults to None.
profiling: Collects metrics for cache operations, adding slight overhead. Defaults to False.
"""
self.ttl = ttl
plugins = []
if revalidate:
plugins.append(RevalidationPlugin())
if profiling:
plugins.append(TimingPlugin())
self.cache = SimpleMemoryCache(ttl=ttl, timeout=timeout, plugins=plugins, namespace=None)
self.revalidate_enable = revalidate
self.cache = SimpleMemoryCache(timeout=timeout, plugins=plugins, namespace=None)
async def get(self, model_name: str, model_type: ModelType, **model_kwargs: Any) -> InferenceModel:
"""
@ -49,11 +46,14 @@ class ModelCache:
"""
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_type, model_name, **model_kwargs)
await lock.cas(model, ttl=self.ttl)
await lock.cas(model, ttl=model_kwargs.get("ttl", None))
elif self.revalidate_enable:
await self.revalidate(key, model_kwargs.get("ttl", None))
return model
async def get_profiling(self) -> dict[str, float] | None:
@ -62,21 +62,6 @@ class ModelCache:
return self.cache.profiling
class RevalidationPlugin(BasePlugin): # type: ignore[misc]
"""Revalidates cache item's TTL after cache hit."""
async def post_get(
self,
client: SimpleMemoryCache,
key: str,
ret: Any | None = None,
namespace: str | None = None,
**kwargs: Any,
) -> None:
if ret is None:
return
if namespace is not None:
key = client.build_key(key, namespace)
if key in client._handlers:
await client.expire(key, client.ttl)
async def revalidate(self, key: str, ttl: int | None) -> None:
if ttl is not None and key in self.cache._handlers:
await self.cache.expire(key, ttl)

View File

@ -13,11 +13,12 @@ import onnxruntime as ort
import pytest
from fastapi.testclient import TestClient
from PIL import Image
from pytest import MonkeyPatch
from pytest_mock import MockerFixture
from app.main import load
from app.main import load, preload_models
from .config import log, settings
from .config import Settings, log, settings
from .models.base import InferenceModel
from .models.cache import ModelCache
from .models.clip import MCLIPEncoder, OpenCLIPEncoder
@ -509,20 +510,20 @@ 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(ttl=100)
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION)
model_cache = ModelCache()
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(ttl=100, revalidate=True)
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION)
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION)
model_cache = ModelCache(revalidate=True)
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(ttl=100, profiling=True)
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION)
model_cache = ModelCache(profiling=True)
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
@ -548,6 +549,25 @@ class TestCache:
with pytest.raises(ValueError):
await model_cache.get("test_model_name", ModelType.CLIP, mode="text")
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"
settings = Settings()
assert settings.preload is not None
assert settings.preload.clip == "ViT-B-32__openai"
assert settings.preload.facial_recognition == "buffalo_s"
model_cache = ModelCache()
monkeypatch.setattr("app.main.model_cache", model_cache)
await preload_models(settings.preload)
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
class TestLoad:

View File

@ -1,4 +1,4 @@
FROM mambaorg/micromamba:bookworm-slim@sha256:6038b89363c9181215f3d9e8ce2720c880e224537f4028a854482e43a9b4998a as builder
FROM mambaorg/micromamba:bookworm-slim@sha256:96586e238e2fed914b839e50cf91943b5655262348d141466b34ced2e0b5b155 as builder
ENV NODE_ENV=production \
TRANSFORMERS_CACHE=/cache \

View File

@ -680,13 +680,13 @@ test = ["pytest (>=6)"]
[[package]]
name = "fastapi"
version = "0.109.2"
version = "0.110.0"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false
python-versions = ">=3.8"
files = [
{file = "fastapi-0.109.2-py3-none-any.whl", hash = "sha256:2c9bab24667293b501cad8dd388c05240c850b58ec5876ee3283c47d6e1e3a4d"},
{file = "fastapi-0.109.2.tar.gz", hash = "sha256:f3817eac96fe4f65a2ebb4baa000f394e55f5fccdaf7f75250804bc58f354f73"},
{file = "fastapi-0.110.0-py3-none-any.whl", hash = "sha256:87a1f6fb632a218222c5984be540055346a8f5d8a68e8f6fb647b1dc9934de4b"},
{file = "fastapi-0.110.0.tar.gz", hash = "sha256:266775f0dcc95af9d3ef39bad55cff525329a931d5fd51930aadd4f428bf7ff3"},
]
[package.dependencies]
@ -1274,13 +1274,13 @@ socks = ["socksio (==1.*)"]
[[package]]
name = "huggingface-hub"
version = "0.20.3"
version = "0.21.3"
description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub"
optional = false
python-versions = ">=3.8.0"
files = [
{file = "huggingface_hub-0.20.3-py3-none-any.whl", hash = "sha256:d988ae4f00d3e307b0c80c6a05ca6dbb7edba8bba3079f74cda7d9c2e562a7b6"},
{file = "huggingface_hub-0.20.3.tar.gz", hash = "sha256:94e7f8e074475fbc67d6a71957b678e1b4a74ff1b64a644fd6cbb83da962d05d"},
{file = "huggingface_hub-0.21.3-py3-none-any.whl", hash = "sha256:b183144336fdf2810a8c109822e0bb6ef1fd61c65da6fb60e8c3f658b7144016"},
{file = "huggingface_hub-0.21.3.tar.gz", hash = "sha256:26a15b604e4fc7bad37c467b76456543ec849386cbca9cd7e1e135f53e500423"},
]
[package.dependencies]
@ -1297,11 +1297,12 @@ all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "gradio", "jedi",
cli = ["InquirerPy (==0.3.4)"]
dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "gradio", "jedi", "mypy (==1.5.1)", "numpy", "pydantic (>1.1,<2.0)", "pydantic (>1.1,<3.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.1.3)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"]
fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"]
hf-transfer = ["hf-transfer (>=0.1.4)"]
inference = ["aiohttp", "pydantic (>1.1,<2.0)", "pydantic (>1.1,<3.0)"]
quality = ["mypy (==1.5.1)", "ruff (>=0.1.3)"]
tensorflow = ["graphviz", "pydot", "tensorflow"]
testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "gradio", "jedi", "numpy", "pydantic (>1.1,<2.0)", "pydantic (>1.1,<3.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"]
torch = ["torch"]
torch = ["safetensors", "torch"]
typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"]
[[package]]
@ -1566,13 +1567,13 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"]
[[package]]
name = "locust"
version = "2.23.1"
version = "2.24.0"
description = "Developer friendly load testing framework"
optional = false
python-versions = ">=3.8"
files = [
{file = "locust-2.23.1-py3-none-any.whl", hash = "sha256:96013a460a4b4d6d4fd46c70e6ff1fd2b6e03b48ddb1b48d1513d3134ba2cecf"},
{file = "locust-2.23.1.tar.gz", hash = "sha256:6cc729729e5ebf5852fc9d845302cfcf0ab0132f198e68b3eb0c88b438b6a863"},
{file = "locust-2.24.0-py3-none-any.whl", hash = "sha256:1b6b878b4fd0108fec956120815e69775d2616c8f4d1e9f365c222a7a5c17d9a"},
{file = "locust-2.24.0.tar.gz", hash = "sha256:6cffa378d995244a7472af6be1d6139331f19aee44e907deee73e0281252804d"},
]
[package.dependencies]
@ -1588,6 +1589,7 @@ pywin32 = {version = "*", markers = "platform_system == \"Windows\""}
pyzmq = ">=25.0.0"
requests = ">=2.26.0"
roundrobin = ">=0.0.2"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
Werkzeug = ">=2.0.0"
[[package]]
@ -1988,36 +1990,36 @@ reference = ["Pillow", "google-re2"]
[[package]]
name = "onnxruntime"
version = "1.17.0"
version = "1.17.1"
description = "ONNX Runtime is a runtime accelerator for Machine Learning models"
optional = false
python-versions = "*"
files = [
{file = "onnxruntime-1.17.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:d2b22a25a94109cc983443116da8d9805ced0256eb215c5e6bc6dcbabefeab96"},
{file = "onnxruntime-1.17.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4c87d83c6f58d1af2675fc99e3dc810f2dbdb844bcefd0c1b7573632661f6fc"},
{file = "onnxruntime-1.17.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dba55723bf9b835e358f48c98a814b41692c393eb11f51e02ece0625c756b797"},
{file = "onnxruntime-1.17.0-cp310-cp310-win32.whl", hash = "sha256:ee48422349cc500273beea7607e33c2237909f58468ae1d6cccfc4aecd158565"},
{file = "onnxruntime-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:f34cc46553359293854e38bdae2ab1be59543aad78a6317e7746d30e311110c3"},
{file = "onnxruntime-1.17.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:16d26badd092c8c257fa57c458bb600d96dc15282c647ccad0ed7b2732e6c03b"},
{file = "onnxruntime-1.17.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6f1273bebcdb47ed932d076c85eb9488bc4768fcea16d5f2747ca692fad4f9d3"},
{file = "onnxruntime-1.17.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cb60fd3c2c1acd684752eb9680e89ae223e9801a9b0e0dc7b28adabe45a2e380"},
{file = "onnxruntime-1.17.0-cp311-cp311-win32.whl", hash = "sha256:4b038324586bc905299e435f7c00007e6242389c856b82fe9357fdc3b1ef2bdc"},
{file = "onnxruntime-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:93d39b3fa1ee01f034f098e1c7769a811a21365b4883f05f96c14a2b60c6028b"},
{file = "onnxruntime-1.17.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:90c0890e36f880281c6c698d9bc3de2afbeee2f76512725ec043665c25c67d21"},
{file = "onnxruntime-1.17.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7466724e809a40e986b1637cba156ad9fc0d1952468bc00f79ef340bc0199552"},
{file = "onnxruntime-1.17.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d47bee7557a8b99c8681b6882657a515a4199778d6d5e24e924d2aafcef55b0a"},
{file = "onnxruntime-1.17.0-cp312-cp312-win32.whl", hash = "sha256:bb1bf1ee575c665b8bbc3813ab906e091a645a24ccc210be7932154b8260eca1"},
{file = "onnxruntime-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:ac2f286da3494b29b4186ca193c7d4e6a2c1f770c4184c7192c5da142c3dec28"},
{file = "onnxruntime-1.17.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1ec485643b93e0a3896c655eb2426decd63e18a278bb7ccebc133b340723624f"},
{file = "onnxruntime-1.17.0-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83c35809cda898c5a11911c69ceac8a2ac3925911854c526f73bad884582f911"},
{file = "onnxruntime-1.17.0-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fa464aa4d81df818375239e481887b656e261377d5b6b9a4692466f5f3261edc"},
{file = "onnxruntime-1.17.0-cp38-cp38-win32.whl", hash = "sha256:b7b337cd0586f7836601623cbd30a443df9528ef23965860d11c753ceeb009f2"},
{file = "onnxruntime-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:fbb9faaf51d01aa2c147ef52524d9326744c852116d8005b9041809a71838878"},
{file = "onnxruntime-1.17.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:5a06ab84eaa350bf64b1d747b33ccf10da64221ed1f38f7287f15eccbec81603"},
{file = "onnxruntime-1.17.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d3d11db2c8242766212a68d0b139745157da7ce53bd96ba349a5c65e5a02357"},
{file = "onnxruntime-1.17.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5632077c3ab8b0cd4f74b0af9c4e924be012b1a7bcd7daa845763c6c6bf14b7d"},
{file = "onnxruntime-1.17.0-cp39-cp39-win32.whl", hash = "sha256:61a12732cba869b3ad2d4e29ab6cb62c7a96f61b8c213f7fcb961ba412b70b37"},
{file = "onnxruntime-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:461fa0fc7d9c392c352b6cccdedf44d818430f3d6eacd924bb804fdea2dcfd02"},
{file = "onnxruntime-1.17.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:d43ac17ac4fa3c9096ad3c0e5255bb41fd134560212dc124e7f52c3159af5d21"},
{file = "onnxruntime-1.17.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55b5e92a4c76a23981c998078b9bf6145e4fb0b016321a8274b1607bd3c6bd35"},
{file = "onnxruntime-1.17.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ebbcd2bc3a066cf54e6f18c75708eb4d309ef42be54606d22e5bdd78afc5b0d7"},
{file = "onnxruntime-1.17.1-cp310-cp310-win32.whl", hash = "sha256:5e3716b5eec9092e29a8d17aab55e737480487deabfca7eac3cd3ed952b6ada9"},
{file = "onnxruntime-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:fbb98cced6782ae1bb799cc74ddcbbeeae8819f3ad1d942a74d88e72b6511337"},
{file = "onnxruntime-1.17.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:36fd6f87a1ecad87e9c652e42407a50fb305374f9a31d71293eb231caae18784"},
{file = "onnxruntime-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99a8bddeb538edabc524d468edb60ad4722cff8a49d66f4e280c39eace70500b"},
{file = "onnxruntime-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd7fddb4311deb5a7d3390cd8e9b3912d4d963efbe4dfe075edbaf18d01c024e"},
{file = "onnxruntime-1.17.1-cp311-cp311-win32.whl", hash = "sha256:606a7cbfb6680202b0e4f1890881041ffc3ac6e41760a25763bd9fe146f0b335"},
{file = "onnxruntime-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:53e4e06c0a541696ebdf96085fd9390304b7b04b748a19e02cf3b35c869a1e76"},
{file = "onnxruntime-1.17.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:40f08e378e0f85929712a2b2c9b9a9cc400a90c8a8ca741d1d92c00abec60843"},
{file = "onnxruntime-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac79da6d3e1bb4590f1dad4bb3c2979d7228555f92bb39820889af8b8e6bd472"},
{file = "onnxruntime-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ae9ba47dc099004e3781f2d0814ad710a13c868c739ab086fc697524061695ea"},
{file = "onnxruntime-1.17.1-cp312-cp312-win32.whl", hash = "sha256:2dff1a24354220ac30e4a4ce2fb1df38cb1ea59f7dac2c116238d63fe7f4c5ff"},
{file = "onnxruntime-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:6226a5201ab8cafb15e12e72ff2a4fc8f50654e8fa5737c6f0bd57c5ff66827e"},
{file = "onnxruntime-1.17.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:cd0c07c0d1dfb8629e820b05fda5739e4835b3b82faf43753d2998edf2cf00aa"},
{file = "onnxruntime-1.17.1-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:617ebdf49184efa1ba6e4467e602fbfa029ed52c92f13ce3c9f417d303006381"},
{file = "onnxruntime-1.17.1-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9dae9071e3facdf2920769dceee03b71c684b6439021defa45b830d05e148924"},
{file = "onnxruntime-1.17.1-cp38-cp38-win32.whl", hash = "sha256:835d38fa1064841679433b1aa8138b5e1218ddf0cfa7a3ae0d056d8fd9cec713"},
{file = "onnxruntime-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:96621e0c555c2453bf607606d08af3f70fbf6f315230c28ddea91754e17ad4e6"},
{file = "onnxruntime-1.17.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:7a9539935fb2d78ebf2cf2693cad02d9930b0fb23cdd5cf37a7df813e977674d"},
{file = "onnxruntime-1.17.1-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45c6a384e9d9a29c78afff62032a46a993c477b280247a7e335df09372aedbe9"},
{file = "onnxruntime-1.17.1-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e19f966450f16863a1d6182a685ca33ae04d7772a76132303852d05b95411ea"},
{file = "onnxruntime-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e2ae712d64a42aac29ed7a40a426cb1e624a08cfe9273dcfe681614aa65b07dc"},
{file = "onnxruntime-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:f7e9f7fb049825cdddf4a923cfc7c649d84d63c0134315f8e0aa9e0c3004672c"},
]
[package.dependencies]
@ -2633,6 +2635,7 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
@ -2812,13 +2815,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "rich"
version = "13.7.0"
version = "13.7.1"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
optional = false
python-versions = ">=3.7.0"
files = [
{file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"},
{file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"},
{file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"},
{file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"},
]
[package.dependencies]
@ -2840,28 +2843,28 @@ files = [
[[package]]
name = "ruff"
version = "0.2.2"
version = "0.3.0"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"},
{file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3"},
{file = "ruff-0.2.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726"},
{file = "ruff-0.2.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e"},
{file = "ruff-0.2.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e"},
{file = "ruff-0.2.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9"},
{file = "ruff-0.2.2-py3-none-win32.whl", hash = "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325"},
{file = "ruff-0.2.2-py3-none-win_amd64.whl", hash = "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d"},
{file = "ruff-0.2.2-py3-none-win_arm64.whl", hash = "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd"},
{file = "ruff-0.2.2.tar.gz", hash = "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d"},
{file = "ruff-0.3.0-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7deb528029bacf845bdbb3dbb2927d8ef9b4356a5e731b10eef171e3f0a85944"},
{file = "ruff-0.3.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e1e0d4381ca88fb2b73ea0766008e703f33f460295de658f5467f6f229658c19"},
{file = "ruff-0.3.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f7dbba46e2827dfcb0f0cc55fba8e96ba7c8700e0a866eb8cef7d1d66c25dcb"},
{file = "ruff-0.3.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:23dbb808e2f1d68eeadd5f655485e235c102ac6f12ad31505804edced2a5ae77"},
{file = "ruff-0.3.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ef655c51f41d5fa879f98e40c90072b567c666a7114fa2d9fe004dffba00932"},
{file = "ruff-0.3.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d0d3d7ef3d4f06433d592e5f7d813314a34601e6c5be8481cccb7fa760aa243e"},
{file = "ruff-0.3.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b08b356d06a792e49a12074b62222f9d4ea2a11dca9da9f68163b28c71bf1dd4"},
{file = "ruff-0.3.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9343690f95710f8cf251bee1013bf43030072b9f8d012fbed6ad702ef70d360a"},
{file = "ruff-0.3.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1f3ed501a42f60f4dedb7805fa8d4534e78b4e196f536bac926f805f0743d49"},
{file = "ruff-0.3.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:cc30a9053ff2f1ffb505a585797c23434d5f6c838bacfe206c0e6cf38c921a1e"},
{file = "ruff-0.3.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5da894a29ec018a8293d3d17c797e73b374773943e8369cfc50495573d396933"},
{file = "ruff-0.3.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:755c22536d7f1889be25f2baf6fedd019d0c51d079e8417d4441159f3bcd30c2"},
{file = "ruff-0.3.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dd73fe7f4c28d317855da6a7bc4aa29a1500320818dd8f27df95f70a01b8171f"},
{file = "ruff-0.3.0-py3-none-win32.whl", hash = "sha256:19eacceb4c9406f6c41af806418a26fdb23120dfe53583df76d1401c92b7c14b"},
{file = "ruff-0.3.0-py3-none-win_amd64.whl", hash = "sha256:128265876c1d703e5f5e5a4543bd8be47c73a9ba223fd3989d4aa87dd06f312f"},
{file = "ruff-0.3.0-py3-none-win_arm64.whl", hash = "sha256:e3a4a6d46aef0a84b74fcd201a4401ea9a6cd85614f6a9435f2d33dd8cefbf83"},
{file = "ruff-0.3.0.tar.gz", hash = "sha256:0886184ba2618d815067cf43e005388967b67ab9c80df52b32ec1152ab49f53a"},
]
[[package]]

View File

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

View File

@ -62,9 +62,12 @@ fi
if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then
echo "Pumping Server: $CURRENT_SERVER => $NEXT_SERVER"
npm --prefix server version "$SERVER_PUMP"
npm --prefix web version "$SERVER_PUMP"
npm --prefix open-api/typescript-sdk version "$SERVER_PUMP"
make open-api
npm --prefix open-api/typescript-sdk version "$SERVER_PUMP"
npm --prefix web version "$SERVER_PUMP"
npm --prefix web i --package-lock-only
npm --prefix cli i --package-lock-only
npm --prefix e2e i --package-lock-only
poetry --directory machine-learning version "$SERVER_PUMP"
fi

View File

@ -35,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 125,
"android.injected.version.name" => "1.97.0",
"android.injected.version.code" => 126,
"android.injected.version.name" => "1.98.0",
}
)
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "DETALLS",
"exif_bottom_sheet_location": "UBICACIÓ",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Work in progress",
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Use at your own risk!",

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "PODROBNOSTI",
"exif_bottom_sheet_location": "LOKALITA",
"exif_bottom_sheet_location_add": "Přidat polohu",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Zpracovávám",
"experimental_settings_new_asset_list_title": "Povolení experimentální mřížky fotografií",
"experimental_settings_subtitle": "Používejte na vlastní riziko!",

View File

@ -35,8 +35,8 @@
"app_bar_signout_dialog_title": "Log ud",
"archive_page_no_archived_assets": "Ingen arkiverede elementer blev fundet",
"archive_page_title": "Arkivér ({})",
"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_action_delete_err_read_only": "Kan ikke slette kun læselige elementer. Springer over",
"asset_action_share_err_offline": "Kan ikke hente offline element(er). Springer over",
"asset_list_layout_settings_dynamic_layout_title": "Dynamisk layout",
"asset_list_layout_settings_group_automatically": "Automatisk",
"asset_list_layout_settings_group_by": "Gruppér elementer pr. ",
@ -150,7 +150,7 @@
"control_bottom_app_bar_share": "Del",
"control_bottom_app_bar_share_to": "Del til",
"control_bottom_app_bar_stack": "Stak",
"control_bottom_app_bar_trash_from_immich": "Move to Trash",
"control_bottom_app_bar_trash_from_immich": "Flyt til papirkurv",
"control_bottom_app_bar_unarchive": "Afakivér",
"control_bottom_app_bar_unfavorite": "Fjern favorit",
"control_bottom_app_bar_upload": "Upload",
@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "DETALJER",
"exif_bottom_sheet_location": "LOKATION",
"exif_bottom_sheet_location_add": "Tilføj en placering",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Under udarbejdelse",
"experimental_settings_new_asset_list_title": "Aktiver eksperimentelt fotogitter",
"experimental_settings_subtitle": "Brug på eget ansvar!",
@ -199,7 +200,7 @@
"home_page_archive_err_partner": "Kan endnu ikke arkivere partners elementer. Springer over",
"home_page_building_timeline": "Bygger tidslinjen",
"home_page_delete_err_partner": "Kan endnu ikke slette partners elementer. Springer over",
"home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping",
"home_page_delete_remote_err_local": "Lokale elementer i fjernsletningssektion. Springer over",
"home_page_favorite_err_local": "Kan endnu ikke gøre lokale elementer til favoritter. Springer over..",
"home_page_favorite_err_partner": "Kan endnu ikke tilføje partners elementer som favoritter. Springer over",
"home_page_first_time_notice": "Hvis det er din første gang i appen, bedes du vælge en sikkerhedskopi af albummer så tidlinjen kan blive fyldt med billeder og videoer fra albummerne.",
@ -279,8 +280,8 @@
"map_zoom_to_see_photos": "Zoom ud for at vise billeder",
"monthly_title_text_date_format": "MMMM y",
"motion_photos_page_title": "Bevægelsesbilleder",
"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",
"multiselect_grid_edit_date_time_err_read_only": "Kan ikke redigere datoen på kun læselige elementer. Springer over",
"multiselect_grid_edit_gps_err_read_only": "Kan ikke redigere lokation af kun læselige elementer. Springer over",
"notification_permission_dialog_cancel": "Annuller",
"notification_permission_dialog_content": "Gå til indstillinger for at slå notifikationer til.",
"notification_permission_dialog_settings": "Indstillinger",

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "DETAILS",
"exif_bottom_sheet_location": "STANDORT",
"exif_bottom_sheet_location_add": "Aufnahmeort hinzufügen",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "In Arbeit",
"experimental_settings_new_asset_list_title": "Experimentelles Fotogitter aktivieren",
"experimental_settings_subtitle": "Benutzung auf eigene Gefahr!",

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "DETAILS",
"exif_bottom_sheet_location": "LOCATION",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Work in progress",
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Use at your own risk!",

View File

@ -1,6 +1,6 @@
{
"action_common_cancel": "Cancel",
"action_common_update": "Update",
"action_common_cancel": "Cancelar",
"action_common_update": "Actualizar",
"add_to_album_bottom_sheet_added": "Agregado a {album}",
"add_to_album_bottom_sheet_already_exists": "Ya se encuentra en {album}",
"advanced_settings_log_level_title": "Nivel de log: {}",
@ -35,7 +35,7 @@
"app_bar_signout_dialog_title": "Cerrar sesión",
"archive_page_no_archived_assets": "No se encontraron recursos archivados",
"archive_page_title": "Archivo ({})",
"asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping",
"asset_action_delete_err_read_only": "No se pueden borrar los archivos de solo lectura. Saltando.",
"asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping",
"asset_list_layout_settings_dynamic_layout_title": "Diseño dinámico",
"asset_list_layout_settings_group_automatically": "Automatico",
@ -142,15 +142,15 @@
"control_bottom_app_bar_archive": "Archivar",
"control_bottom_app_bar_create_new_album": "Crear nuevo álbum",
"control_bottom_app_bar_delete": "Eliminar",
"control_bottom_app_bar_delete_from_immich": "Delete from Immich",
"control_bottom_app_bar_delete_from_local": "Delete from device",
"control_bottom_app_bar_delete_from_immich": "Borrar de Immich",
"control_bottom_app_bar_delete_from_local": "Borrar del dispositivo",
"control_bottom_app_bar_edit_location": "Editar ubicación",
"control_bottom_app_bar_edit_time": "Editar fecha y hora",
"control_bottom_app_bar_favorite": "Favorito",
"control_bottom_app_bar_share": "Compartir",
"control_bottom_app_bar_share_to": "Enviar",
"control_bottom_app_bar_stack": "Apilar",
"control_bottom_app_bar_trash_from_immich": "Move to Trash",
"control_bottom_app_bar_trash_from_immich": "Mover a la papelera",
"control_bottom_app_bar_unarchive": "Desarchivar",
"control_bottom_app_bar_unfavorite": "Unfavorite",
"control_bottom_app_bar_upload": "Subir",
@ -165,9 +165,9 @@
"daily_title_text_date_year": "E dd de MMM, yyyy",
"date_format": "E d, LLL y • h:mm a",
"delete_dialog_alert": "Estos elementos serán eliminados permanentemente de Immich y de tu dispositivo",
"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_alert_local": "Estas imágenes van a ser borradas de tu dispositivo, pero seguirán disponibles en el servidor Immich",
"delete_dialog_alert_local_non_backed_up": "Algunas de las imágenes no tienen copia de seguridad y serán borradas de forma permanente de tu dispositivo",
"delete_dialog_alert_remote": "Estas imágenes van a ser borradas de forma permanente del servidor Immich",
"delete_dialog_cancel": "Cancelar",
"delete_dialog_ok": "Eliminar",
"delete_dialog_ok_force": "Delete Anyway",
@ -178,13 +178,14 @@
"delete_shared_link_dialog_title": "Eliminar enlace compartido",
"description_input_hint_text": "Agregar descripción...",
"description_input_submit_error": "Error al actualizar la descripción, verifica el registro para obtener más detalles",
"edit_date_time_dialog_date_time": "Date and Time",
"edit_date_time_dialog_timezone": "Timezone",
"edit_location_dialog_title": "Location",
"edit_date_time_dialog_date_time": "Fecha y Hora",
"edit_date_time_dialog_timezone": "Zona horaria",
"edit_location_dialog_title": "Ubicación",
"exif_bottom_sheet_description": "Agregar Descripción...",
"exif_bottom_sheet_details": "DETALLES",
"exif_bottom_sheet_location": "UBICACIÓN",
"exif_bottom_sheet_location_add": "Añadir ubicación",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Trabajo en progreso",
"experimental_settings_new_asset_list_title": "Habilitar cuadrícula fotográfica experimental",
"experimental_settings_subtitle": "Úsalo bajo tu responsabilidad",
@ -220,13 +221,13 @@
"library_page_sort_most_oldest_photo": "Foto más antigua",
"library_page_sort_most_recent_photo": "Foto más reciente",
"library_page_sort_title": "Título del álbum",
"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",
"location_picker_choose_on_map": "Elegir en el mapa",
"location_picker_latitude": "Latitud",
"location_picker_latitude_error": "Introduce una latitud válida",
"location_picker_latitude_hint": "Introduce tu latitud aquí",
"location_picker_longitude": "Longitud",
"location_picker_longitude_error": "Introduce una longitud válida",
"location_picker_longitude_hint": "Introduce tu longitud aquí",
"login_disabled": "El inicio de sesión ha sido desactivado",
"login_form_api_exception": "Excepción producida por API. Por favor, verifica el URL del servidor e inténtalo de nuevo.",
"login_form_back_button_text": "Atrás",
@ -252,12 +253,12 @@
"login_form_server_error": "No se pudo conectar al servidor.",
"login_password_changed_error": "Hubo un error actualizando la contraseña",
"login_password_changed_success": "Contraseña cambiado con éxito",
"map_assets_in_bound": "{} photo",
"map_assets_in_bounds": "{} photos",
"map_assets_in_bound": "{} foto",
"map_assets_in_bounds": "{} fotos",
"map_cannot_get_user_location": "No se pudo obtener la posición del usuario",
"map_location_dialog_cancel": "Cancelar",
"map_location_dialog_yes": "Sí",
"map_location_picker_page_use_location": "Use this location",
"map_location_picker_page_use_location": "Usar esta ubicación",
"map_location_service_disabled_content": "Los servicios de ubicación deben estar activados para mostrar elementos de tu ubicación actual. Deseas activarlos ahora?",
"map_location_service_disabled_title": "Servicios de ubicación desactivados",
"map_no_assets_in_bounds": "No hay fotos en esta zona",
@ -265,22 +266,22 @@
"map_no_location_permission_title": "Permisos de ubicación denegados",
"map_settings_dark_mode": "Modo oscuro",
"map_settings_date_range_option_all": "Todo",
"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_date_range_option_day": "Últimas 24 horas",
"map_settings_date_range_option_days": "Últimos {} días",
"map_settings_date_range_option_year": "Último año",
"map_settings_date_range_option_years": "Últimos {} años",
"map_settings_dialog_cancel": "Cancelar",
"map_settings_dialog_save": "Guardar",
"map_settings_dialog_title": "Ajustes mapa",
"map_settings_include_show_archived": "Incluir archivados",
"map_settings_only_relative_range": "Rango de fechas",
"map_settings_only_show_favorites": "Mostrar solo favoritas",
"map_settings_theme_settings": "Map Theme",
"map_settings_theme_settings": "Apariencia del Mapa",
"map_zoom_to_see_photos": "Alejar para ver fotos",
"monthly_title_text_date_format": "MMMM y",
"motion_photos_page_title": "Foto en Movimiento",
"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",
"multiselect_grid_edit_date_time_err_read_only": "No se puede cambiar la fecha de archivos de solo lectura. Saltando.",
"multiselect_grid_edit_gps_err_read_only": "No se puede cambiar la localización de archivos de solo lectura. Saltando.",
"notification_permission_dialog_cancel": "Cancelar",
"notification_permission_dialog_content": "Para activar las notificaciones, ve a Configuración y selecciona permitir.",
"notification_permission_dialog_settings": "Ajustes",
@ -318,7 +319,7 @@
"profile_drawer_sign_out": "Cerrar Sesión",
"profile_drawer_trash": "Papelera",
"recently_added_page_title": "Recién Agregadas",
"scaffold_body_error_occurred": "Error occurred",
"scaffold_body_error_occurred": "Ha ocurrido un error",
"search_bar_hint": "Busca tus fotos",
"search_page_categories": "Categorías",
"search_page_favorites": "Favoritos",
@ -330,9 +331,9 @@
"search_page_person_add_name_dialog_hint": "Nombre",
"search_page_person_add_name_dialog_save": "Guardar",
"search_page_person_add_name_dialog_title": "Añadir nombre",
"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_person_add_name_subtitle": "Encuéntralos rápido buscando por nombre",
"search_page_person_add_name_title": "Añadir nombre",
"search_page_person_edit_name": "Cambiar nombre",
"search_page_places": "Lugares",
"search_page_recently_added": "Recién agregadas",
"search_page_screenshots": "Capturas de pantalla",
@ -341,7 +342,7 @@
"search_page_videos": "Videos",
"search_page_view_all_button": "Ver todo",
"search_page_your_activity": "Tu actividad",
"search_page_your_map": "Your Map",
"search_page_your_map": "Tu Mapa",
"search_result_page_new_search_hint": "Nueva Busqueda",
"search_suggestion_list_smart_search_hint_1": "La búsqueda inteligente está habilitada por defecto, para buscar metadatos utiliza esta sintaxis ",
"search_suggestion_list_smart_search_hint_2": "m:tu-término-de-búsqueda",
@ -381,17 +382,17 @@
"shared_album_activity_remove_title": "Eliminar Actividad",
"shared_album_activity_setting_subtitle": "Permitir que otros respondan",
"shared_album_activity_setting_title": "Comentarios y me gusta",
"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",
"shared_album_section_people_action_error": "Error dejando/eliminando del album",
"shared_album_section_people_action_leave": "Eliminar usuario del album",
"shared_album_section_people_action_remove_user": "Eliminar usuario del album",
"shared_album_section_people_owner_label": "Propietario",
"shared_album_section_people_title": "GENTE",
"share_dialog_preparing": "Preparando...",
"shared_link_app_bar_title": "Enlaces compartidos",
"shared_link_clipboard_copied_massage": "Copied to clipboard",
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
"shared_link_clipboard_copied_massage": "Copiado al portapapeles",
"shared_link_clipboard_text": "Enlace: {}\nContraseña: {}",
"shared_link_create_app_bar_title": "Crear enlace compartido",
"shared_link_create_error": "Error while creating shared link",
"shared_link_create_error": "Error creando el enlace compartido",
"shared_link_create_info": "Cualquier persona con el enlace puede ver las fotos seleccionadas",
"shared_link_create_submit_button": "Crear enlace",
"shared_link_edit_allow_download": "Permitir descargar a usuarios públicos",
@ -401,32 +402,32 @@
"shared_link_edit_description": "Descripción",
"shared_link_edit_description_hint": "Introduce la descripción del enlace",
"shared_link_edit_expire_after": "Expirar después de",
"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_never": "Never",
"shared_link_edit_expire_after_option_day": "1 día",
"shared_link_edit_expire_after_option_days": "{} días",
"shared_link_edit_expire_after_option_hour": "1 hora",
"shared_link_edit_expire_after_option_hours": "{} horas",
"shared_link_edit_expire_after_option_minute": "1 minuto",
"shared_link_edit_expire_after_option_minutes": "{} minutos",
"shared_link_edit_expire_after_option_never": "Nunca",
"shared_link_edit_password": "Contraseña",
"shared_link_edit_password_hint": "Introduce la contraseña del enlace",
"shared_link_edit_show_meta": "Mostrar metadatos",
"shared_link_edit_submit_button": "Actualizar enlace",
"shared_link_empty": "No tienes enlaces compartidos",
"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_error_server_url_fetch": "No se puede adquirir la URL del servidor",
"shared_link_expired": "Caducado",
"shared_link_expires_day": "Caduca en {} día",
"shared_link_expires_days": "Caduca en {} días",
"shared_link_expires_hour": "Caduca en {} hora",
"shared_link_expires_hours": "Caduca en {} horas",
"shared_link_expires_minute": "Caduca en {} minuto",
"shared_link_expires_minutes": "Caduca en {} minutos",
"shared_link_expires_never": "Expires ∞",
"shared_link_expires_second": "Expires in {} second",
"shared_link_expires_never": "Caduca ∞",
"shared_link_expires_second": "Caduca en {} segundo",
"shared_link_expires_seconds": "Caduca en {} segundos",
"shared_link_info_chip_download": "Download",
"shared_link_info_chip_download": "Descargar",
"shared_link_info_chip_metadata": "EXIF",
"shared_link_info_chip_upload": "Upload",
"shared_link_info_chip_upload": "Subir",
"shared_link_manage_links": "Administrar enlaces compartidos",
"share_done": "Hecho",
"share_invite": "Invitar al álbum",

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "DETALLES",
"exif_bottom_sheet_location": "UBICACIÓN",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Trabajo en progreso",
"experimental_settings_new_asset_list_title": "Habilitar cuadrícula fotográfica experimental",
"experimental_settings_subtitle": "Úsalo bajo tu responsabilidad",

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "DETALLES",
"exif_bottom_sheet_location": "UBICACIÓN",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Trabajo en progreso",
"experimental_settings_new_asset_list_title": "Habilitar cuadrícula fotográfica experimental",
"experimental_settings_subtitle": "Úsalo bajo tu responsabilidad",

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "TIEDOT",
"exif_bottom_sheet_location": "SIJAINTI",
"exif_bottom_sheet_location_add": "Lisää sijainti",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Työn alla",
"experimental_settings_new_asset_list_title": "Ota käyttöön kokeellinen kuvaruudukko",
"experimental_settings_subtitle": "Käyttö omalla vastuulla!",
@ -199,7 +200,7 @@
"home_page_archive_err_partner": "Kumppanin kohteita ei voi arkistoida. Hypätään yli",
"home_page_building_timeline": "Rakennetaan aikajanaa",
"home_page_delete_err_partner": "Kumppanin kohteita ei voi poistaa.Hypätään yli",
"home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping",
"home_page_delete_remote_err_local": "Paikallisia kohteita etäkohdevalintojen joukossa, ohitetaan",
"home_page_favorite_err_local": "Paikallisten kohteiden lisääminen suosikkeihin ei ole mahdollista, ohitetaan",
"home_page_favorite_err_partner": "Kumppanin kohteita ei voi vielä merkitä suosikiksi. Hypätään yli",
"home_page_first_time_notice": "Jos käytät sovellusta ensimmäistä kertaa, muista valita varmuuskopioitavat albumi(t), jotta aikajanalla voi olla kuvia ja videoita.",

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "DÉTAILS",
"exif_bottom_sheet_location": "LOCALISATION",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "En cours de développement",
"experimental_settings_new_asset_list_title": "Activer la grille de photos expérimentale",
"experimental_settings_subtitle": "Utilisez à vos dépends !",

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "DETAILS",
"exif_bottom_sheet_location": "LOCATION",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Work in progress",
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Use at your own risk!",

View File

@ -1,74 +1,74 @@
{
"action_common_cancel": "Cancel",
"action_common_update": "Update",
"action_common_cancel": "Mégsem",
"action_common_update": "Frissít",
"add_to_album_bottom_sheet_added": "Hozzáadva a(z) {album} nevű albumhoz",
"add_to_album_bottom_sheet_already_exists": "Már eleme a(z) {album} nevű albumnak",
"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_log_level_title": "Naplózás szintje: {}",
"advanced_settings_prefer_remote_subtitle": "Néhány eszköz fájdalmasan lassan tölti be az eszközön lévő elemeket. Ezzel a beállítással inkább a távoli képeket töltjük be helyette.",
"advanced_settings_prefer_remote_title": "Távoli képek preferálása",
"advanced_settings_self_signed_ssl_subtitle": "SSL tanúsítvány ellenőrzésének kihagyása a szerver végponthoz. Ehhez saját aláírt tanúsítványok szükségesek.",
"advanced_settings_self_signed_ssl_title": "Saját aláírt SSL tanúsítványok engedélyezése",
"advanced_settings_tile_subtitle": "Haladó felhasználói beállítások",
"advanced_settings_tile_title": "Haladó",
"advanced_settings_troubleshooting_subtitle": "További funkciók engedélyezése hibaelhárítás céljából",
"advanced_settings_troubleshooting_title": "Hibaelhárítás",
"album_info_card_backup_album_excluded": "EXCLUDED",
"album_info_card_backup_album_included": "INCLUDED",
"album_info_card_backup_album_excluded": "KIZÁRVA",
"album_info_card_backup_album_included": "BELEÉRTVE",
"album_thumbnail_card_item": "1 elem",
"album_thumbnail_card_items": "{} elem",
"album_thumbnail_card_shared": "· Megosztott",
"album_thumbnail_owned": "Tulajdonos",
"album_thumbnail_shared_by": "Megosztotta: {}",
"album_viewer_appbar_share_delete": "Album törlése",
"album_viewer_appbar_share_err_delete": "Hiba az album törlése közben",
"album_viewer_appbar_share_err_leave": "Hiba az albumból való kilépés közben",
"album_viewer_appbar_share_err_remove": "Hiba az elemek törlése közben",
"album_viewer_appbar_share_err_title": "Hiba az album átnevezése közben",
"album_viewer_appbar_share_err_delete": "Nem sikerült törölni az albumot",
"album_viewer_appbar_share_err_leave": "Nem sikerült kilépni az albumból",
"album_viewer_appbar_share_err_remove": "Néhány elemet nem sikerült törölni az albumból",
"album_viewer_appbar_share_err_title": "Nem sikerült átnevezni az albumot",
"album_viewer_appbar_share_leave": "Kilépés az albumból",
"album_viewer_appbar_share_remove": "Törlés az albumból",
"album_viewer_appbar_share_to": "Share To",
"album_viewer_appbar_share_remove": "Eltávolítás az albumból",
"album_viewer_appbar_share_to": "Megosztás Ide",
"album_viewer_page_share_add_users": "Felhasználók hozzáadása",
"all_people_page_title": "Emberek",
"all_videos_page_title": "Videók",
"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": "Nem található archivált média",
"app_bar_signout_dialog_content": "Biztos, hogy ki szeretnél jelentkezni?",
"app_bar_signout_dialog_ok": "Igen",
"app_bar_signout_dialog_title": "Kijelentkezés",
"archive_page_no_archived_assets": "Nem található archivált elem",
"archive_page_title": "Archívum ({})",
"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_layout_settings_dynamic_layout_title": "Dynamic layout",
"asset_action_delete_err_read_only": "Nem sikerült törölni a csak-olvasható elem(ek)et, így ezeket átugorjuk",
"asset_action_share_err_offline": "Nem sikerült betölteni az offline elem(ek)et, így ezeket kihagyjuk",
"asset_list_layout_settings_dynamic_layout_title": "Dinamikus elrendezés",
"asset_list_layout_settings_group_automatically": "Automatikus",
"asset_list_layout_settings_group_by": "Group assets by",
"asset_list_layout_settings_group_by_month": "Hónap",
"asset_list_layout_settings_group_by_month_day": "Hónap + nap",
"asset_list_settings_subtitle": "Photo grid layout settings",
"asset_list_settings_title": "Photo Grid",
"backup_album_selection_page_albums_device": "Az eszközön lévő albumok ({})",
"backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude",
"backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.",
"backup_album_selection_page_select_albums": "Albumok kiválasztása",
"backup_album_selection_page_selection_info": "Selection Info",
"asset_list_layout_settings_group_by": "Elemek csoportosítása",
"asset_list_layout_settings_group_by_month": "hónapok szerint",
"asset_list_layout_settings_group_by_month_day": "hónap és nap szerint",
"asset_list_settings_subtitle": "Fotórács elrendezése",
"asset_list_settings_title": "Fotórács",
"backup_album_selection_page_albums_device": "Ezen az eszközön lévő albumok ({})",
"backup_album_selection_page_albums_tap": "Koppincs a hozzáadáshoz, duplán koppincs az eltávolításhoz",
"backup_album_selection_page_assets_scatter": "Egy elem több albumban is lehet. Ezért a mentéshez albumokat lehet hozzáadni vagy azokat a mentésből kihagyni.",
"backup_album_selection_page_select_albums": "Válassz albumokat",
"backup_album_selection_page_selection_info": "Összegzés",
"backup_album_selection_page_total_assets": "Összes egyedi elem",
"backup_all": "Összes",
"backup_background_service_backup_failed_message": "HIba a mentés közben. Újrapróbálkozás...",
"backup_background_service_connection_failed_message": "HIba a szerverhez való csatlakozás közben. Újrapróbálkozás...",
"backup_background_service_current_upload_notification": "Feltöltés {}",
"backup_background_service_default_notification": "Keresés új elemek után...",
"backup_background_service_default_notification": "Új elemek keresése...",
"backup_background_service_error_title": "Hiba mentés közben",
"backup_background_service_in_progress_notification": "Elemek mentés alatt..",
"backup_background_service_upload_failure_notification": "Hiba feltöltés közben {}",
"backup_controller_page_albums": "Albumok mentése",
"backup_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_albums": "Albumok Mentése",
"backup_controller_page_background_app_refresh_disabled_content": "Engedélyezd a háttérben történő frissítést a Beállítások > Általános > Háttérben Frissítés menüpontban.",
"backup_controller_page_background_app_refresh_disabled_title": "Háttérben frissítés kikapcsolva",
"backup_controller_page_background_app_refresh_enable_button_text": "Beállítások megnyitása",
"backup_controller_page_background_battery_info_link": "Mutasd meg hogyan",
"backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.",
"backup_controller_page_background_battery_info_message": "A sikeres háttérben történő mentéshez kérjük, tiltsd le az Immich akkumulátor optimalizálását.\n\nMivel ezt a különféle eszközökön máshogy kell, ezért kérjük, az eszközöd gyártójától tudd meg, hogyan kell.",
"backup_controller_page_background_battery_info_ok": "OK",
"backup_controller_page_background_battery_info_title": "Akkumulátoroptimalizálás",
"backup_controller_page_background_battery_info_title": "Akkumulátor optimalizálás",
"backup_controller_page_background_charging": "Csak töltés közben",
"backup_controller_page_background_configure_error": "Failed to configure the background service",
"backup_controller_page_background_delay": "Delay new assets backup: {}",
"backup_controller_page_background_configure_error": "Nem sikerült beállítani a háttér szolgáltatást",
"backup_controller_page_background_delay": "Új elemek mentésének késleltetése: {}",
"backup_controller_page_background_description": "Kapcsold be a háttérfolyamatot, hogy automatikusan mentsen elemeket az applikáció megnyitása nélkül",
"backup_controller_page_background_is_off": "Automatikus mentés a háttérben ki van kapcsolva",
"backup_controller_page_background_is_on": "Automatikus mentés a háttérben bekapcsolva",
@ -78,63 +78,63 @@
"backup_controller_page_backup": "Mentés",
"backup_controller_page_backup_selected": "Kiválasztva:",
"backup_controller_page_backup_sub": "Mentett fotók és videók",
"backup_controller_page_cancel": "Megszakít",
"backup_controller_page_cancel": "Mégsem",
"backup_controller_page_created": "Létrehozva: {}",
"backup_controller_page_desc_backup": "Turn on foreground backup to automatically upload new assets to the server when opening the app.",
"backup_controller_page_desc_backup": "Ha engedélyezed az előtérben mentést, akkor az új elemek automatikusan feltöltődnek a szerverre, amikor megyitod az alkalmazást.",
"backup_controller_page_excluded": "Kivéve:",
"backup_controller_page_failed": "Sikertelen ({})",
"backup_controller_page_filename": "Fájlnév: {}[{}]",
"backup_controller_page_id": "Azonosító: {}",
"backup_controller_page_info": "Mentésinformációk",
"backup_controller_page_none_selected": "Egy sincs kiválasztva",
"backup_controller_page_remainder": "Maradék",
"backup_controller_page_remainder": "Hátralévő",
"backup_controller_page_remainder_sub": "Hátralévő fotók és videók a kijelöltek közül",
"backup_controller_page_select": "Kiválaszt",
"backup_controller_page_server_storage": "Szerver tárhely",
"backup_controller_page_start_backup": "Mentés elindítása",
"backup_controller_page_status_off": "Autoatikus mentés az előtérben kikapcsolva",
"backup_controller_page_status_on": "Autoatikus mentés az előtérben bekapcsolva",
"backup_controller_page_server_storage": "Szerver Tárhely",
"backup_controller_page_start_backup": "Mentés Elindítása",
"backup_controller_page_status_off": "Automatikus mentés az előtérben kikapcsolva",
"backup_controller_page_status_on": "Automatikus mentés az előtérben bekapcsolva",
"backup_controller_page_storage_format": "{} / {} felhasználva",
"backup_controller_page_to_backup": "Albumok amiket mentesz",
"backup_controller_page_total": "Összes",
"backup_controller_page_to_backup": "Mentésre kijelölt albumok",
"backup_controller_page_total": "Összesen",
"backup_controller_page_total_sub": "Minden egyedi fotó és videó a kijelölt albumokból",
"backup_controller_page_turn_off": "Turn off foreground backup",
"backup_controller_page_turn_on": "Turn on foreground backup",
"backup_controller_page_uploading_file_info": "Uploading file info",
"backup_controller_page_turn_off": "Előtérben mentés kikapcsolása",
"backup_controller_page_turn_on": "Előtérben mentés bekapcsolása",
"backup_controller_page_uploading_file_info": "Fájl információk feltöltése",
"backup_err_only_album": "Az utolsó albumot nem tudod törölni",
"backup_info_card_assets": "elemek",
"backup_manual_cancelled": "Megszakítva",
"backup_manual_failed": "Failed",
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
"backup_manual_failed": "Sikertelen",
"backup_manual_in_progress": "Feltöltés már folyamatban. Próbáld meg később",
"backup_manual_success": "Sikeres",
"backup_manual_title": "Upload status",
"backup_manual_title": "Feltöltés állapota",
"cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
"cache_settings_clear_cache_button": "Gyorsítótár törlése",
"cache_settings_clear_cache_button": "Gyorsítótár kiürítése",
"cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
"cache_settings_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": "Image cache size ({} assets)",
"cache_settings_statistics_album": "Library thumbnails",
"cache_settings_statistics_assets": "{} assets ({})",
"cache_settings_statistics_full": "Teljes képek",
"cache_settings_statistics_shared": "Shared album thumbnails",
"cache_settings_statistics_thumbnail": "Előnézeti képek",
"cache_settings_statistics_title": "Gyorsítótár által használt terület",
"cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application",
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
"cache_settings_tile_subtitle": "Control the local storage behaviour",
"cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Gyorsítótár beállítások",
"cache_settings_duplicated_assets_clear_button": "KIÜRÍT",
"cache_settings_duplicated_assets_subtitle": "Fotók és videók, amiket az alkalmazás fekete listára tett",
"cache_settings_duplicated_assets_title": "Duplikált Elemek ({})",
"cache_settings_image_cache_size": "Kép gyorsítótár mérete ({} elem)",
"cache_settings_statistics_album": "Mappa bélyegképei",
"cache_settings_statistics_assets": "{} elem ({})",
"cache_settings_statistics_full": "Teljes méretű képek",
"cache_settings_statistics_shared": "Megosztott album bélyegképei",
"cache_settings_statistics_thumbnail": "Bélyegképek",
"cache_settings_statistics_title": "Gyorsítótár használata",
"cache_settings_subtitle": "Az Immich mobilalkalmazás gyorsítótár viselkedésének beállítása",
"cache_settings_thumbnail_size": "Bélyegkép gyorsítótár mérete ({} elem)",
"cache_settings_tile_subtitle": "Helyi tárhely viselkedésének beállítása",
"cache_settings_tile_title": "Helyi Tárhely",
"cache_settings_title": "Gyorsítótár Beállítások",
"change_password_form_confirm_password": "Jelszó Megerősítése",
"change_password_form_description": "Kedves {name}!\n\nMost jelentkezel be először a rendszerbe vagy más okból szükséfes a jelszavad meváltoztatása. Kérjük, add meg új jelszavad.",
"change_password_form_description": "Kedves {name}!\n\nMost jelentkezel be először a rendszerbe vagy más okból szükséges a jelszavad meváltoztatása. Kérjük, add meg új jelszavad.",
"change_password_form_new_password": "Új Jelszó",
"change_password_form_password_mismatch": "A két beírt jelszó nem egyezik",
"change_password_form_reenter_new_password": "Jelszó (még egyszer)",
"common_add_to_album": "Albumhoz ad",
"common_change_password": "Jelszócsere",
"common_create_new_album": "Új album létrehozása",
"common_server_error": "Kérjük, ellenőrizd a hálózati kapcsolatot, gondoskodj róla, hogy a szerver elérhető legyen, valamint az app és a szerver kompatibilis verziójú legyen.",
"common_server_error": "Kérjük, ellenőrizd a hálózati kapcsolatot, gondoskodj róla, hogy a szerver elérhető legyen, valamint az alkalmazás és a szerver kompatibilis verziójú legyen.",
"common_shared": "Megosztva",
"control_bottom_app_bar_add_to_album": "Hozzáadás az albumhoz",
"control_bottom_app_bar_album_info": "{} elem",
@ -142,23 +142,23 @@
"control_bottom_app_bar_archive": "Archivál",
"control_bottom_app_bar_create_new_album": "Album létrehozása",
"control_bottom_app_bar_delete": "Törlés",
"control_bottom_app_bar_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_delete_from_immich": "Törlés az Immich-ből",
"control_bottom_app_bar_delete_from_local": "Törlés az eszközről",
"control_bottom_app_bar_edit_location": "Hely Módosítása",
"control_bottom_app_bar_edit_time": "Dátum és Idő Módosítása",
"control_bottom_app_bar_favorite": "Kedvenc",
"control_bottom_app_bar_share": "Megosztás",
"control_bottom_app_bar_share_to": "Share To",
"control_bottom_app_bar_share_to": "Megosztás Ide",
"control_bottom_app_bar_stack": "Stack",
"control_bottom_app_bar_trash_from_immich": "Move to Trash",
"control_bottom_app_bar_unarchive": "Archiválás megszüntetése",
"control_bottom_app_bar_unfavorite": "Unfavorite",
"control_bottom_app_bar_upload": "Upload",
"control_bottom_app_bar_trash_from_immich": "Lomtárba Helyez",
"control_bottom_app_bar_unarchive": "Nem Archivált",
"control_bottom_app_bar_unfavorite": "Nem Kedvenc",
"control_bottom_app_bar_upload": "Feltöltés",
"create_album_page_untitled": "Névtelen",
"create_shared_album_page_create": "Létrehoz",
"create_shared_album_page_share": "Megosztás",
"create_shared_album_page_share_add_assets": "ELEMEK HOZZÁADÁSA",
"create_shared_album_page_share_select_photos": "Fotók kiválasztása",
"create_shared_album_page_share_select_photos": "Fotók választása",
"curated_location_page_title": "Helyek",
"curated_object_page_title": "Dolgok",
"daily_title_text_date": "E, MMM dd",
@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "RÉSZLETEK",
"exif_bottom_sheet_location": "HELYSZÍN",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Fejlesztés alatt",
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Csak saját felelősségre használd",
@ -230,10 +231,10 @@
"login_disabled": "A bejelentkezés letiltva",
"login_form_api_exception": "API hiba. Kérljük, ellenőrid a szerver címét, majd próbáld újra.",
"login_form_back_button_text": "Back",
"login_form_button_text": "Belépés",
"login_form_email_hint": "teemailed@email.com",
"login_form_button_text": "Bejelentkezés",
"login_form_email_hint": "email@cimed.hu",
"login_form_endpoint_hint": "http://szerver-címe:port/api",
"login_form_endpoint_url": "Kiszolgáló végpont címe",
"login_form_endpoint_url": "Szerver címe",
"login_form_err_http": "Kérem, adjon meg egy http:// vagy https:// címet",
"login_form_err_invalid_email": "Érvénytelen email cím",
"login_form_err_invalid_url": "Érvénytelen cím",
@ -346,7 +347,7 @@
"search_suggestion_list_smart_search_hint_1": "Az intelligens keresés alapértelmezetten be van kapcsolva, metaadatokat így kereshetsz",
"search_suggestion_list_smart_search_hint_2": "m:keresési-kifejezés",
"select_additional_user_for_sharing_page_suggestions": "Javaslatok",
"select_user_for_sharing_page_err_album": "Hiba az album létrehozása közben",
"select_user_for_sharing_page_err_album": "Nem sikerült létrehozni az albumot",
"select_user_for_sharing_page_share_suggestions": "Javaslatok",
"server_info_box_app_version": "Alkalmazás Verzió",
"server_info_box_latest_release": "Latest Version",
@ -373,7 +374,7 @@
"settings_require_restart": "Kérlek indítsd újra az Immich-et hogy alkalmazd ezt a beállítást",
"share_add": "Hozzáadás",
"share_add_photos": "Fotók hozzáadása",
"share_add_title": "Cím hozzáadása",
"share_add_title": "Album neve",
"share_create_album": "Album létrehozása",
"shared_album_activities_input_disable": "Comment is disabled",
"shared_album_activities_input_hint": "Say something",
@ -431,11 +432,11 @@
"share_done": "Done",
"share_invite": "Meghívás az albumba",
"sharing_page_album": "Megosztott albumok",
"sharing_page_description": "Hozzon létre megosztott albumokat, hogy megoszthasson fényképeket és videókat a hálózatában lévő emberekkel.",
"sharing_page_description": "Megosztott albumok létrehozásával fényképeket és videókatoszthatsz meg a hálózatodban lévő emberekkel.",
"sharing_page_empty_list": "ÜRES LISTA",
"sharing_silver_appbar_create_shared_album": "Megosztott album létrehozása",
"sharing_silver_appbar_shared_links": "Shared links",
"sharing_silver_appbar_share_partner": "Megosztás másokkal",
"sharing_silver_appbar_share_partner": "Megosztás partnerrel",
"tab_controller_nav_library": "Könyvtár",
"tab_controller_nav_photos": "Képek",
"tab_controller_nav_search": "Keresés",

View File

@ -1,13 +1,13 @@
{
"action_common_cancel": "Cancel",
"action_common_update": "Update",
"action_common_cancel": "Annulla",
"action_common_update": "Aggiorna",
"add_to_album_bottom_sheet_added": "Aggiunto in {album}",
"add_to_album_bottom_sheet_already_exists": "Già presente in {album}",
"advanced_settings_log_level_title": "Log level: {}",
"advanced_settings_log_level_title": "Livello log: {}",
"advanced_settings_prefer_remote_subtitle": "Alcuni dispositivi sono molto lenti a caricare le anteprime delle immagini dal dispositivo. Attivare questa impostazione per caricare invece le immagini remote.",
"advanced_settings_prefer_remote_title": "Preferisci immagini remote.",
"advanced_settings_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_self_signed_ssl_subtitle": "Salta la verifica dei certificati SSL del server. Richiesto con l'uso di certificati self-signed.",
"advanced_settings_self_signed_ssl_title": "Consenti certificati SSL self-signed",
"advanced_settings_tile_subtitle": "Impostazioni aggiuntive utenti",
"advanced_settings_tile_title": "Avanzato",
"advanced_settings_troubleshooting_subtitle": "Attiva funzioni addizionali per la risoluzione dei problemi",
@ -35,7 +35,7 @@
"app_bar_signout_dialog_title": "Disconnetti",
"archive_page_no_archived_assets": "Nessuna oggetto archiviato",
"archive_page_title": "Archivia ({})",
"asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping",
"asset_action_delete_err_read_only": "Non posso eliminare degli elementi in sola lettura, ignorato",
"asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping",
"asset_list_layout_settings_dynamic_layout_title": "Layout dinamico",
"asset_list_layout_settings_group_automatically": "Automatico",
@ -111,9 +111,9 @@
"cache_settings_album_thumbnails": "Anteprime pagine librerie ({} assets)",
"cache_settings_clear_cache_button": "Cancella cache",
"cache_settings_clear_cache_button_title": "Cancella la cache dell'app. Questo impatterà significativamente le prestazioni dell''app fino a quando la cache non sarà rigenerata.",
"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_duplicated_assets_clear_button": "ELIMINA",
"cache_settings_duplicated_assets_subtitle": "Foto e video che sono nella black list dell'applicazione",
"cache_settings_duplicated_assets_title": "Elementi duplicati ({})",
"cache_settings_image_cache_size": "Dimensione cache delle foto ({} assets)",
"cache_settings_statistics_album": "Anteprime librerie",
"cache_settings_statistics_assets": "{} contenuti ({})",
@ -142,15 +142,15 @@
"control_bottom_app_bar_archive": "Archivia",
"control_bottom_app_bar_create_new_album": "Crea nuovo album",
"control_bottom_app_bar_delete": "Elimina",
"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_delete_from_immich": "Elimina da Immich",
"control_bottom_app_bar_delete_from_local": "Elimina dal dispositivo",
"control_bottom_app_bar_edit_location": "Modifica posizione",
"control_bottom_app_bar_edit_time": "Modifica data e ora",
"control_bottom_app_bar_favorite": "Preferiti",
"control_bottom_app_bar_share": "Condividi",
"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_trash_from_immich": "Sposta nel cestino",
"control_bottom_app_bar_unarchive": "Rimuovi dagli archivi",
"control_bottom_app_bar_unfavorite": "Unfavorite",
"control_bottom_app_bar_upload": "Upload",
@ -165,26 +165,27 @@
"daily_title_text_date_year": "E, dd MMM, yyyy",
"date_format": "E, d LLL, y • hh:mm",
"delete_dialog_alert": "Questi oggetti saranno cancellati definitivamente da Immich e dal tuo device",
"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_alert_local": "Questi elementi verranno eliminati definitivamente dal dispositivo, ma saranno ancora disponibili sul server Immich",
"delete_dialog_alert_local_non_backed_up": "Alcuni degli elementi non sono stati caricati su Immich e saranno rimossi definitivamente dal tuo dispositivo",
"delete_dialog_alert_remote": "Questi elementi verranno eliminati permanentemente dal server Immich",
"delete_dialog_cancel": "Annulla",
"delete_dialog_ok": "Elimina",
"delete_dialog_ok_force": "Delete Anyway",
"delete_dialog_ok_force": "Elimina comunque",
"delete_dialog_title": "Cancella definitivamente",
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only",
"delete_local_dialog_ok_force": "Delete Anyway",
"delete_local_dialog_ok_backed_up_only": "Elimina solo quelli con backup",
"delete_local_dialog_ok_force": "Elimina comunque",
"delete_shared_link_dialog_content": "Sei sicuro di voler eliminare questo link condiviso?",
"delete_shared_link_dialog_title": "Elimina link condiviso",
"description_input_hint_text": "Aggiungi descrizione...",
"description_input_submit_error": "Errore modificare descrizione, controlli I log per maggiori dettagli",
"edit_date_time_dialog_date_time": "Date and Time",
"edit_date_time_dialog_timezone": "Timezone",
"edit_location_dialog_title": "Location",
"edit_date_time_dialog_date_time": "Data e ora",
"edit_date_time_dialog_timezone": "Fuso orario",
"edit_location_dialog_title": "Posizione",
"exif_bottom_sheet_description": "Aggiungi una descrizione...",
"exif_bottom_sheet_details": "DETTAGLI",
"exif_bottom_sheet_location": "POSIZIONE",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_location_add": "Aggiungi una posizione",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Work in progress",
"experimental_settings_new_asset_list_title": "Attiva griglia di foto sperimentale",
"experimental_settings_subtitle": "Usalo a tuo rischio!",
@ -199,7 +200,7 @@
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
"home_page_building_timeline": "Costruendo il Timeline",
"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_delete_remote_err_local": "Immagini sul disco locale presenti pure nella selezione degli elementi remoti, skippando",
"home_page_favorite_err_local": "Non puoi aggiungere tra i preferiti le foto ancora non caricate",
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
"home_page_first_time_notice": "Se è la prima volta che usi l'app, assicurati di scegliere gli album per avere il Timeline con immagini e video",
@ -214,22 +215,22 @@
"library_page_favorites": "Preferiti",
"library_page_new_album": "Nuovo Album",
"library_page_sharing": "Condividendo",
"library_page_sort_asset_count": "Number of assets",
"library_page_sort_asset_count": "Numero di elementi",
"library_page_sort_created": "Creato il più recente",
"library_page_sort_last_modified": "Ultima modifica",
"library_page_sort_most_oldest_photo": "Oldest photo",
"library_page_sort_most_oldest_photo": "Foto più vecchia",
"library_page_sort_most_recent_photo": "Più recente",
"library_page_sort_title": "Titolo album",
"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",
"location_picker_choose_on_map": "Scegli una mappa",
"location_picker_latitude": "Latitudine",
"location_picker_latitude_error": "Inserisci una latitudine valida",
"location_picker_latitude_hint": "Inserisci la tua latitudine qui",
"location_picker_longitude": "Longitudine",
"location_picker_longitude_error": "Inserisci una longitudine valida",
"location_picker_longitude_hint": "Inserisci la longitudine qui",
"login_disabled": "L'accesso è stato disattivato",
"login_form_api_exception": "API error, per favore ricontrolli URL del server e riprovi",
"login_form_back_button_text": "Back",
"login_form_back_button_text": "Indietro",
"login_form_button_text": "Login",
"login_form_email_hint": "tuaemail@email.com",
"login_form_endpoint_hint": "http://ip-del-tuo-server:port/api",
@ -252,35 +253,35 @@
"login_form_server_error": "Non è possibile connettersi al server",
"login_password_changed_error": "C'è stato un errore durante l'aggiornamento della password",
"login_password_changed_success": "Password aggiornata con successo",
"map_assets_in_bound": "{} photo",
"map_assets_in_bounds": "{} photos",
"map_assets_in_bound": "{} foto",
"map_assets_in_bounds": "{} foto",
"map_cannot_get_user_location": "Cannot get user's location",
"map_location_dialog_cancel": "Annulla",
"map_location_dialog_yes": "Si",
"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_picker_page_use_location": "Usa questa posizione",
"map_location_service_disabled_content": "I servizi di geolocalizzazione devono essere attivati per visualizzare gli elementi per la tua posizione attuale. Vuoi attivarli adesso?",
"map_location_service_disabled_title": "Location Service disabled",
"map_no_assets_in_bounds": "Nessuna foto in questa zona",
"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_content": "L'accesso alla posizione è necessario per visualizzare gli elementi per la tua posizione attuale. Vuoi consentirlo adesso?",
"map_no_location_permission_title": "Location Permission denied",
"map_settings_dark_mode": "Modalità scura",
"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_date_range_option_day": "Ultime 24 ore",
"map_settings_date_range_option_days": "Ultimi {} giorni",
"map_settings_date_range_option_year": "Ultimo anno",
"map_settings_date_range_option_years": "Ultimi {} anni",
"map_settings_dialog_cancel": "Cancel",
"map_settings_dialog_save": "Salva",
"map_settings_dialog_title": "Map Settings",
"map_settings_include_show_archived": "Include Archived",
"map_settings_only_relative_range": "Date range",
"map_settings_only_show_favorites": "Show Favorite Only",
"map_settings_theme_settings": "Map Theme",
"map_settings_theme_settings": "Tema della mappa",
"map_zoom_to_see_photos": "Zoom out to see photos",
"monthly_title_text_date_format": "MMMM y",
"motion_photos_page_title": "Motion Foto",
"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",
"multiselect_grid_edit_date_time_err_read_only": "Non posso modificare la data degli elementi in sola lettura, ignorato",
"multiselect_grid_edit_gps_err_read_only": "Non posso modificare la posizione degli elementi in sola lettura, ignorato",
"notification_permission_dialog_cancel": "Annulla",
"notification_permission_dialog_content": "Per attivare le notifiche, vai alle Impostazioni e seleziona concedi",
"notification_permission_dialog_settings": "Impostazioni",
@ -307,18 +308,18 @@
"permission_onboarding_permission_limited": "Permessi limitati. Perché Immich possa controllare e fare i backup di tutte le foto, concedere i permessi all'intera galleria dalle impostazioni ",
"permission_onboarding_request": "Immich richiede i permessi per vedere le tue foto e video",
"profile_drawer_app_logs": "Logs",
"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_out_of_date_major": "L'applicazione non è aggiornata. Per favore aggiorna all'ultima versione principale.",
"profile_drawer_client_out_of_date_minor": "L'applicazione non è aggiornata. Per favore aggiorna all'ultima versione minore.",
"profile_drawer_client_server_up_to_date": "Client e server sono aggiornati",
"profile_drawer_documentation": "Documentazione",
"profile_drawer_github": "GitHub",
"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_server_out_of_date_major": "Il server non è aggiornato. Per favore aggiorna all'ultima versione principale.",
"profile_drawer_server_out_of_date_minor": "Il server non è aggiornato. Per favore aggiorna all'ultima versione minore.",
"profile_drawer_settings": "Impostazioni ",
"profile_drawer_sign_out": "Logout",
"profile_drawer_trash": "Trash",
"recently_added_page_title": "Aggiunti di recente",
"scaffold_body_error_occurred": "Error occurred",
"scaffold_body_error_occurred": "Si è verificato un errore.",
"search_bar_hint": "Cerca le tue foto",
"search_page_categories": "Categoria",
"search_page_favorites": "Preferiti",
@ -326,13 +327,13 @@
"search_page_no_objects": "Nessuna informazione relativa all'oggetto disponibile",
"search_page_no_places": "Nessun informazione sul luogo disponibile",
"search_page_people": "Persone",
"search_page_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_dialog_cancel": "Annulla",
"search_page_person_add_name_dialog_hint": "Nome",
"search_page_person_add_name_dialog_save": "Salva",
"search_page_person_add_name_dialog_title": "Aggiungi un nome",
"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_person_add_name_title": "Aggiungi un nome",
"search_page_person_edit_name": "Modifica nome",
"search_page_places": "Luoghi",
"search_page_recently_added": "Aggiunte di recente",
"search_page_screenshots": "Screenshot",
@ -341,7 +342,7 @@
"search_page_videos": "Video",
"search_page_view_all_button": "Guarda tutto",
"search_page_your_activity": "Tua attività ",
"search_page_your_map": "Your Map",
"search_page_your_map": "La tua mappa",
"search_result_page_new_search_hint": "Nuova ricerca ",
"search_suggestion_list_smart_search_hint_1": "\nRicerca Smart è attiva di default, per usare la ricerca con i metadata usare la seguente sintassi",
"search_suggestion_list_smart_search_hint_2": "m:your-search-term",
@ -381,17 +382,17 @@
"shared_album_activity_remove_title": "Elimina attività",
"shared_album_activity_setting_subtitle": "Let others respond",
"shared_album_activity_setting_title": "Commenti e Mi piace",
"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",
"shared_album_section_people_action_error": "Errore durante la rimozione/uscita dell'album",
"shared_album_section_people_action_leave": "Rimuovi utente dall'album",
"shared_album_section_people_action_remove_user": "Rimuovi utente dall'album",
"shared_album_section_people_owner_label": "Proprietario",
"shared_album_section_people_title": "PERSONE",
"share_dialog_preparing": "Preparo…",
"shared_link_app_bar_title": "Link condivisi",
"shared_link_clipboard_copied_massage": "Copied to clipboard",
"shared_link_clipboard_copied_massage": "Copiato negli appunti",
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
"shared_link_create_app_bar_title": "Crea link di condivisione",
"shared_link_create_error": "Error while creating shared link",
"shared_link_create_error": "Si è verificato un errore durante la creazione del link condiviso",
"shared_link_create_info": "Consenti a chiunque abbia il link di vedere le foto selezionate",
"shared_link_create_submit_button": "Crea link di condivisione",
"shared_link_edit_allow_download": "Consenti ad utenti pubblici di scaricare i contenuti",
@ -401,32 +402,32 @@
"shared_link_edit_description": "Descrizione",
"shared_link_edit_description_hint": "Inserisci la descrizione della condivisione",
"shared_link_edit_expire_after": "Scade dopo",
"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_never": "Never",
"shared_link_edit_expire_after_option_day": "1 giorno",
"shared_link_edit_expire_after_option_days": "{} giorni",
"shared_link_edit_expire_after_option_hour": "1 ora",
"shared_link_edit_expire_after_option_hours": "{} ore",
"shared_link_edit_expire_after_option_minute": "1 minuto",
"shared_link_edit_expire_after_option_minutes": "{} minuti",
"shared_link_edit_expire_after_option_never": "Mai",
"shared_link_edit_password": "Password",
"shared_link_edit_password_hint": "Inserire la password di condivisione",
"shared_link_edit_show_meta": "Visualizza metadati",
"shared_link_edit_submit_button": "Aggiorna link",
"shared_link_empty": "Non hai alcun link condiviso",
"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_info_chip_download": "Download",
"shared_link_expired": "Scaduto",
"shared_link_expires_day": "Scade tra {} giorno",
"shared_link_expires_days": "Scade tra {} giorni",
"shared_link_expires_hour": "Scade tra {} ora",
"shared_link_expires_hours": "Scade tra {} ore",
"shared_link_expires_minute": "Scade tra {} minuto",
"shared_link_expires_minutes": "Scade tra {} minuti",
"shared_link_expires_never": "Scadenza ∞",
"shared_link_expires_second": "Scade tra {} secondo",
"shared_link_expires_seconds": "Scade tra {} secondi",
"shared_link_info_chip_download": "Scarica",
"shared_link_info_chip_metadata": "EXIF",
"shared_link_info_chip_upload": "Upload",
"shared_link_info_chip_upload": "Carica",
"shared_link_manage_links": "Gestisci link condivisi",
"share_done": "Done",
"share_invite": "Invita nell'album ",
@ -454,7 +455,7 @@
"trash_page_delete": "Elimina",
"trash_page_delete_all": "Elimina tutti",
"trash_page_empty_trash_btn": "Svuota cestino",
"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_content": "Vuoi eliminare gli elementi nel cestino? Questi elementi saranno eliminati definitivamente da Immich",
"trash_page_empty_trash_dialog_ok": "Ok",
"trash_page_info": "Gli elementi cestinati saranno eliminati definitivamente dopo {} giorni",
"trash_page_no_assets": "Nessun elemento cestinato",

View File

@ -1,13 +1,13 @@
{
"action_common_cancel": "Cancel",
"action_common_update": "Update",
"action_common_cancel": "キャンセル",
"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_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_log_level_title": "ログレベル: {}",
"advanced_settings_prefer_remote_subtitle": "端末によっては端末上に存在するサムネイルのロードに非常に時間がかかります。このオプションをに有効にする事によってサーバーから直接画像をロードすることが可能です",
"advanced_settings_prefer_remote_title": "リモートを優先する",
"advanced_settings_self_signed_ssl_subtitle": "SSLのチェックをスキップする。Self-signedな署名で必要です",
"advanced_settings_self_signed_ssl_title": "Self-signed署名を許可する",
"advanced_settings_tile_subtitle": "追加ユーザー設定",
"advanced_settings_tile_title": "詳細設定",
"advanced_settings_troubleshooting_subtitle": "トラブルシューティング用の詳細設定をオンにする",
@ -26,17 +26,17 @@
"album_viewer_appbar_share_err_title": "タイトル変更の失敗",
"album_viewer_appbar_share_leave": "アルバムから脱退",
"album_viewer_appbar_share_remove": "アルバムから削除",
"album_viewer_appbar_share_to": "Share To",
"album_viewer_appbar_share_to": "次の方々と共有します",
"album_viewer_page_share_add_users": "ユーザーを追加",
"all_people_page_title": "People",
"all_people_page_title": "ピープル",
"all_videos_page_title": "ビデオ",
"app_bar_signout_dialog_content": " サインアウトしますか?",
"app_bar_signout_dialog_ok": "はい",
"app_bar_signout_dialog_title": " サインアウト",
"archive_page_no_archived_assets": "アーカイブ済みの写真またはビデオがありません",
"archive_page_title": "アーカイブ({})",
"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_action_delete_err_read_only": "読み取り専用の項目は削除できません。スキップします",
"asset_action_share_err_offline": "オフラインの項目をゲットできません。スキップします",
"asset_list_layout_settings_dynamic_layout_title": "ダイナミックレイアウト",
"asset_list_layout_settings_group_automatically": "自動",
"asset_list_layout_settings_group_by": "写真のグループ分け",
@ -103,17 +103,17 @@
"backup_controller_page_uploading_file_info": "アップロード中のファイル",
"backup_err_only_album": "最低1つのアルバムを選択してください",
"backup_info_card_assets": "写真と動画",
"backup_manual_cancelled": "Cancelled",
"backup_manual_failed": "Failed",
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
"backup_manual_success": "Success",
"backup_manual_title": "Upload status",
"backup_manual_cancelled": "キャンセルされました",
"backup_manual_failed": "失敗",
"backup_manual_in_progress": "アップロードが進行中です。後でもう一度試してください",
"backup_manual_success": "成功",
"backup_manual_title": "アップロード状況",
"cache_settings_album_thumbnails": "ライブラリのサムネイル ({}枚)",
"cache_settings_clear_cache_button": "キャッシュをクリア",
"cache_settings_clear_cache_button_title": "キャッシュを削除(キャッシュ再生成までアプリのパフォーマンスが著しく低下)",
"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_duplicated_assets_clear_button": "クリア",
"cache_settings_duplicated_assets_subtitle": "アプリがブラックリストに追加している項目",
"cache_settings_duplicated_assets_title": "{}項目が重複",
"cache_settings_image_cache_size": "キャッシュのサイズ ({}枚) ",
"cache_settings_statistics_album": "ライブラリのサムネイル",
"cache_settings_statistics_assets": "{} 枚 ({}枚中)",
@ -123,8 +123,8 @@
"cache_settings_statistics_title": "キャッシュ",
"cache_settings_subtitle": "キャッシュの動作を変更する",
"cache_settings_thumbnail_size": "サムネイルのキャッシュのサイズ ({}枚)",
"cache_settings_tile_subtitle": "Control the local storage behaviour",
"cache_settings_tile_title": "Local Storage",
"cache_settings_tile_subtitle": "ローカルストレージの挙動を確認する",
"cache_settings_tile_title": "ローカルストレージ",
"cache_settings_title": "キャッシュの設定",
"change_password_form_confirm_password": "確定",
"change_password_form_description": "{name}さん こんにちは\n\nサーバーにアクセスするのが初めてか、パスワードリセットのリクエストがされました。新しいパスワードを入力してください",
@ -142,18 +142,18 @@
"control_bottom_app_bar_archive": "アーカイブ",
"control_bottom_app_bar_create_new_album": "アルバムを作成",
"control_bottom_app_bar_delete": "削除",
"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_delete_from_immich": "Immichから削除",
"control_bottom_app_bar_delete_from_local": "端末から削除",
"control_bottom_app_bar_edit_location": "位置情報を編集",
"control_bottom_app_bar_edit_time": "日時を変更",
"control_bottom_app_bar_favorite": "お気に入り",
"control_bottom_app_bar_share": "共有",
"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_share_to": "次のユーザーに共有: ",
"control_bottom_app_bar_stack": "スタック",
"control_bottom_app_bar_trash_from_immich": "ゴミ箱に捨てる",
"control_bottom_app_bar_unarchive": "アーカイブを解除",
"control_bottom_app_bar_unfavorite": "Unfavorite",
"control_bottom_app_bar_upload": "Upload",
"control_bottom_app_bar_unfavorite": "お気に入りから外す",
"control_bottom_app_bar_upload": "アップロード",
"create_album_page_untitled": "タイトルなし",
"create_shared_album_page_create": "作成",
"create_shared_album_page_share": "共有",
@ -165,26 +165,27 @@
"daily_title_text_date_year": "yyyy年 MM月 DD日, EE",
"date_format": "MM月 DD日, EE • hh時mm分",
"delete_dialog_alert": "サーバーとデバイスの両方から永久的に削除されます!",
"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_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": "Delete Anyway",
"delete_dialog_ok_force": "削除します",
"delete_dialog_title": "永久的に削除",
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only",
"delete_local_dialog_ok_force": "Delete Anyway",
"delete_local_dialog_ok_backed_up_only": "バックアップ済みのみを削除",
"delete_local_dialog_ok_force": "削除します",
"delete_shared_link_dialog_content": "本当にこの共有リンクを消しますか?",
"delete_shared_link_dialog_title": "共有リンクを消す",
"description_input_hint_text": "説明を追加",
"description_input_submit_error": "説明の編集に失敗、詳細の確認はログで行ってください",
"edit_date_time_dialog_date_time": "Date and Time",
"edit_date_time_dialog_timezone": "Timezone",
"edit_location_dialog_title": "Location",
"edit_date_time_dialog_date_time": "日付と時間",
"edit_date_time_dialog_timezone": "タイムゾーン",
"edit_location_dialog_title": "位置情報",
"exif_bottom_sheet_description": "説明を追加",
"exif_bottom_sheet_details": "詳細",
"exif_bottom_sheet_location": "撮影場所",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_location_add": "位置情報を追加",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "製作途中(WIP)",
"experimental_settings_new_asset_list_title": "試験的なグリッドを有効化",
"experimental_settings_subtitle": "試験的機能につき自己責任で!",
@ -194,42 +195,42 @@
"home_page_add_to_album_conflicts": "{album}に{added}枚写真を追加しました。追加済みの{failed}枚はスキップしました。",
"home_page_add_to_album_err_local": "まだアップロードされてない項目はアルバムに登録できません",
"home_page_add_to_album_success": "{album}に{added}枚写真を追加しました",
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
"home_page_album_err_partner": "まだパートナーの写真はアルバムに追加できません。スキップします(アップデート待ってね)",
"home_page_archive_err_local": "まだアップロードされてない項目はアーカイブできません",
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
"home_page_archive_err_partner": "パートナーの写真はアーカイブできません。スキップします",
"home_page_building_timeline": "タイムライン構築中",
"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_delete_err_partner": "パートナーの写真は削除できません。スキップします",
"home_page_delete_remote_err_local": "リモート削除の選択にローカルなアイテムが含まれています。スキップします",
"home_page_favorite_err_local": "まだアップロードされてない項目はお気に入り登録できません",
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
"home_page_favorite_err_partner": "まだパートナーの写真をお気に入り登録できません。スキップします(アップデート待ってね)",
"home_page_first_time_notice": "はじめてアプリを使う場合、タイムラインに写真を表示するためにアルバムを選択してください",
"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",
"home_page_share_err_local": "ローカルのみの項目をリンクで共有はできません。スキップします",
"home_page_upload_err_limit": "一回でアップロードできる写真の数は30枚です。スキップします",
"image_viewer_page_state_provider_download_error": "ダウンロード失敗",
"image_viewer_page_state_provider_download_success": "ダウンロード成功",
"image_viewer_page_state_provider_share_error": "Share Error",
"image_viewer_page_state_provider_share_error": "共有エラー",
"library_page_albums": "アルバム",
"library_page_archive": "アーカイブ",
"library_page_device_albums": "デバイス上のアルバム",
"library_page_favorites": "お気に入り",
"library_page_new_album": "新しいアルバム",
"library_page_sharing": "共有中",
"library_page_sort_asset_count": "Number of assets",
"library_page_sort_asset_count": "項目の数",
"library_page_sort_created": "作成日時",
"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_last_modified": "最終変更",
"library_page_sort_most_oldest_photo": "一番古い項目",
"library_page_sort_most_recent_photo": "最近の項目",
"library_page_sort_title": "アルバム名",
"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",
"location_picker_choose_on_map": "マップを選択",
"location_picker_latitude": "緯度",
"location_picker_latitude_error": "有効な緯度を入力してください",
"location_picker_latitude_hint": "緯度をここに入力",
"location_picker_longitude": "経度",
"location_picker_longitude_error": "有効な経度を入力してください",
"location_picker_longitude_hint": "経度をここに入力",
"login_disabled": "ログインは無効化されました",
"login_form_api_exception": "APIエラー。URLをチェックしてもう一度試してください",
"login_form_back_button_text": "Back",
"login_form_back_button_text": "戻る",
"login_form_button_text": "ログイン",
"login_form_email_hint": "hoge@email.com",
"login_form_endpoint_hint": "https://example.com:port/api",
@ -242,7 +243,7 @@
"login_form_failed_get_oauth_server_config": "OAuthログインに失敗しました。サーバーのURLを確認してください。",
"login_form_failed_get_oauth_server_disable": "このサーバーではOAuthが使えません",
"login_form_failed_login": "ログインエラー。サーバーのURL・メールアドレス・パスワードを再確認してください。",
"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_handshake_exception": "Handshake Exceptionエラー。self-signed署名を設定で有効にしてください",
"login_form_label_email": "メールアドレス",
"login_form_label_password": "パスワード",
"login_form_next_button": "次",
@ -250,53 +251,53 @@
"login_form_save_login": "ログインを保持",
"login_form_server_empty": "URLを入力",
"login_form_server_error": "サーバーに接続できません",
"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",
"login_password_changed_error": "パスワードの変更でエラーが発生しました",
"login_password_changed_success": "パスワードの変更に成功",
"map_assets_in_bound": "{}項目",
"map_assets_in_bounds": "{}項目",
"map_cannot_get_user_location": "位置情報がゲットできません",
"map_location_dialog_cancel": "キャンセル",
"map_location_dialog_yes": "はい",
"map_location_picker_page_use_location": "この位置情報を使う",
"map_location_service_disabled_content": "現在地の項目を表示するには位置情報がオンである必要があります。有効化しますか?",
"map_location_service_disabled_title": "位置情報がオフです",
"map_no_assets_in_bounds": "このエリアに写真はありません",
"map_no_location_permission_content": "現在地の項目を表示するには位置情報へのアクセスが必要です。許可しますか?",
"map_no_location_permission_title": "位置情報へのアクセスが拒否されました",
"map_settings_dark_mode": "ダークモード",
"map_settings_date_range_option_all": "全て",
"map_settings_date_range_option_day": "過去24時間",
"map_settings_date_range_option_days": "過去{}日間",
"map_settings_date_range_option_year": "過去1年",
"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_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",
"map_settings_only_show_favorites": "お気に入りのみを表示",
"map_settings_theme_settings": "マップの見た目",
"map_zoom_to_see_photos": "写真を見るにはズームアウト",
"monthly_title_text_date_format": "yyyy年 MM月",
"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": "Cannot edit location of read only asset(s), skipping",
"multiselect_grid_edit_date_time_err_read_only": "読み取り専用の項目の日付を変更できません",
"multiselect_grid_edit_gps_err_read_only": "読み取り専用の項目の位置情報を変更できません",
"notification_permission_dialog_cancel": "キャンセル",
"notification_permission_dialog_content": "通知を許可するには設定を開いてオンにしてください",
"notification_permission_dialog_settings": "設定",
"notification_permission_list_tile_content": "通知の許可 をオンにしてください",
"notification_permission_list_tile_enable_button": "通知をオンにする",
"notification_permission_list_tile_title": "通知の許可",
"partner_page_add_partner": "Add partner",
"partner_page_empty_message": "Your photos are not yet shared with any partner.",
"partner_page_no_more_users": "No more users to add",
"partner_page_partner_add_failed": "Failed to add partner",
"partner_page_select_partner": "Select partner",
"partner_page_shared_to_title": "Shared to",
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
"partner_page_stop_sharing_title": "Stop sharing your photos?",
"partner_page_title": "Partner",
"permission_onboarding_back": "Back",
"partner_page_add_partner": "パートナーを追加",
"partner_page_empty_message": "まだどのパートナーとも写真を共有してません",
"partner_page_no_more_users": "追加できるユーザーがもういません",
"partner_page_partner_add_failed": "パートナーの追加に失敗",
"partner_page_select_partner": "パートナーを選択",
"partner_page_shared_to_title": "次のユーザーと共有しす: ",
"partner_page_stop_sharing_content": "{}は写真へのアクセスができなくなります",
"partner_page_stop_sharing_title": "写真の共有を無効化しますか?",
"partner_page_title": "パートナー",
"permission_onboarding_back": "戻る",
"permission_onboarding_continue_anyway": "無視して続行",
"permission_onboarding_get_started": "はじめる",
"permission_onboarding_go_to_settings": "システム設定",
@ -307,32 +308,32 @@
"permission_onboarding_permission_limited": "写真へのアクセスが制限されています。Immichに写真のバックアップと管理を行わせるにはシステム設定から写真と動画のアクセス権限を変更してください。",
"permission_onboarding_request": "Immichは写真へのアクセス許可が必要です",
"profile_drawer_app_logs": "ログ",
"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_out_of_date_major": "アプリが更新されてません。最新のバージョンに更新してください",
"profile_drawer_client_out_of_date_minor": "アプリが更新されてません。最新のマイナーバージョンに更新してください",
"profile_drawer_client_server_up_to_date": "すべて最新です",
"profile_drawer_documentation": "Documentation",
"profile_drawer_documentation": "Immcihの説明書",
"profile_drawer_github": "GitHub",
"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_server_out_of_date_major": "サーバーが更新されてません。最新のバージョンに更新してください",
"profile_drawer_server_out_of_date_minor": "サーバーが更新されてません。最新のマイナーバージョンに更新してください",
"profile_drawer_settings": "設定",
"profile_drawer_sign_out": "サインアウト",
"profile_drawer_trash": "Trash",
"profile_drawer_trash": "ゴミ箱",
"recently_added_page_title": "最近",
"scaffold_body_error_occurred": "Error occurred",
"scaffold_body_error_occurred": "エラーが発生しました",
"search_bar_hint": "写真を検索",
"search_page_categories": "カテゴリ",
"search_page_favorites": "お気に入り",
"search_page_motion_photos": "モーションフォト",
"search_page_no_objects": "被写体に関するデータがなし",
"search_page_no_places": "場所に関するデータなし",
"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_people": "ピープル",
"search_page_person_add_name_dialog_cancel": "キャンセル",
"search_page_person_add_name_dialog_hint": "名前",
"search_page_person_add_name_dialog_save": "セーブ",
"search_page_person_add_name_dialog_title": "名前を追加",
"search_page_person_add_name_subtitle": "名前で検索して高速に探す",
"search_page_person_add_name_title": "名前を追加",
"search_page_person_edit_name": "名前を変更",
"search_page_places": "撮影地",
"search_page_recently_added": "最近追加",
"search_page_screenshots": "スクリーンショット",
@ -341,7 +342,7 @@
"search_page_videos": "ビデオ",
"search_page_view_all_button": "すべて表示",
"search_page_your_activity": "アクティビティ",
"search_page_your_map": "Your Map",
"search_page_your_map": "あなたのマップ",
"search_result_page_new_search_hint": "検索",
"search_suggestion_list_smart_search_hint_1": "スマート検索はデフォルトでオンになっています。メタデータで検索を行う場合:",
"search_suggestion_list_smart_search_hint_2": "m:単語",
@ -349,7 +350,7 @@
"select_user_for_sharing_page_err_album": "アルバム作成に失敗",
"select_user_for_sharing_page_share_suggestions": "ユーザ一覧",
"server_info_box_app_version": "アプリVer.",
"server_info_box_latest_release": "Latest Version",
"server_info_box_latest_release": "最新バージョン",
"server_info_box_server_url": " サーバのURL",
"server_info_box_server_version": "サーバーVer.",
"setting_image_viewer_help": "写真をタップするとサムネイル・中画質(要設定)・オリジナル(要設定)の順に読み込みます",
@ -375,66 +376,66 @@
"share_add_photos": "写真を追加",
"share_add_title": "タイトルを追加",
"share_create_album": "アルバムを作成",
"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",
"shared_album_activities_input_disable": "コメントはオフになってます",
"shared_album_activities_input_hint": "何か書き込みましょう",
"shared_album_activity_remove_content": "このアクティビティを削除しますか",
"shared_album_activity_remove_title": "アクティビティを削除します",
"shared_album_activity_setting_subtitle": "他のユーザーの返信を許可する",
"shared_album_activity_setting_title": "お気に入りとコメント",
"shared_album_section_people_action_error": "アルバムからの退出に失敗",
"shared_album_section_people_action_leave": "ユーザーをアルバムから退出",
"shared_album_section_people_action_remove_user": "ユーザーをアルバムから退出",
"shared_album_section_people_owner_label": "オーナー",
"shared_album_section_people_title": "ピープル",
"share_dialog_preparing": "準備中",
"shared_link_app_bar_title": "共有リンク",
"shared_link_clipboard_copied_massage": "Copied to clipboard",
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
"shared_link_clipboard_copied_massage": "クリップボードにコピーしました",
"shared_link_clipboard_text": "リンク: {}\nパスワード: {}",
"shared_link_create_app_bar_title": "共有リンクを作る",
"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_error": "共有用のリンク作成時にエラーが発生しました",
"shared_link_create_info": "誰でも写真を見れるようにする",
"shared_link_create_submit_button": "リンクを作る",
"shared_link_edit_allow_download": "Allow public user to download",
"shared_link_edit_allow_upload": "Allow public user to upload",
"shared_link_edit_allow_download": "写真のダウンロードの許可",
"shared_link_edit_allow_upload": "写真のアップロードを許可",
"shared_link_edit_app_bar_title": " リンクを編集する",
"shared_link_edit_change_expiry": "Change expiration time",
"shared_link_edit_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_never": "Never",
"shared_link_edit_change_expiry": "有効期限を変更",
"shared_link_edit_description": "概要欄",
"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": "{}",
"shared_link_edit_expire_after_option_hour": "1時間",
"shared_link_edit_expire_after_option_hours": "{}時間",
"shared_link_edit_expire_after_option_minute": "1",
"shared_link_edit_expire_after_option_minutes": "{}",
"shared_link_edit_expire_after_option_never": "有効期限なし",
"shared_link_edit_password": " パスワード",
"shared_link_edit_password_hint": "共有パスワードを入力する",
"shared_link_edit_show_meta": " メタデータを見る",
"shared_link_edit_submit_button": "リンクをアップデートする",
"shared_link_empty": "共有リンクはありません ",
"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_info_chip_download": "Download",
"shared_link_error_server_url_fetch": "サーバーのURLがゲットできません",
"shared_link_expired": "有効期限が切れました",
"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": "{}秒で切れます",
"shared_link_expires_seconds": "{}秒で切れます",
"shared_link_info_chip_download": "ダウンロード",
"shared_link_info_chip_metadata": "EXIF",
"shared_link_info_chip_upload": "Upload",
"shared_link_manage_links": "Manage Shared links",
"share_done": "Done",
"shared_link_info_chip_upload": "アップロード",
"shared_link_manage_links": "共有済みのリンクを管理",
"share_done": "完了",
"share_invite": "アルバムに招待",
"sharing_page_album": "共有アルバム",
"sharing_page_description": "共有アルバムを作成して同じネットワークにいる人たちに写真を共有",
"sharing_page_empty_list": "共有アルバムなし",
"sharing_silver_appbar_create_shared_album": "共有アルバムを作成",
"sharing_silver_appbar_shared_links": "Shared links",
"sharing_silver_appbar_shared_links": "共有リンク",
"sharing_silver_appbar_share_partner": "パートナーと共有",
"tab_controller_nav_library": "ライブラリ",
"tab_controller_nav_photos": "写真",
@ -450,30 +451,30 @@
"theme_setting_theme_title": "テーマ",
"theme_setting_three_stage_loading_subtitle": "三段階読み込みを有効にするとパフォーマンスが改善する可能性がありますが、ネットワーク負荷が著しく増加します",
"theme_setting_three_stage_loading_title": "三段階読み込みをオンにする",
"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": "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",
"translated_text_options": "オプション",
"trash_page_delete": "削除",
"trash_page_delete_all": "全て削除",
"trash_page_empty_trash_btn": "コミ箱を空にする",
"trash_page_empty_trash_dialog_content": "ゴミ箱を空にしますか?選択された項目は完全に削除されます。この操作は取り消せません",
"trash_page_empty_trash_dialog_ok": "了解",
"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": "削除({})",
"upload_dialog_cancel": "キャンセル",
"upload_dialog_info": "選択した項目のバックアップをしますか?",
"upload_dialog_ok": "アップロード",
"upload_dialog_title": "アップロード",
"version_announcement_overlay_ack": "了解",
"version_announcement_overlay_release_notes": "更新情報",
"version_announcement_overlay_text_1": "こんにちは、またはこんばんは!新しい",
"version_announcement_overlay_text_2": "のバージョンが公開中です。",
"version_announcement_overlay_text_3": "を確認してみてください。docker-composeや.envファイルが最新の状態に更新されているか、特にWatchTowerなどのツールを使ってDockerイメージを自動アップデートしてる人は確認してください。",
"version_announcement_overlay_title": "サーバーの新バージョンリリース\uD83C\uDF89",
"viewer_remove_from_stack": "Remove from Stack",
"viewer_stack_use_as_main_asset": "Use as Main Asset",
"viewer_unstack": "Un-Stack"
"viewer_remove_from_stack": "スタックから外す",
"viewer_stack_use_as_main_asset": "メインの画像として使用する",
"viewer_unstack": "スタックを解除"
}

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "상세정보",
"exif_bottom_sheet_location": "위치",
"exif_bottom_sheet_location_add": "위치 지정",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "진행중",
"experimental_settings_new_asset_list_title": "실험적 사진 그리드 적용",
"experimental_settings_subtitle": "문제시 책임지지 않습니다!",

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "INFORMĀCIJA",
"exif_bottom_sheet_location": "ATRAŠANĀS VIETA",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PEOPLE",
"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!",

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "DETAILS",
"exif_bottom_sheet_location": "LOCATION",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Work in progress",
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Use at your own risk!",

View File

@ -35,8 +35,8 @@
"app_bar_signout_dialog_title": "Logg ut",
"archive_page_no_archived_assets": "Ingen arkiverte objekter funnet",
"archive_page_title": "Arkiv ({})",
"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_action_delete_err_read_only": "Kan ikke slette objekt(er) med kun lese-rettighet, hopper over",
"asset_action_share_err_offline": "Kan ikke hente offline objekt(er), hopper over",
"asset_list_layout_settings_dynamic_layout_title": "Dynamisk bildeorganisering",
"asset_list_layout_settings_group_automatically": "Automatisk",
"asset_list_layout_settings_group_by": "Grupper bilder etter",
@ -142,15 +142,15 @@
"control_bottom_app_bar_archive": "Arkiver",
"control_bottom_app_bar_create_new_album": "Lag nytt album",
"control_bottom_app_bar_delete": "Slett",
"control_bottom_app_bar_delete_from_immich": "Delete from Immich",
"control_bottom_app_bar_delete_from_local": "Delete from device",
"control_bottom_app_bar_delete_from_immich": "Slett fra Immich",
"control_bottom_app_bar_delete_from_local": "Slett fra enhet",
"control_bottom_app_bar_edit_location": "Endre lokasjon",
"control_bottom_app_bar_edit_time": "Endre Dato og tid",
"control_bottom_app_bar_favorite": "Favoritt",
"control_bottom_app_bar_share": "Del",
"control_bottom_app_bar_share_to": "Del til",
"control_bottom_app_bar_stack": "Stable",
"control_bottom_app_bar_trash_from_immich": "Move to Trash",
"control_bottom_app_bar_trash_from_immich": "Flytt til søppelkasse",
"control_bottom_app_bar_unarchive": "Fjern fra arkiv",
"control_bottom_app_bar_unfavorite": "Fjern favoritt",
"control_bottom_app_bar_upload": "Last opp",
@ -165,15 +165,15 @@
"daily_title_text_date_year": "E, MMM dd, yyyy",
"date_format": "E, LLL d, y • h:mm a",
"delete_dialog_alert": "Disse objektene vil bli slettet permanent fra Immich og fra enheten din",
"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_alert_local": "Disse objektene vil bli permanent slettet fra enheten din, men vil fortsatt være tilgjengelige fra Immich serveren",
"delete_dialog_alert_local_non_backed_up": "Noen av objektene er ikke sikkerhetskopiert til Immich og vil bli permanent fjernet fra enheten din",
"delete_dialog_alert_remote": "Disse objektene vil bli permanent slettet fra Immich serveren",
"delete_dialog_cancel": "Avbryt",
"delete_dialog_ok": "Slett",
"delete_dialog_ok_force": "Delete Anyway",
"delete_dialog_ok_force": "Slett uansett",
"delete_dialog_title": "Slett permanent",
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only",
"delete_local_dialog_ok_force": "Delete Anyway",
"delete_local_dialog_ok_backed_up_only": "Slett kun sikkerhetskopierte objekter",
"delete_local_dialog_ok_force": "Slett uansett",
"delete_shared_link_dialog_content": "Er du sikker på at du vil slette denne delte linken?",
"delete_shared_link_dialog_title": "Slett delt link",
"description_input_hint_text": "Legg til beskrivelse ...",
@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "DETALJER",
"exif_bottom_sheet_location": "PLASSERING",
"exif_bottom_sheet_location_add": "Legg til lokasjon",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Under utvikling",
"experimental_settings_new_asset_list_title": "Aktiver eksperimentell rutenettsvisning",
"experimental_settings_subtitle": "Bruk på egen risiko!",
@ -199,7 +200,7 @@
"home_page_archive_err_partner": "Kan ikke arkivere partnerobjekter, hopper over",
"home_page_building_timeline": "Genererer tidslinjen",
"home_page_delete_err_partner": "Kan ikke slette partnerobjekter, hopper over",
"home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping",
"home_page_delete_remote_err_local": "Lokale objekter i fjernslettingsvalgene, hopper over",
"home_page_favorite_err_local": "Kan ikke sette favoritt på lokale objekter enda, hopper over",
"home_page_favorite_err_partner": "Kan ikke merke partnerobjekter som favoritt enda, hopper over",
"home_page_first_time_notice": "Hvis dette er første gangen du benytter appen, velg et album (eller flere) for sikkerhetskopiering, slik at tidslinjen kan fylles med dine bilder og videoer.",
@ -275,12 +276,12 @@
"map_settings_include_show_archived": "Inkluder arkiverte",
"map_settings_only_relative_range": "Datoområde",
"map_settings_only_show_favorites": "Vis kun favoritter",
"map_settings_theme_settings": "Map Theme",
"map_settings_theme_settings": "Karttema",
"map_zoom_to_see_photos": "Zoom ut for å se bilder",
"monthly_title_text_date_format": "MMMM y",
"motion_photos_page_title": "Bevegelige bilder",
"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",
"multiselect_grid_edit_date_time_err_read_only": "Kan ikke endre dato på objekt(er) med kun lese-rettigheter, hopper over",
"multiselect_grid_edit_gps_err_read_only": "Kan ikke endre lokasjon på objekt(er) med kun lese-rettigheter, hopper over",
"notification_permission_dialog_cancel": "Avbryt",
"notification_permission_dialog_content": "For å aktivere notifikasjoner, gå til Innstillinger og velg tillat.",
"notification_permission_dialog_settings": "Innstillinger",

View File

@ -1,6 +1,6 @@
{
"action_common_cancel": "Annuleren",
"action_common_update": "Updaten",
"action_common_update": "Bijwerken",
"add_to_album_bottom_sheet_added": "Toegevoegd aan {album}",
"add_to_album_bottom_sheet_already_exists": "Staat al in {album}",
"advanced_settings_log_level_title": "Log niveau: {}",
@ -26,17 +26,17 @@
"album_viewer_appbar_share_err_title": "Albumtitel wijzigen mislukt",
"album_viewer_appbar_share_leave": "Verlaat album",
"album_viewer_appbar_share_remove": "Verwijder uit album",
"album_viewer_appbar_share_to": "Deel Naar",
"album_viewer_appbar_share_to": "Delen met",
"album_viewer_page_share_add_users": "Gebruikers toevoegen",
"all_people_page_title": "Personen",
"all_videos_page_title": "Video's",
"app_bar_signout_dialog_content": "Weet je zeker dat je je wilt afmelden?",
"app_bar_signout_dialog_content": "Weet je zeker dat je wilt uitloggen?",
"app_bar_signout_dialog_ok": "Ja",
"app_bar_signout_dialog_title": "Log uit",
"archive_page_no_archived_assets": "Geen gearchiveerde assets gevonden",
"archive_page_title": "Archief ({})",
"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_action_delete_err_read_only": "Kan alleen-lezen asset(s) niet verwijderen, overslaan",
"asset_action_share_err_offline": "Kan offline asset(s) niet ophalen, overslaan",
"asset_list_layout_settings_dynamic_layout_title": "Dynamische layout",
"asset_list_layout_settings_group_automatically": "Automatisch",
"asset_list_layout_settings_group_by": "Groupeer assets per",
@ -104,7 +104,7 @@
"backup_err_only_album": "Kan het enige album niet verwijderen",
"backup_info_card_assets": "assets",
"backup_manual_cancelled": "Geannuleerd",
"backup_manual_failed": "Gefaald",
"backup_manual_failed": "Mislukt",
"backup_manual_in_progress": "Het uploaden is al bezig. Probeer het na een tijdje",
"backup_manual_success": "Succes",
"backup_manual_title": "Uploadstatus",
@ -113,7 +113,7 @@
"cache_settings_clear_cache_button_title": "Wist de cache van de app. Dit zal de presentaties van de app aanzienlijk beïnvloeden totdat de cache opnieuw is opgebouwd.",
"cache_settings_duplicated_assets_clear_button": "MAAK VRIJ",
"cache_settings_duplicated_assets_subtitle": "Foto's en video's op de zwarte lijst van de app",
"cache_settings_duplicated_assets_title": "Gedupliceerde Assets ({})",
"cache_settings_duplicated_assets_title": "Gedupliceerde assets ({})",
"cache_settings_image_cache_size": "Grootte afbeeldingscache ({} assets)",
"cache_settings_statistics_album": "Bibliotheekthumbnails",
"cache_settings_statistics_assets": "{} assets ({})",
@ -124,7 +124,7 @@
"cache_settings_subtitle": "Beheer het cachegedrag van de Immich app",
"cache_settings_thumbnail_size": "Thumbnail-cachegrootte ({} assets)",
"cache_settings_tile_subtitle": "Beheer het gedrag van lokale opslag",
"cache_settings_tile_title": "Lokale Opslag",
"cache_settings_tile_title": "Lokale opslag",
"cache_settings_title": "Cache-instellingen",
"change_password_form_confirm_password": "Bevestig wachtwoord",
"change_password_form_description": "Hallo {name},\n\nDit is ofwel de eerste keer dat je inlogt, of er is een verzoek gedaan om je wachtwoord te wijzigen. Vul hieronder een nieuw wachtwoord in.",
@ -142,15 +142,15 @@
"control_bottom_app_bar_archive": "Archiveren",
"control_bottom_app_bar_create_new_album": "Nieuw album maken",
"control_bottom_app_bar_delete": "Verwijderen",
"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": "Locatie Bewerken",
"control_bottom_app_bar_edit_time": "Datum & Tijd Bewerken",
"control_bottom_app_bar_delete_from_immich": "Verwijderen van Immich",
"control_bottom_app_bar_delete_from_local": "Verwijderen van apparaat",
"control_bottom_app_bar_edit_location": "Locatie bewerken",
"control_bottom_app_bar_edit_time": "Datum & tijd bewerken",
"control_bottom_app_bar_favorite": "Favoriet",
"control_bottom_app_bar_share": "Delen",
"control_bottom_app_bar_share_to": "Deel Naar",
"control_bottom_app_bar_share_to": "Delen met",
"control_bottom_app_bar_stack": "Stapel",
"control_bottom_app_bar_trash_from_immich": "Move to Trash",
"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",
@ -165,26 +165,27 @@
"daily_title_text_date_year": "E dd MMM yyyy",
"date_format": "E d LLL y • H:mm",
"delete_dialog_alert": "Deze items zullen permanent verwijderd worden van Immich en je apparaat",
"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_alert_local": "Deze items worden permanent verwijderd van je apparaat, maar blijven beschikbaar op de Immich server",
"delete_dialog_alert_local_non_backed_up": "Van sommige items is geen back-up gemaakt in Immich en zullen permanent van je apparaat worden verwijderd",
"delete_dialog_alert_remote": "Deze items worden permanent verwijderd van de Immich server",
"delete_dialog_cancel": "Annuleren",
"delete_dialog_ok": "Verwijderen",
"delete_dialog_ok_force": "Delete Anyway",
"delete_dialog_ok_force": "Toch verwijderen",
"delete_dialog_title": "Permanent verwijderen",
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only",
"delete_local_dialog_ok_force": "Delete Anyway",
"delete_local_dialog_ok_backed_up_only": "Verwijder alleen met back-up",
"delete_local_dialog_ok_force": "Toch verwijderen",
"delete_shared_link_dialog_content": "Weet je zeker dat je deze gedeelde link wilt verwijderen?",
"delete_shared_link_dialog_title": "Verwijder Gedeelde Link",
"delete_shared_link_dialog_title": "Verwijder gedeelde link",
"description_input_hint_text": "Beschrijving toevoegen...",
"description_input_submit_error": "Beschrijving bijwerken mislukt, controleer het logboek voor meer details",
"edit_date_time_dialog_date_time": "Datum en Tijd",
"edit_date_time_dialog_date_time": "Datum en tijd",
"edit_date_time_dialog_timezone": "Tijdzone",
"edit_location_dialog_title": "Locatie",
"exif_bottom_sheet_description": "Beschrijving toevoegen...",
"exif_bottom_sheet_details": "DETAILS",
"exif_bottom_sheet_location": "LOCATIE",
"exif_bottom_sheet_location_add": "Locatie toevoegen",
"exif_bottom_sheet_people": "MENSEN",
"experimental_settings_new_asset_list_subtitle": "Werk in uitvoering",
"experimental_settings_new_asset_list_title": "Experimenteel fotoraster inschakelen",
"experimental_settings_subtitle": "Gebruik op eigen risico!",
@ -199,7 +200,7 @@
"home_page_archive_err_partner": "Partner assets kunnen niet gearchiveerd worden, overslaan",
"home_page_building_timeline": "Tijdlijn opbouwen",
"home_page_delete_err_partner": "Partner assets kunnen niet verwijderd worden, overslaan",
"home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping",
"home_page_delete_remote_err_local": "Lokale assets staan in verwijder selectie externe assets, overslaan",
"home_page_favorite_err_local": "Lokale assets kunnen nog niet als favoriet worden aangemerkt, overslaan",
"home_page_favorite_err_partner": "Partner assets kunnen nog niet ge-favoriet worden, overslaan",
"home_page_first_time_notice": "Als dit de eerste keer is dat je de app gebruikt, zorg er dan voor dat je een back-up album kiest, zodat de tijdlijn gevuld kan worden met foto's en video's uit het album.",
@ -259,10 +260,10 @@
"map_location_dialog_yes": "Ja",
"map_location_picker_page_use_location": "Gebruik deze locatie",
"map_location_service_disabled_content": "Locatie service moet ingeschakeld zijn om assets van je huidige locatie weer te geven. Wil je het nu inschakelen?",
"map_location_service_disabled_title": "Locatie Service uitgeschakeld",
"map_location_service_disabled_title": "Locatie service uitgeschakeld",
"map_no_assets_in_bounds": "Geen foto's in dit gebied",
"map_no_location_permission_content": "Locatie toestemming is nodig om assets van je huidige locatie weer te geven. Wil je het nu toestaan?",
"map_no_location_permission_title": "Locatie Toestemming geweigerd",
"map_no_location_permission_title": "Locatie toestemming geweigerd",
"map_settings_dark_mode": "Donkere modus",
"map_settings_date_range_option_all": "Alle",
"map_settings_date_range_option_day": "Afgelopen 24 uur",
@ -270,17 +271,17 @@
"map_settings_date_range_option_year": "Afgelopen jaar",
"map_settings_date_range_option_years": "Afgelopen {} jaar",
"map_settings_dialog_cancel": "Annuleren",
"map_settings_dialog_save": "Sla op",
"map_settings_dialog_save": "Opslaan",
"map_settings_dialog_title": "Kaart Instellingen",
"map_settings_include_show_archived": "Weergeef Gearchiveerden",
"map_settings_include_show_archived": "Toon gearchiveerde",
"map_settings_only_relative_range": "Datum bereik",
"map_settings_only_show_favorites": "Toon enkel favorieten",
"map_settings_theme_settings": "Map Theme",
"map_settings_theme_settings": "Kaart thema",
"map_zoom_to_see_photos": "Zoom uit om foto's te zien",
"monthly_title_text_date_format": "MMMM y",
"motion_photos_page_title": "Bewegende foto's",
"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",
"multiselect_grid_edit_date_time_err_read_only": "Kan datum van alleen-lezen asset(s) niet wijzigen, overslaan",
"multiselect_grid_edit_gps_err_read_only": "Kan locatie van alleen-lezen asset(s) niet wijzigen, overslaan",
"notification_permission_dialog_cancel": "Annuleren",
"notification_permission_dialog_content": "Om meldingen in te schakelen, ga naar Instellingen en selecteer toestaan.",
"notification_permission_dialog_settings": "Instellingen",
@ -390,7 +391,7 @@
"shared_link_app_bar_title": "Gedeelde links",
"shared_link_clipboard_copied_massage": "Gekopieerd naar klembord",
"shared_link_clipboard_text": "Link: {}\nWachtwoord: {}",
"shared_link_create_app_bar_title": "Link maken om te delen",
"shared_link_create_app_bar_title": "Gedeelde link maken",
"shared_link_create_error": "Fout bij het maken van een gedeelde link",
"shared_link_create_info": "Laat iedereen met de link de geselecteerde foto(s) zien",
"shared_link_create_submit_button": "Link maken",
@ -399,7 +400,7 @@
"shared_link_edit_app_bar_title": "Bewerk link",
"shared_link_edit_change_expiry": "Bewerk vervaltijd",
"shared_link_edit_description": "Beschrijving",
"shared_link_edit_description_hint": "Geef de deel beschrijving",
"shared_link_edit_description_hint": "Voer beschrijving voor de gedeelde link in",
"shared_link_edit_expire_after": "Verval na",
"shared_link_edit_expire_after_option_day": "1 dag",
"shared_link_edit_expire_after_option_days": "{} dagen",
@ -409,9 +410,9 @@
"shared_link_edit_expire_after_option_minutes": "{} minuten",
"shared_link_edit_expire_after_option_never": "Nooit",
"shared_link_edit_password": "Wachtwoord",
"shared_link_edit_password_hint": "Voer het deel wachtwoord in",
"shared_link_edit_password_hint": "Voer wachtwoord voor de gedeelde link in",
"shared_link_edit_show_meta": "Toon metadata",
"shared_link_edit_submit_button": "Update link",
"shared_link_edit_submit_button": "Link bijwerken",
"shared_link_empty": "Je hebt geen gedeelde links",
"shared_link_error_server_url_fetch": "Kan de server url niet ophalen",
"shared_link_expired": "Verlopen",
@ -452,19 +453,19 @@
"theme_setting_three_stage_loading_title": "Laden in drie fasen inschakelen",
"translated_text_options": "Opties",
"trash_page_delete": "Verwijderen",
"trash_page_delete_all": "Verwijder Alle",
"trash_page_delete_all": "Verwijder alle",
"trash_page_empty_trash_btn": "Leeg prullenbak",
"trash_page_empty_trash_dialog_content": "Wil je je weggegooide assets leegmaken? Deze items worden permanent verwijderd van Immich",
"trash_page_empty_trash_dialog_content": "Wil je de prullenbak leegmaken? Deze items worden permanent verwijderd van Immich",
"trash_page_empty_trash_dialog_ok": "Ok",
"trash_page_info": "Verwijderde items worden permanent verwijderd na {} dagen",
"trash_page_no_assets": "Geen verwijderde assets",
"trash_page_restore": "Herstellen",
"trash_page_restore_all": "Herstel Alle",
"trash_page_restore_all": "Herstel alle",
"trash_page_select_assets_btn": "Selecteer assets",
"trash_page_select_btn": "Selecteren",
"trash_page_title": "Prullenbak ({})",
"upload_dialog_cancel": "Annuleren",
"upload_dialog_info": "Wilt u een backup maken van de geselecteerde Asset(s) op de server?",
"upload_dialog_info": "Wil je een backup maken van de geselecteerde asset(s) op de server?",
"upload_dialog_ok": "Uploaden",
"upload_dialog_title": "Asset uploaden",
"version_announcement_overlay_ack": "Bevestig",

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "SZCZEGÓŁY",
"exif_bottom_sheet_location": "LOKALIZACJA",
"exif_bottom_sheet_location_add": "Dodaj lokalizację",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Praca w toku",
"experimental_settings_new_asset_list_title": "Włącz eksperymentalną układ zdjęć",
"experimental_settings_subtitle": "Używaj na własne ryzyko!",

View File

@ -1,8 +1,8 @@
{
"action_common_cancel": "Cancel",
"action_common_update": "Update",
"add_to_album_bottom_sheet_added": "Added to {album}",
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
"action_common_cancel": "Cancelar",
"action_common_update": "Atualizar",
"add_to_album_bottom_sheet_added": "Adicionar a {album}",
"add_to_album_bottom_sheet_already_exists": "Já pertence a {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",
@ -16,7 +16,7 @@
"album_info_card_backup_album_included": "INCLUÍDO",
"album_thumbnail_card_item": "1 item",
"album_thumbnail_card_items": "{} itens",
"album_thumbnail_card_shared": "Compartilhado",
"album_thumbnail_card_shared": " · Partilhado",
"album_thumbnail_owned": "Owned",
"album_thumbnail_shared_by": "Shared by {}",
"album_viewer_appbar_share_delete": "Deletar álbum",
@ -29,21 +29,21 @@
"album_viewer_appbar_share_to": "Share To",
"album_viewer_page_share_add_users": "Adicionar usuários",
"all_people_page_title": "People",
"all_videos_page_title": "Videos",
"app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"all_videos_page_title": "Vídeos",
"app_bar_signout_dialog_content": "Tem a certeza que deseja sair?",
"app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out",
"app_bar_signout_dialog_title": "Sair",
"archive_page_no_archived_assets": "No archived assets found",
"archive_page_title": "Archive ({})",
"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_layout_settings_dynamic_layout_title": "Dynamic layout",
"asset_action_delete_err_read_only": "Não é possível eliminar o(s) recurso(s) só de leitura, ignorando",
"asset_action_share_err_offline": "Não é possível obter recurso(s) offline, ignorando",
"asset_list_layout_settings_dynamic_layout_title": "Disposição dinâmica",
"asset_list_layout_settings_group_automatically": "Automatic",
"asset_list_layout_settings_group_by": "Group assets by",
"asset_list_layout_settings_group_by_month": "Month",
"asset_list_layout_settings_group_by_month_day": "Month + day",
"asset_list_settings_subtitle": "Configurações de layout da grade de fotos",
"asset_list_settings_title": "Grade de fotos",
"asset_list_layout_settings_group_by": "Agrupar recursos por",
"asset_list_layout_settings_group_by_month": "Mês",
"asset_list_layout_settings_group_by_month_day": "Mês + dia",
"asset_list_settings_subtitle": "Configurações de layout da grelha de fotos",
"asset_list_settings_title": "Grelha de fotos",
"backup_album_selection_page_albums_device": "Álbuns no dispositivo ({})",
"backup_album_selection_page_albums_tap": "Toque para incluir, duplo toque para exluir",
"backup_album_selection_page_assets_scatter": "Os itens podem estar espalhados por vários álbuns. Assim, os álbuns podem ser incluídos ou excluídos durante o processo de backup.",
@ -90,11 +90,11 @@
"backup_controller_page_remainder": "Restante",
"backup_controller_page_remainder_sub": "Fotos e vídeos restantes para fazer backup da seleção",
"backup_controller_page_select": "Selecione",
"backup_controller_page_server_storage": "Espaço no Servidor",
"backup_controller_page_server_storage": "Armazenamento no servidor",
"backup_controller_page_start_backup": "Iniciar Backup",
"backup_controller_page_status_off": "Backup está desligado",
"backup_controller_page_status_on": "Backup está ligado",
"backup_controller_page_storage_format": "{} de {} usado",
"backup_controller_page_storage_format": "{} de {} usados",
"backup_controller_page_to_backup": "Álbuns para fazer backup",
"backup_controller_page_total": "Total",
"backup_controller_page_total_sub": "Todas as fotos e vídeos dos álbuns selecionados",
@ -118,91 +118,92 @@
"cache_settings_statistics_album": "Miniaturas da biblioteca",
"cache_settings_statistics_assets": "{} itens ({})",
"cache_settings_statistics_full": "Imagens completas",
"cache_settings_statistics_shared": "Miniaturas de álbuns compartilhados",
"cache_settings_statistics_shared": "Miniaturas de álbuns partilhados",
"cache_settings_statistics_thumbnail": "Miniaturas",
"cache_settings_statistics_title": "Uso de cache",
"cache_settings_subtitle": "Controle o comportamento de cache do aplicativo Immich",
"cache_settings_thumbnail_size": "Tamanho do cache de miniaturas ({} itens)",
"cache_settings_tile_subtitle": "Control the local storage behaviour",
"cache_settings_tile_title": "Local Storage",
"cache_settings_tile_subtitle": "Controlar o comportamento do armazenamento local",
"cache_settings_tile_title": "Armazenamento local",
"cache_settings_title": "Configurações de cache",
"change_password_form_confirm_password": "Confirm Password",
"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": "New Password",
"change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password",
"common_add_to_album": "Add to album",
"common_change_password": "Change Password",
"common_create_new_album": "Create new album",
"common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.",
"change_password_form_confirm_password": "Confirme a senha",
"change_password_form_description": "Olá {name},\n\nÉ a primeira vez que entra no sistema ou foi-lhe pedido que alterasse a sua palavra-passe. Introduza a nova palavra-passe abaixo.",
"change_password_form_new_password": "Nova senha",
"change_password_form_password_mismatch": "As senhas não coincidem",
"change_password_form_reenter_new_password": "Re-introduza a nova senha",
"common_add_to_album": "Adicionar ao álbum",
"common_change_password": "Mudar a senha",
"common_create_new_album": "Criar novo álbum",
"common_server_error": "Verifique a sua ligação de rede, certifique-se de que o servidor está acessível e de que as versões da aplicação/servidor são compatíveis.",
"common_shared": "Shared",
"control_bottom_app_bar_add_to_album": "Adicionar ao álbum",
"control_bottom_app_bar_album_info": "{} itens",
"control_bottom_app_bar_album_info_shared": "{} itens · Compartilhado",
"control_bottom_app_bar_album_info_shared": "{} itens · Partilhado",
"control_bottom_app_bar_archive": "Archive",
"control_bottom_app_bar_create_new_album": "Criar novo álbum",
"control_bottom_app_bar_delete": "Deletar",
"control_bottom_app_bar_delete_from_immich": "Delete from Immich",
"control_bottom_app_bar_delete_from_local": "Delete from device",
"control_bottom_app_bar_delete_from_immich": "Apagar do Immich",
"control_bottom_app_bar_delete_from_local": "Apagar do dispositivo",
"control_bottom_app_bar_edit_location": "Edit Location",
"control_bottom_app_bar_edit_time": "Edit Date & Time",
"control_bottom_app_bar_favorite": "Favorite",
"control_bottom_app_bar_share": "Compartilhar",
"control_bottom_app_bar_share": "Partilhar",
"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_trash_from_immich": "Mover para o lixo",
"control_bottom_app_bar_unarchive": "Unarchive",
"control_bottom_app_bar_unfavorite": "Unfavorite",
"control_bottom_app_bar_upload": "Upload",
"create_album_page_untitled": "Sem título",
"create_shared_album_page_create": "Criar",
"create_shared_album_page_share": "Compartilhar",
"create_shared_album_page_share": "Partilhar",
"create_shared_album_page_share_add_assets": "ADICIONAR ITENS",
"create_shared_album_page_share_select_photos": "Selecionar Fotos",
"curated_location_page_title": "Places",
"curated_object_page_title": "Things",
"curated_location_page_title": "Sítios",
"curated_object_page_title": "Objetos",
"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": "Esses itens serão permanentemente deletados do Immich e do seu dispositivo",
"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_alert_local": "Estes itens serão removidos permanentemente do seu dispositivo, mas continuarão disponíveis no servidor Immich",
"delete_dialog_alert_local_non_backed_up": "Alguns dos itens não estão guardados no Immich e serão removidos permanentemente do seu dispositivo",
"delete_dialog_alert_remote": "Estes itens serão permanentemente eliminados do servidor Immich",
"delete_dialog_cancel": "Cancelar",
"delete_dialog_ok": "Deletar",
"delete_dialog_ok_force": "Delete Anyway",
"delete_dialog_ok_force": "Apagar de qualquer forma",
"delete_dialog_title": "Deletar Permanentemente",
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only",
"delete_local_dialog_ok_force": "Delete Anyway",
"delete_local_dialog_ok_backed_up_only": "Eliminar apenas existentes na cópia de segurança",
"delete_local_dialog_ok_force": "Apagar de qualquer forma",
"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": "Add description...",
"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_date_time_dialog_date_time": "Data e Hora",
"edit_date_time_dialog_timezone": "Fuso horário",
"edit_location_dialog_title": "Location",
"exif_bottom_sheet_description": "Adicionar Descrição...",
"exif_bottom_sheet_details": "DETALHES",
"exif_bottom_sheet_location": "LOCALIZAÇÃO",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Trabalho em andamento",
"experimental_settings_new_asset_list_title": "Ativar visualização de grade experimental",
"experimental_settings_new_asset_list_title": "Ativar visualização de grelha experimental",
"experimental_settings_subtitle": "Use por sua conta e risco!",
"experimental_settings_title": "Experimental",
"favorites_page_no_favorites": "No favorite assets found",
"favorites_page_title": "Favorites",
"favorites_page_title": "Favoritos",
"home_page_add_to_album_conflicts": "Ativos {added} adicionados ao álbum {album}. {failed} ativos já estão no álbum.",
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
"home_page_add_to_album_err_local": "Ainda não é possível adicionar recursos locais aos álbuns, ignorando",
"home_page_add_to_album_success": "Ativos {added} adicionados ao álbum {album}.",
"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": "Building the timeline",
"home_page_building_timeline": "A construir a timeline",
"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_delete_remote_err_local": "Recursos locais na seleção remota de eliminação, ignorando",
"home_page_favorite_err_local": "Ainda não é possível adicionar recursos locais favoritos, ignorando",
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
"home_page_first_time_notice": "Se for a primeira vez que utiliza a aplicação, certifique-se de que escolhe um álbum ou álbuns de cópia de segurança, para que a linha cronológica possa preencher as fotografias e os vídeos no(s) álbum(s).",
"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",
@ -210,16 +211,16 @@
"image_viewer_page_state_provider_share_error": "Share Error",
"library_page_albums": "Álbuns",
"library_page_archive": "Archive",
"library_page_device_albums": "Albums on Device",
"library_page_favorites": "Favorites",
"library_page_new_album": "Novo Album",
"library_page_sharing": "Sharing",
"library_page_sort_asset_count": "Number of assets",
"library_page_sort_created": "Created date",
"library_page_device_albums": "Álbuns no dispositivo",
"library_page_favorites": "Favoritos",
"library_page_new_album": "Novo álbum",
"library_page_sharing": "Partilhar",
"library_page_sort_asset_count": "Número de recursos",
"library_page_sort_created": "Data de criação",
"library_page_sort_last_modified": "Last modified",
"library_page_sort_most_oldest_photo": "Oldest photo",
"library_page_sort_most_oldest_photo": "Foto mais antiga",
"library_page_sort_most_recent_photo": "Most recent photo",
"library_page_sort_title": "Album title",
"library_page_sort_title": "Título do álbum",
"location_picker_choose_on_map": "Choose on map",
"location_picker_latitude": "Latitude",
"location_picker_latitude_error": "Enter a valid latitude",
@ -228,8 +229,8 @@
"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_api_exception": "Excepção de API. Verifique o URL do servidor e tente novamente.",
"login_form_back_button_text": "Voltar",
"login_form_button_text": "Login",
"login_form_email_hint": "seuemail@email.com",
"login_form_endpoint_hint": "http://ip-do-seu-servidor:porta/api",
@ -245,15 +246,15 @@
"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": "Senha",
"login_form_next_button": "Next",
"login_form_next_button": "Avançar",
"login_form_password_hint": "senha",
"login_form_save_login": "Permanecer logado",
"login_form_server_empty": "Enter a server URL.",
"login_form_server_error": "Could not connect to server.",
"login_form_server_empty": "Introduzir um URL de servidor.",
"login_form_server_error": "Não foi possível ligar ao servidor.",
"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_assets_in_bounds": "{} fotos",
"map_cannot_get_user_location": "Cannot get user's location",
"map_location_dialog_cancel": "Cancel",
"map_location_dialog_yes": "Yes",
@ -264,83 +265,83 @@
"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_date_range_option_all": "Tudo",
"map_settings_date_range_option_day": "Últimas 24 horas",
"map_settings_date_range_option_days": "Últimos {} dias",
"map_settings_date_range_option_year": "Último ano",
"map_settings_date_range_option_years": "Últimos {} anos",
"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_only_relative_range": "Date range",
"map_settings_only_show_favorites": "Show Favorite Only",
"map_settings_theme_settings": "Map Theme",
"map_settings_theme_settings": "Tema do mapa",
"map_zoom_to_see_photos": "Zoom out to see photos",
"monthly_title_text_date_format": "MMMM y",
"motion_photos_page_title": "Motion Photos",
"motion_photos_page_title": "Fotos com movimento",
"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",
"notification_permission_dialog_cancel": "Cancel",
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
"notification_permission_dialog_cancel": "Cancelar",
"notification_permission_dialog_content": "Para ativar as notificações, vá a Definições e selecione permitir.",
"notification_permission_dialog_settings": "Settings",
"notification_permission_list_tile_content": "Grant permission to enable notifications.",
"notification_permission_list_tile_enable_button": "Enable Notifications",
"notification_permission_list_tile_title": "Notification Permission",
"partner_page_add_partner": "Add partner",
"notification_permission_list_tile_title": "Permissão de notificações",
"partner_page_add_partner": "Adicionar parceiro",
"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_select_partner": "Selecionar parceiro",
"partner_page_shared_to_title": "Shared to",
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
"partner_page_stop_sharing_title": "Stop sharing your photos?",
"partner_page_title": "Partner",
"permission_onboarding_back": "Back",
"permission_onboarding_continue_anyway": "Continue anyway",
"permission_onboarding_continue_anyway": "Continuar de qualquer maneira",
"permission_onboarding_get_started": "Get started",
"permission_onboarding_go_to_settings": "Go to settings",
"permission_onboarding_grant_permission": "Grant permission",
"permission_onboarding_log_out": "Log out",
"permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.",
"permission_onboarding_permission_granted": "Permission granted! You are all set.",
"permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.",
"permission_onboarding_request": "Immich requires permission to view your photos and videos.",
"permission_onboarding_log_out": "Sair",
"permission_onboarding_permission_denied": "Permissão negada. Para utilizar o Immich, conceda permissões de fotografia e vídeo nas Definições.",
"permission_onboarding_permission_granted": "Autorização concedida! Está tudo pronto.",
"permission_onboarding_permission_limited": "Permissão limitada. Para permitir que o Immich faça cópias de segurança e gira toda a sua coleção de galerias, conceda permissões para fotografias e vídeos nas Definições.",
"permission_onboarding_request": "O Immich requer autorização para ver as suas fotografias e vídeos.",
"profile_drawer_app_logs": "Logs",
"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_out_of_date_major": "A aplicação móvel está desatualizada. Atualize para a versão principal mais recente.",
"profile_drawer_client_out_of_date_minor": "A aplicação móvel está desatualizada. Por favor, atualize para a versão mais recente.",
"profile_drawer_client_server_up_to_date": "Cliente e Servidor atualizados",
"profile_drawer_documentation": "Documentation",
"profile_drawer_github": "GitHub",
"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_server_out_of_date_major": "O servidor está desatualizado. Atualize para a versão principal mais recente.",
"profile_drawer_server_out_of_date_minor": "O servidor está desatualizado. Atualize para a versão mais recente.",
"profile_drawer_settings": "Configurações",
"profile_drawer_sign_out": "Sair",
"profile_drawer_trash": "Trash",
"recently_added_page_title": "Recently Added",
"scaffold_body_error_occurred": "Error occurred",
"recently_added_page_title": "Adicionado recentemente",
"scaffold_body_error_occurred": "Ocorreu um erro",
"search_bar_hint": "Busque suas fotos",
"search_page_categories": "Categories",
"search_page_favorites": "Favorites",
"search_page_motion_photos": "Motion Photos",
"search_page_favorites": "Favoritos",
"search_page_motion_photos": "Fotos com movimento",
"search_page_no_objects": "Nenhuma informação de objeto disponível",
"search_page_no_places": "Nenhuma informação de lugares disponível",
"search_page_no_places": "Nenhuma informação de sítios disponível",
"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": "Lugares",
"search_page_recently_added": "Recently added",
"search_page_person_add_name_dialog_cancel": "Cancelar",
"search_page_person_add_name_dialog_hint": "Nome",
"search_page_person_add_name_dialog_save": "Guardar",
"search_page_person_add_name_dialog_title": "Adicionar um nome",
"search_page_person_add_name_subtitle": "Encontre-os rapidamente pelo nome com a pesquisa",
"search_page_person_add_name_title": "Adicionar um nome",
"search_page_person_edit_name": "Editar nome",
"search_page_places": "Sítios",
"search_page_recently_added": "Adicionado recentemente",
"search_page_screenshots": "Screenshots",
"search_page_selfies": "Selfies",
"search_page_things": "Objetos",
"search_page_videos": "Videos",
"search_page_view_all_button": "View all",
"search_page_your_activity": "Your activity",
"search_page_videos": "Vídeos",
"search_page_view_all_button": "Ver tudo",
"search_page_your_activity": "A sua atividade",
"search_page_your_map": "Your Map",
"search_result_page_new_search_hint": "Nova Busca",
"search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ",
@ -348,10 +349,10 @@
"select_additional_user_for_sharing_page_suggestions": "Sugestões",
"select_user_for_sharing_page_err_album": "Falha ao criar o álbum",
"select_user_for_sharing_page_share_suggestions": "Sugestões",
"server_info_box_app_version": "App Version",
"server_info_box_latest_release": "Latest Version",
"server_info_box_app_version": "Versão da app",
"server_info_box_latest_release": "Última versão",
"server_info_box_server_url": "Server URL",
"server_info_box_server_version": "Server Version",
"server_info_box_server_version": "Versão do servidor",
"setting_image_viewer_help": "O visualizador de detalhes carrega primeiro a miniatura pequena, depois carrega a visualização de tamanho médio (se ativado) e, finalmente, carrega o original (se ativado).",
"setting_image_viewer_original_subtitle": "Ative para carregar a imagem original em resolução total (grande!). Desative para reduzir o uso de dados (na rede e no cache do dispositivo).",
"setting_image_viewer_original_title": "Carregar imagem original",
@ -381,65 +382,65 @@
"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_action_error": "Erro ao sair/remover do álbum",
"shared_album_section_people_action_leave": "Remover utilizador do álbum",
"shared_album_section_people_action_remove_user": "Remover utilizador do álbum",
"shared_album_section_people_owner_label": "Owner",
"shared_album_section_people_title": "PEOPLE",
"share_dialog_preparing": "Preparando...",
"shared_link_app_bar_title": "Shared Links",
"shared_link_app_bar_title": "Links partilhados",
"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_allow_download": "Permitir que um utilizador público descarregue",
"shared_link_edit_allow_upload": "Permitir que um utilizador público carregue",
"shared_link_edit_app_bar_title": "Edit link",
"shared_link_edit_change_expiry": "Change expiration time",
"shared_link_edit_change_expiry": "Alterar o prazo de validade",
"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_day": "1 dia",
"shared_link_edit_expire_after_option_days": "{} dias",
"shared_link_edit_expire_after_option_hour": "1 hora",
"shared_link_edit_expire_after_option_hours": "{} horas",
"shared_link_edit_expire_after_option_minute": "1 minuto",
"shared_link_edit_expire_after_option_minutes": "{} minutos",
"shared_link_edit_expire_after_option_never": "Never",
"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_edit_show_meta": "Mostrar metadados",
"shared_link_edit_submit_button": "Atualizar link",
"shared_link_empty": "Não tem links partilhados",
"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_day": "Expira em {} dia",
"shared_link_expires_days": "Expira em {} dias",
"shared_link_expires_hour": "Expira em {} hora",
"shared_link_expires_hours": "Expira em {} horas",
"shared_link_expires_minute": "Expira em {} minuto",
"shared_link_expires_minutes": "Expires in {} minutes",
"shared_link_expires_never": "Expires ∞",
"shared_link_expires_second": "Expires in {} second",
"shared_link_expires_second": "Expira em {} segundo",
"shared_link_expires_seconds": "Expires in {} seconds",
"shared_link_info_chip_download": "Download",
"shared_link_info_chip_download": "Descarregar",
"shared_link_info_chip_metadata": "EXIF",
"shared_link_info_chip_upload": "Upload",
"shared_link_manage_links": "Manage Shared links",
"shared_link_manage_links": "Gerir links partilhados",
"share_done": "Done",
"share_invite": "Convidar para álbum",
"sharing_page_album": "Álbuns compartilhados",
"sharing_page_description": "Criar álbuns compartilhados para compartilhas fotos e vídeos com pessoas na sua rede.",
"sharing_page_album": "Álbuns partilhados",
"sharing_page_description": "Crie álbuns partilhados para partilhar fotografias e vídeos com pessoas da sua rede.",
"sharing_page_empty_list": "LISTA VAZIA",
"sharing_silver_appbar_create_shared_album": "Criar um álgum compartilhado",
"sharing_silver_appbar_create_shared_album": "Criar álbum partilhado",
"sharing_silver_appbar_shared_links": "Shared links",
"sharing_silver_appbar_share_partner": "Compartilhar com parceiro",
"sharing_silver_appbar_share_partner": "Partilhar com parceiro",
"tab_controller_nav_library": "Biblioteca",
"tab_controller_nav_photos": "Fotos",
"tab_controller_nav_search": "Buscar",
"tab_controller_nav_sharing": "Compartilhando",
"tab_controller_nav_search": "Procurar",
"tab_controller_nav_sharing": "Partilhar",
"theme_setting_asset_list_storage_indicator_title": "Mostrar indicador de armazenamento em blocos de ativos",
"theme_setting_asset_list_tiles_per_row_title": "Número de itens por linha ({})",
"theme_setting_dark_mode_switch": "Modo escuro",
@ -467,11 +468,11 @@
"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": "Need Context",
"version_announcement_overlay_ack": "Aceitar",
"version_announcement_overlay_release_notes": "notas de lançamento",
"version_announcement_overlay_text_1": "Olá, há um novo lançamento de",
"version_announcement_overlay_text_2": "por favor, tome o seu tempo para visitar o",
"version_announcement_overlay_text_3": "e certifique-se de que a configuração do docker-compose e do .env estejam atualizadas para evitar configurações incorretas, especialmente se você usar o WatchTower ou qualquer mecanismo que lide com a atualização automática do aplicativo do servidor.",
"version_announcement_overlay_text_3": "e certifique-se de que a configuração do docker-compose e do .env estejam atualizadas para evitar configurações incorretas, especialmente se usar o WatchTower ou qualquer mecanismo que lide com a atualização automática do servidor.",
"version_announcement_overlay_title": "Nova versão do servidor disponível \uD83C\uDF89",
"viewer_remove_from_stack": "Remove from Stack",
"viewer_stack_use_as_main_asset": "Use as Main Asset",

View File

@ -1,12 +1,12 @@
{
"action_common_cancel": "Cancel",
"action_common_update": "Update",
"action_common_cancel": "Отмена",
"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_prefer_remote_subtitle": "Некоторые устройства очень медленно загружают предпросмотр объектов, находящихся на устройстве. Активируйте эту настройку, чтобы вместо них загружались изображени с сервера.",
"advanced_settings_prefer_remote_subtitle": "Некоторые устройства очень медленно загружают предпросмотр объектов, находящихся на устройстве. Активируйте эту настройку, чтобы вместо них загружались изображения с сервера.",
"advanced_settings_prefer_remote_title": "Предпочитать фото на сервере",
"advanced_settings_self_signed_ssl_subtitle": "Пропускает проверку сертификата SSL для конечной точки сервера. Требуется для самоподписанных сертификатов.",
"advanced_settings_self_signed_ssl_subtitle": "Пропускает проверку SSL-сертификата сервера. Требуется для самоподписанных сертификатов.",
"advanced_settings_self_signed_ssl_title": "Разрешить самоподписанные SSL-сертификаты",
"advanced_settings_tile_subtitle": "Расширенные настройки пользователя",
"advanced_settings_tile_title": "Расширенные",
@ -35,14 +35,14 @@
"app_bar_signout_dialog_title": "Выйти из системы",
"archive_page_no_archived_assets": "В архиве сейчас пусто",
"archive_page_title": "Архив ({})",
"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_action_delete_err_read_only": "Невозможно удалить объект(ы) только для чтения, пропуск...",
"asset_action_share_err_offline": "Невозможно получить оффлайн-объект(ы), пропуск...",
"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_settings_subtitle": "Настройки макета сетки фотографий",
"asset_list_settings_subtitle": "Настройка макета сетки фотографий",
"asset_list_settings_title": "Сетка фотографий",
"backup_album_selection_page_albums_device": "Альбомов на устройстве ({})",
"backup_album_selection_page_albums_tap": "Нажмите, чтобы включить, нажмите дважды, чтобы исключить",
@ -65,7 +65,7 @@
"backup_controller_page_background_battery_info_link": "Показать как",
"backup_controller_page_background_battery_info_message": "Для наилучшего фонового резервного копирования отключите любые настройки оптимизации батареи, ограничивающие фоновую активность для Immich.\n\nПоскольку это зависит от устройства, найдите необходимую информацию для производителя вашего устройства.",
"backup_controller_page_background_battery_info_ok": "ОК",
"backup_controller_page_background_battery_info_title": "\nОптимизация батареи",
"backup_controller_page_background_battery_info_title": "Оптимизация батареи",
"backup_controller_page_background_charging": "Только во время зарядки",
"backup_controller_page_background_configure_error": "Не удалось настроить фоновую службу",
"backup_controller_page_background_delay": "Отложить резервное копирование новых объектов: {}",
@ -112,8 +112,8 @@
"cache_settings_clear_cache_button": "Очистить кэш",
"cache_settings_clear_cache_button_title": "Очищает кэш приложения. Это значительно повлияет на производительность приложения, до тех пор, пока кэш не будет перестроен заново.",
"cache_settings_duplicated_assets_clear_button": "ОЧИСТИТЬ",
"cache_settings_duplicated_assets_subtitle": "Photos and videos that are black listed by the app",
"cache_settings_duplicated_assets_title": "Дублированные ресурсы",
"cache_settings_duplicated_assets_subtitle": "Фото и видео, занесенные приложением в черный список",
"cache_settings_duplicated_assets_title": "Дублирующиеся объекты ({})",
"cache_settings_image_cache_size": "Размер кэша изображений ({} объектов)",
"cache_settings_statistics_album": "Миниатюры библиотеки",
"cache_settings_statistics_assets": "{} объектов ({})",
@ -140,19 +140,19 @@
"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": "\nСоздать новый альбом",
"control_bottom_app_bar_create_new_album": "Создать новый альбом",
"control_bottom_app_bar_delete": "Удалить",
"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": "Изменить местоположение",
"control_bottom_app_bar_edit_time": "Изменить дату и время",
"control_bottom_app_bar_favorite": "Избранное",
"control_bottom_app_bar_delete_from_immich": "Удалить из Immich\n",
"control_bottom_app_bar_delete_from_local": "Удалить с устройства",
"control_bottom_app_bar_edit_location": "Редактировать местоположение",
"control_bottom_app_bar_edit_time": "Редактировать дату и время",
"control_bottom_app_bar_favorite": "В избранное",
"control_bottom_app_bar_share": "Поделиться",
"control_bottom_app_bar_share_to": "Поделиться",
"control_bottom_app_bar_stack": "Стек",
"control_bottom_app_bar_trash_from_immich": "Move to Trash",
"control_bottom_app_bar_trash_from_immich": "Переместить в корзину",
"control_bottom_app_bar_unarchive": "Восстановить",
"control_bottom_app_bar_unfavorite": "Исключить из избранного",
"control_bottom_app_bar_unfavorite": "Удалить из избранного",
"control_bottom_app_bar_upload": "Загрузить",
"create_album_page_untitled": "Без названия",
"create_shared_album_page_create": "Создать",
@ -165,26 +165,27 @@
"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": "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_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": "Delete Anyway",
"delete_dialog_ok_force": "Все равно удалить",
"delete_dialog_title": "Удалить навсегда",
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only",
"delete_local_dialog_ok_force": "Delete Anyway",
"delete_local_dialog_ok_backed_up_only": "Удалить только резервные копии",
"delete_local_dialog_ok_force": "Все равно удалить",
"delete_shared_link_dialog_content": "Вы уверены, что хотите удалить эту общую ссылку?",
"delete_shared_link_dialog_title": "Удалить общую ссылку",
"description_input_hint_text": "Добавить описание...",
"description_input_submit_error": "Не удалось обновить описание, проверьте логи, чтобы узнать причину",
"edit_date_time_dialog_date_time": "Date and Time",
"edit_date_time_dialog_timezone": "Timezone",
"edit_location_dialog_title": "Location",
"edit_date_time_dialog_date_time": "Дата и время",
"edit_date_time_dialog_timezone": "Часовой пояс",
"edit_location_dialog_title": "Местоположение",
"exif_bottom_sheet_description": "Добавить описание...",
"exif_bottom_sheet_details": "ПОДРОБНОСТИ",
"exif_bottom_sheet_location": "МЕСТОПОЛОЖЕНИЕ",
"exif_bottom_sheet_location": "Местоположение",
"exif_bottom_sheet_location_add": "Добавить местоположение",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Ведутся работы",
"experimental_settings_new_asset_list_title": "Включить экспериментальную сетку фотографий",
"experimental_settings_subtitle": "Используйте на свой страх и риск!",
@ -194,39 +195,39 @@
"home_page_add_to_album_conflicts": "Добавлено {added} объектов в альбом {album}. Объекты {failed} уже есть в альбоме.",
"home_page_add_to_album_err_local": "Пока нельзя добавлять локальные объекты в альбомы, пропускаем",
"home_page_add_to_album_success": "Добавлено {added} объектов в альбом {album}.",
"home_page_album_err_partner": "Пока не удается добавить партнерские активы в альбом, пропуск...",
"home_page_album_err_partner": "Пока не удается добавить объекты партнера в альбом, пропуск...",
"home_page_archive_err_local": "Пока невозможно добавить локальные объекты в архив, пропускаем",
"home_page_archive_err_partner": "Невозможно архивировать активы партнеров, пропуск...",
"home_page_archive_err_partner": "Невозможно архивировать объекты партнера, пропуск...",
"home_page_building_timeline": "Построение временной шкалы",
"home_page_delete_err_partner": "Невозможно удалить активы партнера, пропуск...",
"home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping",
"home_page_favorite_err_local": "Пока не удается добавить в избранное локальные объекты, пропускаем",
"home_page_favorite_err_partner": "Пока не удается выделить партнерские активы, пропуск...",
"home_page_delete_err_partner": "Невозможно удалить объекты партнера, пропуск...",
"home_page_delete_remote_err_local": "Локальные объект(ы) уже в процессе удаления с сервера, пропуск...",
"home_page_favorite_err_local": "Пока не удается добавить в избранное локальные объекты, пропуск...",
"home_page_favorite_err_partner": "Пока не удается добавить в избранное объекты партнера, пропуск...",
"home_page_first_time_notice": "Если вы используете приложение впервые, убедитесь, что вы выбрали резервный(е) альбом(ы), чтобы временная шкала могла заполнить фотографии и видео в альбоме(ах).",
"home_page_share_err_local": "Невозможно поделиться локальными данными по ссылке, пропуск...",
"home_page_upload_err_limit": "Вы можете выгрузить максимум 30 файлов за раз",
"image_viewer_page_state_provider_download_error": "Ошибка загрузки",
"image_viewer_page_state_provider_download_success": "Успешно загружено",
"image_viewer_page_state_provider_share_error": "Ошибка при публикации",
"image_viewer_page_state_provider_share_error": "Ошибка общего доступа",
"library_page_albums": "Альбомы",
"library_page_archive": "Архив",
"library_page_device_albums": "Альбомы на устройстве",
"library_page_favorites": "Избранное",
"library_page_new_album": "Новый альбом",
"library_page_sharing": "Общие",
"library_page_sort_asset_count": "Number of assets",
"library_page_sort_created": "По новизне",
"library_page_sort_asset_count": "Количество объектов",
"library_page_sort_created": "Недавно созданные",
"library_page_sort_last_modified": "Последнее изменение",
"library_page_sort_most_oldest_photo": "Oldest photo",
"library_page_sort_most_recent_photo": "Последняя фотография",
"library_page_sort_title": "По названию альбома",
"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",
"library_page_sort_most_oldest_photo": "Самые старые фото",
"library_page_sort_most_recent_photo": "Самые последние фото",
"library_page_sort_title": "Название альбома",
"location_picker_choose_on_map": "Выбрать на карте",
"location_picker_latitude": "Широта",
"location_picker_latitude_error": "Укажите правильную широту",
"location_picker_latitude_hint": "Укажите широту",
"location_picker_longitude": "Долгота",
"location_picker_longitude_error": "Укажите правильную долготу",
"location_picker_longitude_hint": "Укажите долготу",
"login_disabled": "Вход отключен",
"login_form_api_exception": "Ошибка при попытке взаимодействия с сервером. Проверьте URL-адрес до него и попробуйте еще раз.",
"login_form_back_button_text": "Назад",
@ -252,35 +253,35 @@
"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": "Да",
"map_location_picker_page_use_location": "Use this location",
"map_location_picker_page_use_location": "Это местоположение",
"map_location_service_disabled_content": "Для отображения объектов в данном месте необходимо включить службу определения местоположения. Хотите включить ее сейчас?",
"map_location_service_disabled_title": "Служба определения местоположения отключена",
"map_no_assets_in_bounds": "Нет фотографий в этой области",
"map_no_location_permission_content": "Для отображения объектов из текущего местоположения необходимо разрешение на определение местоположения. Хотите ли вы разрешить его сейчас?",
"map_no_location_permission_title": "Доступ к местоположению отклонен",
"map_settings_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_date_range_option_all": "Все",
"map_settings_date_range_option_day": "Прошлые 24 часа",
"map_settings_date_range_option_days": "Прошлые {} дней",
"map_settings_date_range_option_year": "Прошлый год",
"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_only_relative_range": "Период времени",
"map_settings_only_show_favorites": "Показать только избранное",
"map_settings_theme_settings": "Map Theme",
"map_settings_theme_settings": "Тема карты",
"map_zoom_to_see_photos": "Уменьшение масштаба для просмотра фотографий",
"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": "Cannot edit location of read only asset(s), skipping",
"multiselect_grid_edit_date_time_err_read_only": "Невозможно редактировать дату объектов только для чтения, пропуск...",
"multiselect_grid_edit_gps_err_read_only": "Невозможно редактировать местоположение объектов только для чтения, пропуск...",
"notification_permission_dialog_cancel": "Отмена",
"notification_permission_dialog_content": "Чтобы включить уведомления, перейдите в «Настройки» и выберите «Разрешить».",
"notification_permission_dialog_settings": "Настройки",
@ -318,7 +319,7 @@
"profile_drawer_sign_out": "Выйти",
"profile_drawer_trash": "Корзина",
"recently_added_page_title": "Недавно добавленные",
"scaffold_body_error_occurred": "Error occurred",
"scaffold_body_error_occurred": "Возникла ошибка",
"search_bar_hint": "Поиск фотографий",
"search_page_categories": "Категории",
"search_page_favorites": "Избранное",
@ -332,31 +333,31 @@
"search_page_person_add_name_dialog_title": "Добавить имя",
"search_page_person_add_name_subtitle": "Быстро найдите их по имени с помощью поиска",
"search_page_person_add_name_title": "Добавить имя",
"search_page_person_edit_name": "Изменить имя",
"search_page_person_edit_name": "Редактировать имя",
"search_page_places": "Места",
"search_page_recently_added": "Недавно добавленные",
"search_page_screenshots": "Скриншоты",
"search_page_screenshots": "Снимки экрана",
"search_page_selfies": "Селфи",
"search_page_things": "Предметы",
"search_page_videos": "Видео",
"search_page_view_all_button": "Посмотреть все",
"search_page_your_activity": "Ваша активность",
"search_page_your_activity": "Ваши действия",
"search_page_your_map": "Ваша карта",
"search_result_page_new_search_hint": "Новый поиск",
"search_suggestion_list_smart_search_hint_1": "Интеллектуальный поиск включен по умолчанию, для поиска метаданных используйте специальный синтаксис",
"search_suggestion_list_smart_search_hint_2": "m:ваш-запрос",
"search_suggestion_list_smart_search_hint_2": "m:ваш-поисковый-запрос",
"select_additional_user_for_sharing_page_suggestions": "Предложения",
"select_user_for_sharing_page_err_album": "\nНе удалось создать альбом",
"select_user_for_sharing_page_err_album": "Не удалось создать альбом",
"select_user_for_sharing_page_share_suggestions": "Предложения",
"server_info_box_app_version": "Версия приложения",
"server_info_box_latest_release": "Крайняя версия",
"server_info_box_latest_release": "Последняя версия",
"server_info_box_server_url": "URL сервера",
"server_info_box_server_version": "Версия сервера",
"setting_image_viewer_help": "Средство просмотра деталей сначала загружает маленькую миниатюру, затем загружает предварительный просмотр среднего размера (если включено) и, наконец, загружает оригинал (если включено).",
"setting_image_viewer_original_subtitle": "Включите загрузку оригинального изображения в полном разрешении (большое!). Отключите, чтобы уменьшить объем данных (как в сети, так и в кеше устройства).",
"setting_image_viewer_original_title": "Загрузить исходное изображение",
"setting_image_viewer_preview_subtitle": "Включите загрузку изображения среднего разрешения. Отключите, чтобы загрузить оригинал напрямую или использовать только миниатюру.",
"setting_image_viewer_preview_title": "Загрузить изображение для предварительного просмотра",
"setting_image_viewer_help": "Полноэкранный просмотрщик сначала загружает изображение для предпросмотра в низком разрешении, затем загружает изображение в уменьшенном разрешении относительно оригинала (если включено) и в конце концов загружает оригинал (если включено).",
"setting_image_viewer_original_subtitle": "Включите для загрузки исходного изображения в полном разрешении (большое!).\nОтключите, чтобы уменьшить объем данных (как сети, так и кэша устройства).",
"setting_image_viewer_original_title": "Загружать исходное изображение",
"setting_image_viewer_preview_subtitle": "Включите для загрузки изображения среднего разрешения.\nОтключите, чтобы загружать оригинал напрямую или использовать только миниатюру.",
"setting_image_viewer_preview_title": "Загружать изображение для предварительного просмотра",
"setting_notifications_notify_failures_grace_period": "Уведомлять об ошибках фонового резервного копирования: {}",
"setting_notifications_notify_hours": "{} часов",
"setting_notifications_notify_immediately": "немедленно",
@ -365,7 +366,7 @@
"setting_notifications_notify_seconds": "{} секунд",
"setting_notifications_single_progress_subtitle": "Подробная информация о ходе загрузки для каждого объекта",
"setting_notifications_single_progress_title": "Показать ход выполнения фонового резервного копирования",
"setting_notifications_subtitle": "Настроить параметры уведомлений",
"setting_notifications_subtitle": "Настройка параметров уведомлени",
"setting_notifications_title": "Уведомления",
"setting_notifications_total_progress_subtitle": "Общий прогресс загрузки (выполнено/всего объектов)",
"setting_notifications_total_progress_title": "Показать общий прогресс фонового резервного копирования",
@ -375,61 +376,61 @@
"share_add_photos": "Добавить фото",
"share_add_title": "Добавить название",
"share_create_album": "Создать альбом",
"shared_album_activities_input_disable": "Комментарий отключен",
"shared_album_activities_input_disable": "Комментирование отключено",
"shared_album_activities_input_hint": "Скажите что-нибудь",
"shared_album_activity_remove_content": "Хотите ли Вы удалить это действие?",
"shared_album_activity_remove_title": "Удалить действие",
"shared_album_activity_setting_subtitle": "Предоставьте другим возможность отвечать",
"shared_album_activity_remove_content": "Хотите ли Вы удалить это сообщение?",
"shared_album_activity_remove_title": "Удалить сообщение",
"shared_album_activity_setting_subtitle": "Разрешить другим отвечат",
"shared_album_activity_setting_title": "Комментарии и лайки",
"shared_album_section_people_action_error": "Error leaving/removing from album",
"shared_album_section_people_action_error": "Ошибка при выходе/удалении из альбома",
"shared_album_section_people_action_leave": "Удалить пользователя из альбома",
"shared_album_section_people_action_remove_user": "Удалить пользователя из альбома",
"shared_album_section_people_owner_label": "Owner",
"shared_album_section_people_title": "PEOPLE",
"shared_album_section_people_owner_label": "Владелец",
"shared_album_section_people_title": "ЛЮДИ",
"share_dialog_preparing": "Подготовка...",
"shared_link_app_bar_title": "Общие ссылки",
"shared_link_clipboard_copied_massage": "Скопировано в буфер обмена",
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
"shared_link_create_app_bar_title": "Создать ссылку для совместного использования",
"shared_link_create_error": "Error while creating shared link",
"shared_link_create_info": "Позволить любому человеку, имеющему ссылку, увидеть выбранную фотографию (фотографии)",
"shared_link_clipboard_text": "Ссылка: {}\nПароль: {}",
"shared_link_create_app_bar_title": "Создать ссылку общего доступа",
"shared_link_create_error": "Ошибка при создании общей ссылки",
"shared_link_create_info": "Разрешить всем, у кого есть ссылка, просматривать выбранные фото",
"shared_link_create_submit_button": "Создать ссылку",
"shared_link_edit_allow_download": "Разрешить публичному пользователю скачивать",
"shared_link_edit_allow_download": "Разрешить публичному пользователю скачивать файлы",
"shared_link_edit_allow_upload": "Разрешить публичному пользователю загружать файлы",
"shared_link_edit_app_bar_title": "Редактировать ссылку",
"shared_link_edit_change_expiry": "Изменить срок действия доступа",
"shared_link_edit_description": "Описание",
"shared_link_edit_description_hint": "Введите описание совместного доступа",
"shared_link_edit_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_description_hint": "Введите описание для общего доступа",
"shared_link_edit_expire_after": "Истекает через",
"shared_link_edit_expire_after_option_day": "1 день",
"shared_link_edit_expire_after_option_days": "{} дней",
"shared_link_edit_expire_after_option_hour": "1 час",
"shared_link_edit_expire_after_option_hours": "{} часов",
"shared_link_edit_expire_after_option_minute": "1 минуту",
"shared_link_edit_expire_after_option_minutes": "{} минут",
"shared_link_edit_expire_after_option_never": "Никогда",
"shared_link_edit_password": "Пароль",
"shared_link_edit_password_hint": "Введите пароль общего доступа",
"shared_link_edit_show_meta": "Показать метаданные",
"shared_link_edit_password_hint": "Введите пароль для общего доступа",
"shared_link_edit_show_meta": "Показывать метаданные",
"shared_link_edit_submit_button": "Обновить ссылку",
"shared_link_empty": "У вас нет общих ссылок",
"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_error_server_url_fetch": "Невозможно запросить URL с сервера",
"shared_link_expired": "Срок действия истек",
"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": "Expires ∞",
"shared_link_expires_second": "Expires in {} second",
"shared_link_expires_never": "Истекает ∞",
"shared_link_expires_second": "Истекает через {} секунду",
"shared_link_expires_seconds": "Истекает через {} секунд",
"shared_link_info_chip_download": "Download",
"shared_link_info_chip_download": "Скачать",
"shared_link_info_chip_metadata": "EXIF",
"shared_link_info_chip_upload": "Upload",
"shared_link_info_chip_upload": "Загрузить",
"shared_link_manage_links": "Управление общими ссылками",
"share_done": "Выполнено",
"share_invite": "\nПригласить в альбом",
"share_done": "Готово",
"share_invite": "Пригласить в альбом",
"sharing_page_album": "Общие альбомы",
"sharing_page_description": "Создавайте общие альбомы, чтобы делиться фотографиями и видео с людьми в вашей сети.",
"sharing_page_empty_list": "ПУСТОЙ СПИСОК",
@ -443,10 +444,10 @@
"theme_setting_asset_list_storage_indicator_title": "Показать индикатор хранилища на плитках объектов",
"theme_setting_asset_list_tiles_per_row_title": "Количество объектов в строке ({})",
"theme_setting_dark_mode_switch": "Тёмная тема",
"theme_setting_image_viewer_quality_subtitle": "Настройка качества детального просмотра изображения",
"theme_setting_image_viewer_quality_subtitle": "Настройка качества просмотра полноэкранных изображения",
"theme_setting_image_viewer_quality_title": "Качество просмотра изображений",
"theme_setting_system_theme_switch": "Автоматически (Как в системе)",
"theme_setting_theme_subtitle": "Выберите настройки темы приложения",
"theme_setting_system_theme_switch": "Автоматически (как в системе)",
"theme_setting_theme_subtitle": "Настройка темы приложения",
"theme_setting_theme_title": "Тема",
"theme_setting_three_stage_loading_subtitle": "Трехэтапная загрузка может повысить производительность загрузки, но вызывает значительно более высокую нагрузку на сеть",
"theme_setting_three_stage_loading_title": "Включить трехэтапную загрузку",
@ -454,10 +455,10 @@
"trash_page_delete": "Удалить",
"trash_page_delete_all": "Удалить все",
"trash_page_empty_trash_btn": "Очистить корзину",
"trash_page_empty_trash_dialog_content": "Вы хотите очистить свою корзину? Эти объекты будут навсегда удалены из Immich",
"trash_page_empty_trash_dialog_content": "Вы хотите очистить свою корзину? Эти объекты будут навсегда удалены из Immich.",
"trash_page_empty_trash_dialog_ok": "ОК",
"trash_page_info": "Удаленные элементы будут окончательно удалены через {} дней",
"trash_page_no_assets": "Отсутствие удаленных объектов",
"trash_page_no_assets": "Удаленные объекты отсутсвуют",
"trash_page_restore": "Восстановить",
"trash_page_restore_all": "Восстановить все",
"trash_page_select_assets_btn": "Выбранные объекты",
@ -474,6 +475,6 @@
"version_announcement_overlay_text_3": " и убедитесь, что ваши настройки docker-compose и .env обновлены, чтобы предотвратить любые неправильные настройки, особенно если вы используете WatchTower или любой другой механизм, который обрабатывает обновление вашего серверного приложения автоматически.",
"version_announcement_overlay_title": "Доступна новая версия сервера \uD83C\uDF89",
"viewer_remove_from_stack": "Удалить из стека",
"viewer_stack_use_as_main_asset": "Использование в качестве основного объекта",
"viewer_stack_use_as_main_asset": "Использовать в качестве основного объекта",
"viewer_unstack": "Разобрать стек"
}

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "PODROBNOSTI",
"exif_bottom_sheet_location": "LOKALITA",
"exif_bottom_sheet_location_add": "Nastaviť polohu",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Prebiehajúca práca",
"experimental_settings_new_asset_list_title": "Povolenie experimentálnej mriežky fotografií",
"experimental_settings_subtitle": "Používajte na vlastné riziko!",

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "DETAILS",
"exif_bottom_sheet_location": "LOCATION",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Work in progress",
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Use at your own risk!",

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "DETALJI",
"exif_bottom_sheet_location": "LOKACIJA",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "U izradi",
"experimental_settings_new_asset_list_title": "Aktiviraj eksperimentalni mrežni prikaz fotografija",
"experimental_settings_subtitle": "Koristiti na sopstvenu odgovornost!",

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "DETAILS",
"exif_bottom_sheet_location": "LOCATION",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Work in progress",
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Use at your own risk!",

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "DETALJER",
"exif_bottom_sheet_location": "PLATS",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PEOPLE",
"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!",

View File

@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "รายละเอียด",
"exif_bottom_sheet_location": "ตำแหน่ง",
"exif_bottom_sheet_location_add": "เพิ่มตำแหน่ง",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "กำลังพัฒนา",
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Use at your own risk!",

View File

@ -1,13 +1,13 @@
{
"action_common_cancel": "Cancel",
"action_common_update": "Update",
"action_common_cancel": "Скасувати",
"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_prefer_remote_subtitle": "Деякі пристрої вельми повільно завантажують мініатюри із елементів на пристрої. Активуйте для завантаження віддалених мініатюр натомість.",
"advanced_settings_prefer_remote_title": "Перевага віддаленим зображенням",
"advanced_settings_self_signed_ssl_subtitle": "Пропускає SSL-сертифікат для точки доступу сервера. Потрібне для власноруч підписаних сертифікатів.",
"advanced_settings_self_signed_ssl_title": "Дозволити власноруч підписані SSL-сертифікати",
"advanced_settings_self_signed_ssl_subtitle": "Пропускає перевірку SSL-сертифіката сервера. Потрібне для самопідписаних сертифікатів.",
"advanced_settings_self_signed_ssl_title": "Дозволити самопідписані SSL-сертифікати",
"advanced_settings_tile_subtitle": "Розширені користувацькі налаштування",
"advanced_settings_tile_title": "Розширені",
"advanced_settings_troubleshooting_subtitle": "Увімкніть додаткові функції для усунення несправностей",
@ -26,17 +26,17 @@
"album_viewer_appbar_share_err_title": "Не вдалося змінити назву альбому",
"album_viewer_appbar_share_leave": "Вийти з альбому",
"album_viewer_appbar_share_remove": "Видалити з альбому",
"album_viewer_appbar_share_to": "Share To",
"album_viewer_appbar_share_to": "Поділитися",
"album_viewer_page_share_add_users": "Додати користувачів",
"all_people_page_title": "Люди",
"all_videos_page_title": "Відео",
"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",
"app_bar_signout_dialog_content": "Ви впевнені, що бажаєте вийти з аккаунта?",
"app_bar_signout_dialog_ok": "Так",
"app_bar_signout_dialog_title": "Вийти з аккаунта",
"archive_page_no_archived_assets": "Немає архівних елементів",
"archive_page_title": "Архів ({})",
"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_action_delete_err_read_only": "Неможливо видалити елемент(и) лише для читання, пропущено",
"asset_action_share_err_offline": "Неможливо отримати оффлайн-елемент(и), пропущено",
"asset_list_layout_settings_dynamic_layout_title": "Динамічне компонування",
"asset_list_layout_settings_group_automatically": "Автоматично",
"asset_list_layout_settings_group_by": "Групувати елементи по",
@ -50,7 +50,7 @@
"backup_album_selection_page_select_albums": "Оберіть альбоми",
"backup_album_selection_page_selection_info": "Інформація про обране",
"backup_album_selection_page_total_assets": "Загальна кількість унікальних елементів",
"backup_all": "Все",
"backup_all": "Усі",
"backup_background_service_backup_failed_message": "Не вдалося зробити резервну копію елементів. Повторюю...",
"backup_background_service_connection_failed_message": "Не вдалося зв'язатися із сервером. Повторюю...",
"backup_background_service_current_upload_notification": "Завантажується {}",
@ -65,7 +65,7 @@
"backup_controller_page_background_battery_info_link": "Покажіть мені як",
"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_battery_info_title": "Оптимізація батареї",
"backup_controller_page_background_charging": "Лише під час заряджання",
"backup_controller_page_background_configure_error": "Не вдалося налаштувати фоновий сервіс",
"backup_controller_page_background_delay": "Затримка перед резервним копіюванням нових елементів: {}",
@ -111,9 +111,9 @@
"cache_settings_album_thumbnails": "Мініатюри сторінок бібліотеки ({} елементи)",
"cache_settings_clear_cache_button": "Очистити кеш",
"cache_settings_clear_cache_button_title": "Очищає кеш програми. Це суттєво знизить продуктивність програми, доки кеш не буде перебудовано.",
"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_duplicated_assets_clear_button": "ОЧИСТИТИ",
"cache_settings_duplicated_assets_subtitle": "Фото та відео, занесені додатком у чорний список",
"cache_settings_duplicated_assets_title": "Дубльовані елементи ({})",
"cache_settings_image_cache_size": "Розмір кешованих зображень ({} елементи)",
"cache_settings_statistics_album": "Бібліотечні мініатюри",
"cache_settings_statistics_assets": "{} елементи ({})",
@ -123,16 +123,16 @@
"cache_settings_statistics_title": "Використання кешу",
"cache_settings_subtitle": "Контролює кешування у мобільному застосунку",
"cache_settings_thumbnail_size": "Розмір кешованих мініатюр ({} елементи)",
"cache_settings_tile_subtitle": "Control the local storage behaviour",
"cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Налаштування Кешування",
"cache_settings_tile_subtitle": "Керування поведінкою локального сховища",
"cache_settings_tile_title": "Локальне сховище",
"cache_settings_title": "Налаштування кешування",
"change_password_form_confirm_password": "Підтвердити пароль",
"change_password_form_description": "Привіт {name},\n\nВи або або вперше входите у систему, або було зроблено запит на зміну вашого пароля. \nВведіть ваш новий пароль.",
"change_password_form_new_password": "Новий Пароль",
"change_password_form_new_password": "Новий пароль",
"change_password_form_password_mismatch": "Паролі не співпадають",
"change_password_form_reenter_new_password": "Повторіть Новий Пароль",
"change_password_form_reenter_new_password": "Повторіть новий пароль",
"common_add_to_album": "Додати в альбом",
"common_change_password": "Змінити Пароль",
"common_change_password": "Змінити пароль",
"common_create_new_album": "Створити новий альбом",
"common_server_error": "Будь ласка, перевірте з'єднання, переконайтеся, що сервер доступний і версія програми/сервера сумісна.",
"common_shared": "Спільні",
@ -142,18 +142,18 @@
"control_bottom_app_bar_archive": "Архівувати",
"control_bottom_app_bar_create_new_album": "Створити новий альбом",
"control_bottom_app_bar_delete": "Видалити",
"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": "Уподобати",
"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": "Редагувати дату та час",
"control_bottom_app_bar_favorite": "До улюблених",
"control_bottom_app_bar_share": "Поділитися",
"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_share_to": "Поділитися",
"control_bottom_app_bar_stack": "Стек",
"control_bottom_app_bar_trash_from_immich": "Перемістити до кошику",
"control_bottom_app_bar_unarchive": "Розархівувати",
"control_bottom_app_bar_unfavorite": "Unfavorite",
"control_bottom_app_bar_upload": "Upload",
"control_bottom_app_bar_unfavorite": "Видалити з улюблених",
"control_bottom_app_bar_upload": "Завантажити",
"create_album_page_untitled": "Без назви",
"create_shared_album_page_create": "Створити",
"create_shared_album_page_share": "Поділитися",
@ -165,26 +165,27 @@
"daily_title_text_date_year": "E, MMM dd, yyyy",
"date_format": "E, LLL d, y • h:mm a",
"delete_dialog_alert": "Ці елементи будуть остаточно видалені з Immich та вашого пристрою",
"delete_dialog_alert_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_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": "Delete Anyway",
"delete_dialog_ok_force": "Все одно видалити",
"delete_dialog_title": "Видалити остаточно",
"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",
"delete_local_dialog_ok_backed_up_only": "Видалити лише резервні копії",
"delete_local_dialog_ok_force": "Все одно видалити",
"delete_shared_link_dialog_content": "Ви впевнені, що хочете видалити це спільне посилання?",
"delete_shared_link_dialog_title": "Видалити спільне посилання",
"description_input_hint_text": "Додати опис...",
"description_input_submit_error": "Помилка оновлення опису, перевірте логи для подробиць",
"edit_date_time_dialog_date_time": "Date and Time",
"edit_date_time_dialog_timezone": "Timezone",
"edit_location_dialog_title": "Location",
"edit_date_time_dialog_date_time": "Дата і час",
"edit_date_time_dialog_timezone": "Часовий пояс",
"edit_location_dialog_title": "Місцезнаходження",
"exif_bottom_sheet_description": "Додати опис...",
"exif_bottom_sheet_details": "ПОДРОБИЦІ",
"exif_bottom_sheet_location": "МІСЦЕ",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_location_add": "Додати місцезнаходження",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "В розробці",
"experimental_settings_new_asset_list_title": "Експериментальний макет знімків",
"experimental_settings_subtitle": "На власний ризик!",
@ -194,42 +195,42 @@
"home_page_add_to_album_conflicts": "Додано {added} елементів у альбом {album}. {failed} елементів вже було в альбомі.",
"home_page_add_to_album_err_local": "Неможливо додати локальні елементи до альбомів, пропущено",
"home_page_add_to_album_success": "Додано {added} елементів у альбом {album}.",
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
"home_page_album_err_partner": "Поки що не вдається додати елементи партнера до альбому, пропущено",
"home_page_archive_err_local": "Поки що неможливо заархівувати локальні елементи, пропущено",
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
"home_page_archive_err_partner": "Неможливо архівувати елементи партнера, пропущено",
"home_page_building_timeline": "Побудова хронології",
"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": "Неможливо отримати улюблені локальні елементи, пропущено",
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
"home_page_delete_err_partner": "Неможливо видалити елементи партнера, пропущено",
"home_page_delete_remote_err_local": "Локальні елемент(и) вже в процесі видалення з сервера, пропущено",
"home_page_favorite_err_local": "Поки що не можна додати до улюблених локальні елементи, пропущено",
"home_page_favorite_err_partner": "Поки що не можна додати до улюблених елементи партнера, пропущено",
"home_page_first_time_notice": "Якщо ви вперше користуєтеся програмою, переконайтеся, що ви вибрали альбоми для резервування, щоб могти заповнювати хронологію знімків та відео в альбомах.",
"home_page_share_err_local": "Can not share local assets via link, skipping",
"home_page_share_err_local": "Неможливо поділитися локальними елементами через посилання, пропущено",
"home_page_upload_err_limit": "Можна вантажити не більше 30 елементів водночас, пропущено",
"image_viewer_page_state_provider_download_error": "Помилка завантаження",
"image_viewer_page_state_provider_download_success": "Усіпшно завантажено",
"image_viewer_page_state_provider_share_error": "Share Error",
"image_viewer_page_state_provider_share_error": "Помилка спільного доступу",
"library_page_albums": "Альбоми",
"library_page_archive": "Архів",
"library_page_device_albums": "Альбоми на Пристрої",
"library_page_device_albums": "Альбоми на пристрої",
"library_page_favorites": "Улюблені",
"library_page_new_album": "Новий альбом",
"library_page_sharing": "Спільні",
"library_page_sort_asset_count": "Number of assets",
"library_page_sort_asset_count": "Кількість елементів",
"library_page_sort_created": "Нещодавно створені",
"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_last_modified": "Остання зміна",
"library_page_sort_most_oldest_photo": "Найдавніші фото",
"library_page_sort_most_recent_photo": "Найновіші фото",
"library_page_sort_title": "Назва альбому",
"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",
"location_picker_choose_on_map": "Обрати на мапі",
"location_picker_latitude": "Широта",
"location_picker_latitude_error": "Вкажіть дійсну широту",
"location_picker_latitude_hint": "Вкажіть широту",
"location_picker_longitude": "Довгота",
"location_picker_longitude_error": "Вкажіть дійсну довготу",
"location_picker_longitude_hint": "Вкажіть довготу",
"login_disabled": "Авторизація була відключена",
"login_form_api_exception": "Помилка API. Перевірте адресу сервера і спробуйте знову",
"login_form_back_button_text": "Back",
"login_form_back_button_text": "Назад",
"login_form_button_text": "Увійти",
"login_form_email_hint": "youremail@email.com",
"login_form_endpoint_hint": "http://your-server-ip:port/api",
@ -239,10 +240,10 @@
"login_form_err_invalid_url": "Хибний URL",
"login_form_err_leading_whitespace": "Пробіл на початку",
"login_form_err_trailing_whitespace": "Пробіл в кінці",
"login_form_failed_get_oauth_server_config": "Помилка входу через OAuth, перевірте адресу сервера\n",
"login_form_failed_get_oauth_server_disable": "OAuth недоступний на цьому сервері\n",
"login_form_failed_get_oauth_server_config": "Помилка входу через OAuth, перевірте адресу сервера",
"login_form_failed_get_oauth_server_disable": "OAuth недоступний на цьому сервері",
"login_form_failed_login": "Помилка входу, перевірте URL-адресу сервера, електронну пошту та пароль",
"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_handshake_exception": "Виняток рукостискання з сервером. Увімкніть підтримку самопідписаного сертифіката в налаштуваннях, якщо ви використовуєте самопідписаний сертифікат.",
"login_form_label_email": "Електронна пошта",
"login_form_label_password": "Пароль",
"login_form_next_button": "Далі",
@ -252,35 +253,35 @@
"login_form_server_error": "Неможливо з'єднатися із сервером",
"login_password_changed_error": "Помилка у оновлені вашого пароля",
"login_password_changed_success": "Пароль оновлено успішно",
"map_assets_in_bound": "{} photo",
"map_assets_in_bounds": "{} photos",
"map_cannot_get_user_location": "Не можу отримати місцеперебування",
"map_assets_in_bound": "{} фото",
"map_assets_in_bounds": "{} фото",
"map_cannot_get_user_location": "Не можу отримати місцезнаходження",
"map_location_dialog_cancel": "Скасувати",
"map_location_dialog_yes": "Так",
"map_location_picker_page_use_location": "Use this location",
"map_location_service_disabled_content": "Служба локації має бути ввімкненою, щоб відображати елементи з вашого поточного місцеперебування. Увімкнути її зараз?",
"map_location_service_disabled_title": "Служба місцеперебування вимкнена",
"map_location_picker_page_use_location": "Це місцезнаходження",
"map_location_service_disabled_content": "Служба локації має бути ввімкненою, щоб відображати елементи з вашого поточного місцезнаходження. Увімкнути її зараз?",
"map_location_service_disabled_title": "Служба місцезнаходження вимкнена",
"map_no_assets_in_bounds": "Немає знімків із цього місця",
"map_no_location_permission_content": "Потрібен дозвіл, аби показувати елементи із поточного місцеперебування. Надати його зараз?",
"map_no_location_permission_title": "Помилка доступу до місцеперебування",
"map_no_location_permission_content": "Потрібен дозвіл, аби показувати елементи із поточного місцезнаходження. Надати його зараз?",
"map_no_location_permission_title": "Помилка доступу до місцезнаходження",
"map_settings_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_date_range_option_all": "Усі",
"map_settings_date_range_option_day": "Минулі 24 години",
"map_settings_date_range_option_days": "Минулих {} днів",
"map_settings_date_range_option_year": "Минулий рік",
"map_settings_date_range_option_years": "Минулі {} роки",
"map_settings_dialog_cancel": "Скасувати",
"map_settings_dialog_save": "Зберегти",
"map_settings_dialog_title": "Налаштування мапи",
"map_settings_include_show_archived": "Include Archived",
"map_settings_only_relative_range": "Діапазон дат",
"map_settings_include_show_archived": "Включити архівні дані",
"map_settings_only_relative_range": "Проміжок часу",
"map_settings_only_show_favorites": "Лише улюбені",
"map_settings_theme_settings": "Map Theme",
"map_settings_theme_settings": "Тема карти",
"map_zoom_to_see_photos": "Зменшіть, аби переглянути знімки",
"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": "Cannot edit location of read only asset(s), skipping",
"multiselect_grid_edit_date_time_err_read_only": "Неможливо редагувати дату елементів лише для читання, пропущено",
"multiselect_grid_edit_gps_err_read_only": "Неможливо редагувати місцезнаходження елементів лише для читання, пропущено",
"notification_permission_dialog_cancel": "Скасувати",
"notification_permission_dialog_content": "Щоб увімкнути сповіщення, перейдіть до Налаштувань і надайте дозвіл.",
"notification_permission_dialog_settings": "Налаштування",
@ -296,7 +297,7 @@
"partner_page_stop_sharing_content": "{} втратить доступ до ваших знімків.",
"partner_page_stop_sharing_title": "Припинити надання ваших знімків?",
"partner_page_title": "Партнер",
"permission_onboarding_back": "Back",
"permission_onboarding_back": "Назад",
"permission_onboarding_continue_anyway": "Все одно продовжити",
"permission_onboarding_get_started": "Розпочати",
"permission_onboarding_go_to_settings": "Перейти до налаштувань",
@ -307,56 +308,56 @@
"permission_onboarding_permission_limited": "Обмежений доступ. Аби дозволити Immich резервне копіювання та керування вашою галереєю, надайте доступ до знімків та відео у Налаштуваннях",
"permission_onboarding_request": "Immich потребує доступу до ваших знімків та відео.",
"profile_drawer_app_logs": "Журнал",
"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": "Клієнт та Сервер — актуальні",
"profile_drawer_documentation": "Documentation",
"profile_drawer_client_out_of_date_major": "Мобільний додаток застарів. Будь ласка, оновіть до останньої мажорної версії.",
"profile_drawer_client_out_of_date_minor": "Мобільний додаток застарів. Будь ласка, оновіть до останньої мінорної версії.",
"profile_drawer_client_server_up_to_date": "Клієнт та сервер — актуальні",
"profile_drawer_documentation": "Документація",
"profile_drawer_github": "GitHub",
"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_server_out_of_date_major": "Сервер застарів. Будь ласка, оновіть до останньої мажорної версії.",
"profile_drawer_server_out_of_date_minor": "Сервер застарів. Будь ласка, оновіть до останньої мінорної версії.",
"profile_drawer_settings": "Налаштування",
"profile_drawer_sign_out": "Вийти",
"profile_drawer_trash": "Trash",
"profile_drawer_trash": "Кошик",
"recently_added_page_title": "Нещодавні",
"scaffold_body_error_occurred": "Error occurred",
"scaffold_body_error_occurred": "Виникла помилка",
"search_bar_hint": "Шукати ваші знімки",
"search_page_categories": "Категорії",
"search_page_favorites": "Улюблені",
"search_page_motion_photos": "Рухомі Знімки",
"search_page_motion_photos": "Рухомі знімки",
"search_page_no_objects": "Немає інформації про об'єкти",
"search_page_no_places": "No Places Info Available",
"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": "Places",
"search_page_recently_added": "Recently added",
"search_page_screenshots": "Screenshots",
"search_page_selfies": "Selfies",
"search_page_things": "Things",
"search_page_videos": "Videos",
"search_page_view_all_button": "View all",
"search_page_your_activity": "Your activity",
"search_page_your_map": "Your Map",
"search_result_page_new_search_hint": "New Search",
"search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ",
"search_suggestion_list_smart_search_hint_2": "m:your-search-term",
"select_additional_user_for_sharing_page_suggestions": "Suggestions",
"search_page_no_places": "Інформація про місця недоступна",
"search_page_people": "Люди",
"search_page_person_add_name_dialog_cancel": "Скасувати",
"search_page_person_add_name_dialog_hint": "Ім'я",
"search_page_person_add_name_dialog_save": "Зберегти",
"search_page_person_add_name_dialog_title": "Додати ім'я",
"search_page_person_add_name_subtitle": "Швидко знайдіть їх за назвою за допомогою пошуку",
"search_page_person_add_name_title": "Додати ім'я",
"search_page_person_edit_name": "Відредагувати ім'я",
"search_page_places": "Місця",
"search_page_recently_added": "Нещодавно додані",
"search_page_screenshots": "Знімки екрану",
"search_page_selfies": "Селфі",
"search_page_things": "Речі",
"search_page_videos": "Відео",
"search_page_view_all_button": "Переглянути усі",
"search_page_your_activity": "Ваші дії",
"search_page_your_map": "Ваша мапа",
"search_result_page_new_search_hint": "Новий пошук",
"search_suggestion_list_smart_search_hint_1": "Інтелектуальний пошук увімкнено за замовчуванням, для пошуку метаданих використовуйте синтаксис",
"search_suggestion_list_smart_search_hint_2": "m:ваш-пошуковий-термін",
"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": "App Version",
"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": "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": "Увімкніть для завантаження зображення середньої роздільної здатності. Вимкніть або для можливості безпосереднього завантаження оригіналу, або для використання лише мініатюр.",
"setting_image_viewer_preview_title": "Завантажити попередній перегляд зображення",
"select_user_for_sharing_page_share_suggestions": "Пропозиції",
"server_info_box_app_version": "Версія додатка",
"server_info_box_latest_release": "Остання версія",
"server_info_box_server_url": "URL сервера",
"server_info_box_server_version": "Версія сервера",
"setting_image_viewer_help": "Повноекранний переглядач спочатку завантажує зображення для попереднього перегляду в низькій роздільній здатності, потім завантажує зображення в зменшеній роздільній здатності відносно оригіналу (якщо включено) і зрештою завантажує оригінал (якщо включено).",
"setting_image_viewer_original_subtitle": "Увімкніть для завантаження оригінального зображення з повною роздільною здатністю (велике!).\nВимкніть, щоб зменшити використання даних (мережі та кешу пристрою).",
"setting_image_viewer_original_title": "Завантажувати оригінальне зображення",
"setting_image_viewer_preview_subtitle": "Увімкніть для завантаження зображень середньої роздільної здатності.\nВимкніть для безпосереднього завантаження оригіналу або використовувати лише мініатюру.",
"setting_image_viewer_preview_title": "Завантажувати зображення попереднього перегляду",
"setting_notifications_notify_failures_grace_period": "Повідомити про помилки фонового резервного копіювання: {}",
"setting_notifications_notify_hours": "{} годин",
"setting_notifications_notify_immediately": "негайно",
@ -365,9 +366,9 @@
"setting_notifications_notify_seconds": "{} секунд",
"setting_notifications_single_progress_subtitle": "Детальна інформація про хід завантаження для кожного елементу",
"setting_notifications_single_progress_title": "Показати хід фонового резервного копіювання",
"setting_notifications_subtitle": "Налаштуйте свої параметри сповіщень",
"setting_notifications_subtitle": "Налаштування параметрів сповіщень",
"setting_notifications_title": "Сповіщення",
"setting_notifications_total_progress_subtitle": "Загальний прогрес (готово/загалом)",
"setting_notifications_total_progress_subtitle": "Загальний прогрес (виконано/загалом)",
"setting_notifications_total_progress_title": "Показати загальний хід фонового резервного копіювання",
"setting_pages_app_bar_settings": "Налаштування",
"settings_require_restart": "Перезавантажте програму для застосування цього налаштування",
@ -375,66 +376,66 @@
"share_add_photos": "Додати знімки",
"share_add_title": "Додати назву",
"share_create_album": "Створити альбом",
"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",
"shared_album_activities_input_disable": "Коментування вимкнено",
"shared_album_activities_input_hint": "Скажіть що-небудь",
"shared_album_activity_remove_content": "Ви бажаєте видалити це повідомлення?",
"shared_album_activity_remove_title": "Видалити повідомлення",
"shared_album_activity_setting_subtitle": "Дозволити іншим відповідати",
"shared_album_activity_setting_title": "Коментарі та лайки",
"shared_album_section_people_action_error": "Помилка виходу/видалення з альбому",
"shared_album_section_people_action_leave": "Видалити користувача з альбому",
"shared_album_section_people_action_remove_user": "Видалити користувача з альбому",
"shared_album_section_people_owner_label": "Власник",
"shared_album_section_people_title": "ЛЮДИ",
"share_dialog_preparing": "Підготовка...",
"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_never": "Never",
"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_info_chip_download": "Download",
"shared_link_app_bar_title": "Спільні посилання",
"shared_link_clipboard_copied_massage": "Скопійовано в буфер обміну",
"shared_link_clipboard_text": "Посилання: {}\nПароль: {}",
"shared_link_create_app_bar_title": "Створити посилання спільного доступу",
"shared_link_create_error": "Помилка під час створення спільного посилання",
"shared_link_create_info": "Дозволити всім, хто має посилання, переглянути вибрані фото",
"shared_link_create_submit_button": "Створити посилання",
"shared_link_edit_allow_download": "Дозволити публічному користувачеві скачувати файли",
"shared_link_edit_allow_upload": "Дозволити публічному користувачеві завантажувати файли",
"shared_link_edit_app_bar_title": "Редагувати посилання",
"shared_link_edit_change_expiry": "Змінити термін дії",
"shared_link_edit_description": "Опис",
"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": "{} днів",
"shared_link_edit_expire_after_option_hour": "1 годину",
"shared_link_edit_expire_after_option_hours": "{} годин",
"shared_link_edit_expire_after_option_minute": "1 хвилину",
"shared_link_edit_expire_after_option_minutes": "{} хвилин",
"shared_link_edit_expire_after_option_never": "Ніколи",
"shared_link_edit_password": "Пароль",
"shared_link_edit_password_hint": "Введіть пароль для спільного доступу",
"shared_link_edit_show_meta": "Показувати метадані",
"shared_link_edit_submit_button": "Оновити посилання",
"shared_link_empty": "У вас немає спільних посилань",
"shared_link_error_server_url_fetch": "Неможливо запитати URL із сервера",
"shared_link_expired": "Закінчився термін дії",
"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": "Закінчується через {} секунду",
"shared_link_expires_seconds": "Закінчується через {} секунд",
"shared_link_info_chip_download": "Скачати",
"shared_link_info_chip_metadata": "EXIF",
"shared_link_info_chip_upload": "Upload",
"shared_link_manage_links": "Manage Shared links",
"share_done": "Done",
"shared_link_info_chip_upload": "Завантажити",
"shared_link_manage_links": "Керування спільними посиланнями",
"share_done": "Готово",
"share_invite": "Запросити в альбом",
"sharing_page_album": "Спільні альбоми",
"sharing_page_description": "Створюйте спільні альбоми, щоб ділитися знімками та відео з людьми у вашій мережі.",
"sharing_page_empty_list": "ПОРОЖНІЙ СПИСОК",
"sharing_silver_appbar_create_shared_album": "Створити спільний альбом",
"sharing_silver_appbar_shared_links": "Shared links",
"sharing_silver_appbar_shared_links": "Спільні посилання",
"sharing_silver_appbar_share_partner": "Поділитися з партнером",
"tab_controller_nav_library": "Бібліотека",
"tab_controller_nav_photos": "Знімки",
@ -443,26 +444,26 @@
"theme_setting_asset_list_storage_indicator_title": "Показувати піктограму сховища на плитках елементів",
"theme_setting_asset_list_tiles_per_row_title": "Кількість елементів у рядку ({})",
"theme_setting_dark_mode_switch": "Темна тема",
"theme_setting_image_viewer_quality_subtitle": "Налаштувати якість перегляду повних зображень",
"theme_setting_image_viewer_quality_subtitle": "Налаштування якості перегляду повноекранних зображень",
"theme_setting_image_viewer_quality_title": "Якість перегляду зображень",
"theme_setting_system_theme_switch": "Автоматично (як система)",
"theme_setting_theme_subtitle": "Виберіть налаштування теми програми",
"theme_setting_system_theme_switch": "Автоматично (як у системі)",
"theme_setting_theme_subtitle": "Налаштування теми додатка",
"theme_setting_theme_title": "Тема",
"theme_setting_three_stage_loading_subtitle": "Триетапне завантаження може підвищити продуктивність завантаження, але спричинить значно більше навантаження на мережу",
"theme_setting_three_stage_loading_title": "Увімкнути триетапне завантаження",
"translated_text_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": "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 ({})",
"trash_page_delete": "Видалити",
"trash_page_delete_all": "Видалити усі",
"trash_page_empty_trash_btn": "Очистити кошик",
"trash_page_empty_trash_dialog_content": "Бажаєте очистити ваші елементи в кошику? Ці елементи буде остаточно видалено з Immich.",
"trash_page_empty_trash_dialog_ok": "OK",
"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": "Кошик ({})",
"upload_dialog_cancel": "Скасувати",
"upload_dialog_info": "Бажаєте створити резервну копію вибраних елементів на сервері?",
"upload_dialog_ok": "Завантажити",
@ -473,7 +474,7 @@
"version_announcement_overlay_text_2": "знайдіть хвильку навідатися на ",
"version_announcement_overlay_text_3": "і переконайтеся, що ваші налаштування docker-compose та .env оновлені, аби запобігти будь-якій неправильній конфігурації, особливо, якщо ви використовуєте WatchTower або інший механізм, для автоматичних оновлень вашої серверної частини.",
"version_announcement_overlay_title": "Доступна нова версія сервера \uD83C\uDF89",
"viewer_remove_from_stack": "Remove from Stack",
"viewer_stack_use_as_main_asset": "Use as Main Asset",
"viewer_unstack": "Un-Stack"
"viewer_remove_from_stack": "Видалити зі стеку",
"viewer_stack_use_as_main_asset": "Використовувати як основний елементи",
"viewer_unstack": "Розібрати стек"
}

View File

@ -1,9 +1,9 @@
{
"action_common_cancel": "Cancel",
"action_common_update": "Update",
"action_common_cancel": "Từ chối",
"action_common_update": "Cập nhật",
"add_to_album_bottom_sheet_added": "Thêm vào {album}",
"add_to_album_bottom_sheet_already_exists": "Đã có sẵn trong {album}",
"advanced_settings_log_level_title": "Log level: {}",
"advanced_settings_log_level_title": "Phân loại nhật ký: {}",
"advanced_settings_prefer_remote_subtitle": "Trên một số thiết bị, việc tải hình thu nhỏ từ ảnh trên thiết bị diễn ra chậm. Kích hoạt cài đặt này để tải ảnh từ máy chủ.",
"advanced_settings_prefer_remote_title": "Ưu tiên ảnh từ máy chủ",
"advanced_settings_self_signed_ssl_subtitle": "Bỏ qua xác minh chứng chỉ SSL cho máy chủ cuối. Yêu cầu cho chứng chỉ tự ký.",
@ -35,8 +35,8 @@
"app_bar_signout_dialog_title": "Đăng xuất",
"archive_page_no_archived_assets": "Không tìm thấy ảnh đã lưu trữ",
"archive_page_title": "Kho lưu trữ ({})",
"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_action_delete_err_read_only": "Không thể xoá ảnh chỉ có quyền đọc, bỏ qua",
"asset_action_share_err_offline": "Không thể tải ảnh ngoại tuyến, bỏ qua",
"asset_list_layout_settings_dynamic_layout_title": "Bố cục động",
"asset_list_layout_settings_group_automatically": "Tự động",
"asset_list_layout_settings_group_by": " Nhóm ảnh theo",
@ -111,9 +111,9 @@
"cache_settings_album_thumbnails": "Trang thư viện hình thu nhỏ ({} ảnh)",
"cache_settings_clear_cache_button": "Xoá bộ nhớ đệm",
"cache_settings_clear_cache_button_title": "Xóa bộ nhớ đệm của ứng dụng. Điều này sẽ ảnh hưởng đến hiệu suất của ứng dụng đến khi bộ nhớ đệm được tạo lại.",
"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_duplicated_assets_clear_button": "XOÁ",
"cache_settings_duplicated_assets_subtitle": "Ảnh và video không được phép hiển thị trên ứng dụng",
"cache_settings_duplicated_assets_title": "Ảnh bị trùng lặp ({})",
"cache_settings_image_cache_size": "Kích thước bộ nhớ đệm ảnh ({} ảnh)",
"cache_settings_statistics_album": "Thư viện hình thu nhỏ",
"cache_settings_statistics_assets": "{} ảnh ({})",
@ -142,17 +142,17 @@
"control_bottom_app_bar_archive": "Kho lưu trữ",
"control_bottom_app_bar_create_new_album": "Tạo album mới",
"control_bottom_app_bar_delete": "Xoá",
"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_delete_from_immich": "Xóa khỏi Immich",
"control_bottom_app_bar_delete_from_local": "Xóa khỏi thiết bị\n",
"control_bottom_app_bar_edit_location": "Chỉnh sửa vị trí",
"control_bottom_app_bar_edit_time": "Chỉnh sửa Ngày và Giờ",
"control_bottom_app_bar_favorite": "Yêu thích",
"control_bottom_app_bar_share": "Chia sẻ",
"control_bottom_app_bar_share_to": "Chia sẻ với",
"control_bottom_app_bar_stack": "Ngắn xếp",
"control_bottom_app_bar_trash_from_immich": "Move to Trash",
"control_bottom_app_bar_stack": "Xếp nhóm",
"control_bottom_app_bar_trash_from_immich": "Di chuyển tới thùng rác",
"control_bottom_app_bar_unarchive": "Huỷ lưu trữ",
"control_bottom_app_bar_unfavorite": "Unfavorite",
"control_bottom_app_bar_unfavorite": "Bỏ yêu thích",
"control_bottom_app_bar_upload": "Tải lên",
"create_album_page_untitled": "Không tiêu đề",
"create_shared_album_page_create": "Tạo",
@ -165,26 +165,27 @@
"daily_title_text_date_year": "E, MMM dd, yyyy",
"date_format": "E, LLL d, y • h:mm a",
"delete_dialog_alert": "Những mục này sẽ bị xóa vĩnh viễn khỏi Immich và thiết bị của bạn",
"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_alert_local": "Những mục này sẽ bị xóa vĩnh viễn khỏi thiết bị của bạn nhưng vẫn còn lưu trữ trên máy chủ Immich",
"delete_dialog_alert_local_non_backed_up": "Một số mục chưa được sao lưu lên Immich và sẽ bị xóa vĩnh viễn khỏi thiết bị của bạn.",
"delete_dialog_alert_remote": "Những mục này sẽ bị xóa vĩnh viễn khỏi máy chủ Immich",
"delete_dialog_cancel": "Từ chối",
"delete_dialog_ok": "Xoá",
"delete_dialog_ok_force": "Delete Anyway",
"delete_dialog_ok_force": "Xóa Vĩnh Viễn",
"delete_dialog_title": "Xoá vĩnh viễn",
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only",
"delete_local_dialog_ok_force": "Delete Anyway",
"delete_local_dialog_ok_backed_up_only": "Xoá ảnh đã sao lưu",
"delete_local_dialog_ok_force": "Vẫn xoá",
"delete_shared_link_dialog_content": "Bạn có muốn xóa liên kết đã chia sẻ này không?",
"delete_shared_link_dialog_title": "Xoá liên kết đã chia sẻ",
"description_input_hint_text": "Thêm mô tả...",
"description_input_submit_error": "Cập nhật mô tả không thành công, vui lòng kiểm tra nhật ký để biết thêm chi tiết",
"edit_date_time_dialog_date_time": "Date and Time",
"edit_date_time_dialog_timezone": "Timezone",
"edit_location_dialog_title": "Location",
"edit_date_time_dialog_date_time": "Ngày và Giờ",
"edit_date_time_dialog_timezone": "Múi giờ",
"edit_location_dialog_title": "Vị trí",
"exif_bottom_sheet_description": "Thêm mô tả...",
"exif_bottom_sheet_details": "CHI TIẾT",
"exif_bottom_sheet_location": "VỊ TRÍ",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_location_add": "Thêm vị trí",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "Đang trong quá trình phát triển",
"experimental_settings_new_asset_list_title": "Bật lưới ảnh thử nghiệm",
"experimental_settings_subtitle": "Sử dụng có thể rủi ro!",
@ -214,22 +215,22 @@
"library_page_favorites": "Ảnh yêu thích",
"library_page_new_album": "Album mới",
"library_page_sharing": "Chia sẻ",
"library_page_sort_asset_count": "Number of assets",
"library_page_sort_asset_count": "Số lượng ảnh",
"library_page_sort_created": "Mới tạo gần đây",
"library_page_sort_last_modified": "Sửa đổi lần cuối",
"library_page_sort_most_oldest_photo": "Oldest photo",
"library_page_sort_most_oldest_photo": "Ảnh cũ nhất",
"library_page_sort_most_recent_photo": "Ảnh gần đây nhất",
"library_page_sort_title": "Tiêu đề album",
"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",
"location_picker_choose_on_map": "Chọn trên bản đồ",
"location_picker_latitude": "Vĩ độ",
"location_picker_latitude_error": "Nhập vĩ độ hợp lệ",
"location_picker_latitude_hint": "Nhập vĩ độ của bạn",
"location_picker_longitude": "Kinh độ",
"location_picker_longitude_error": "Nhập kinh độ hợp lệ",
"location_picker_longitude_hint": "Nhập kinh độ của bạn",
"login_disabled": "Đăng nhập bị vô hiệu hoá",
"login_form_api_exception": "Lỗi API. Vui lòng kiểm tra địa chỉ máy chủ và thử lại.",
"login_form_back_button_text": "Back",
"login_form_back_button_text": "Quay lại",
"login_form_button_text": "Đăng nhập",
"login_form_email_hint": "emailcuaban@email.com",
"login_form_endpoint_hint": "http://địa-chỉ-ip-máy-chủ-bạn:cổng/api",
@ -252,35 +253,35 @@
"login_form_server_error": "Không thể kết nối tới máy chủ.",
"login_password_changed_error": "Thay đổi mật khẩu không thành công",
"login_password_changed_success": "Cập nhật mật khẩu thành công",
"map_assets_in_bound": "{} photo",
"map_assets_in_bounds": "{} photos",
"map_assets_in_bound": "{} ảnh",
"map_assets_in_bounds": "{} ảnh",
"map_cannot_get_user_location": "Không thể xác định vị trí của bạn",
"map_location_dialog_cancel": "Từ chối",
"map_location_dialog_yes": "Có",
"map_location_picker_page_use_location": "Use this location",
"map_location_picker_page_use_location": "Dùng vị trí này",
"map_location_service_disabled_content": "Cần bật dịch vụ định vị để hiển thị ảnh hoặc video từ vị trí hiện tại của bạn. Bạn có muốn bật nó ngay bây giờ không?",
"map_location_service_disabled_title": "Dịch vụ vị trí bị vô hiệu hoá",
"map_no_assets_in_bounds": "Không có ảnh trong khu vực này",
"map_no_location_permission_content": "Cần quyền truy cập vị trí để hiển thị ảnh từ vị trí hiện tại của bạn. Bạn có muốn cho phép ngay bây giờ không?",
"map_no_location_permission_title": "Ứng dụng không được phép truy cập vị trí",
"map_settings_dark_mode": "Chế độ tối",
"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_date_range_option_all": "Tất cả",
"map_settings_date_range_option_day": "Trong vòng 24 giờ qua",
"map_settings_date_range_option_days": "Trong {} ngày qua",
"map_settings_date_range_option_year": "Năm ngoái",
"map_settings_date_range_option_years": "Trong {} năm qua",
"map_settings_dialog_cancel": "Từ chối",
"map_settings_dialog_save": "Lưu",
"map_settings_dialog_title": "Cài đặt bản đồ",
"map_settings_include_show_archived": "Bao gồm ảnh đã lưu trữ",
"map_settings_only_relative_range": "Khoảng thời gian",
"map_settings_only_show_favorites": "Chỉ hiển thị mục yêu thích",
"map_settings_theme_settings": "Map Theme",
"map_settings_theme_settings": "Giao diện bản đồ",
"map_zoom_to_see_photos": "Thu nhỏ để xem ảnh",
"monthly_title_text_date_format": "MMMM y",
"motion_photos_page_title": "Ảnh động",
"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",
"multiselect_grid_edit_date_time_err_read_only": "Không thể chỉnh sửa ngày của ảnh chỉ có quyền đọc, bỏ qua",
"multiselect_grid_edit_gps_err_read_only": "Không thể chỉnh sửa vị trí của ảnh chỉ có quyền đọc, bỏ qua",
"notification_permission_dialog_cancel": "Từ chối",
"notification_permission_dialog_content": "Để bật thông báo, chuyển tới Cài đặt và chọn cho phép",
"notification_permission_dialog_settings": "Cài đặt",
@ -307,18 +308,18 @@
"permission_onboarding_permission_limited": "Quyền truy cập vào ảnh của bạn bị hạn chế. Để Immich sao lưu và quản lý toàn bộ thư viện ảnh của bạn, hãy cấp quyền truy cập toàn bộ ảnh trong Cài đặt.",
"permission_onboarding_request": "Immich cần quyền để xem ảnh và video của bạn",
"profile_drawer_app_logs": "Nhật ký",
"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_out_of_date_major": "Ứng dụng đã lỗi thời. Vui lòng cập nhật lên phiên bản chính mới nhất.",
"profile_drawer_client_out_of_date_minor": "Ứng dụng đã lỗi thời. Vui lòng cập nhật lên phiên bản phụ mới nhất.",
"profile_drawer_client_server_up_to_date": "Máy khách và máy chủ đã cập nhật",
"profile_drawer_documentation": "Tài liệu",
"profile_drawer_github": "GitHub",
"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_server_out_of_date_major": "Máy chủ đã lỗi thời. Vui lòng cập nhật lên phiên bản chính mới nhất.",
"profile_drawer_server_out_of_date_minor": "Máy chủ đã lỗi thời. Vui lòng cập nhật lên phiên bản phụ mới nhất.",
"profile_drawer_settings": "Cài đặt",
"profile_drawer_sign_out": "Đăng xuất",
"profile_drawer_trash": "Thùng rác",
"recently_added_page_title": "Mới thêm gần đây",
"scaffold_body_error_occurred": "Error occurred",
"scaffold_body_error_occurred": "Xảy ra lỗi",
"search_bar_hint": "Tìm kiếm ảnh của bạn",
"search_page_categories": "Danh mục",
"search_page_favorites": "Ảnh yêu thích",
@ -326,13 +327,13 @@
"search_page_no_objects": "Không có thông tin sự vật",
"search_page_no_places": "Không có thông tin địa điểm nào",
"search_page_people": "Mọi người",
"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_person_add_name_dialog_cancel": "Từ chối",
"search_page_person_add_name_dialog_hint": "Tên",
"search_page_person_add_name_dialog_save": "Lưu",
"search_page_person_add_name_dialog_title": "Thêm tên",
"search_page_person_add_name_subtitle": "Tìm nhanh theo tên với chức năng tìm kiếm",
"search_page_person_add_name_title": "Thêm tên",
"search_page_person_edit_name": "Chỉnh sửa tên",
"search_page_places": "Địa điểm",
"search_page_recently_added": "Mới thêm gần đây",
"search_page_screenshots": "Ảnh màn hình",
@ -341,7 +342,7 @@
"search_page_videos": "Video",
"search_page_view_all_button": "Xem tất cả",
"search_page_your_activity": "Hoạt động của bạn",
"search_page_your_map": "Your Map",
"search_page_your_map": "Bản đồ của bạn",
"search_result_page_new_search_hint": "Tìm kiếm mới",
"search_suggestion_list_smart_search_hint_1": "Tìm kiếm thông minh được bật mặc định, để tìm kiếm siêu dữ liệu hãy sử dụng cú pháp",
"search_suggestion_list_smart_search_hint_2": "m:cụm-từ-tìm-kiếm-của-bạn",
@ -381,17 +382,17 @@
"shared_album_activity_remove_title": "Xoá hoạt động",
"shared_album_activity_setting_subtitle": "Cho phép người khác phản hồi",
"shared_album_activity_setting_title": "Bình luận và lượt thích",
"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",
"shared_album_section_people_action_error": "Lỗi khi xoá khỏi album",
"shared_album_section_people_action_leave": "Xóa người dùng khỏi album",
"shared_album_section_people_action_remove_user": "Xóa người dùng khỏi album",
"shared_album_section_people_owner_label": "Chủ sở hữu",
"shared_album_section_people_title": "MỌI NGƯỜI",
"share_dialog_preparing": "Đang xử lý...",
"shared_link_app_bar_title": "Đường liên kết chia sẻ",
"shared_link_clipboard_copied_massage": "Copied to clipboard",
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
"shared_link_clipboard_copied_massage": "Đã sao chép tới bản ghi tạm",
"shared_link_clipboard_text": "Liên kết: {}\nMật khẩu: {}",
"shared_link_create_app_bar_title": "Tạo liên kết để chia sẻ",
"shared_link_create_error": "Error while creating shared link",
"shared_link_create_error": "Tạo liên kết chia sẻ không thành công",
"shared_link_create_info": "Cho phép bất cứ ai có liên kết xem (các) ảnh đã chọn",
"shared_link_create_submit_button": "Tạo liên kết",
"shared_link_edit_allow_download": "Cho phép bất cứ ai đều có thể tải xuống",
@ -401,32 +402,32 @@
"shared_link_edit_description": "Mô tả",
"shared_link_edit_description_hint": "Nhập mô tả chia sẻ",
"shared_link_edit_expire_after": "Hết hạn sau",
"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_never": "Never",
"shared_link_edit_expire_after_option_day": "1 ngày",
"shared_link_edit_expire_after_option_days": "{} ngày",
"shared_link_edit_expire_after_option_hour": "1 giờ",
"shared_link_edit_expire_after_option_hours": "{} giờ",
"shared_link_edit_expire_after_option_minute": "1 phút",
"shared_link_edit_expire_after_option_minutes": "{} phút",
"shared_link_edit_expire_after_option_never": "Không bao giờ",
"shared_link_edit_password": "Mật khẩu",
"shared_link_edit_password_hint": "Nhập mật khẩu chia sẻ",
"shared_link_edit_show_meta": "Hiện thị siêu dữ liệu",
"shared_link_edit_submit_button": "Cập nhật liên kết",
"shared_link_empty": "Bạn không có liên kết được chia sẻ nào",
"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_info_chip_download": "Download",
"shared_link_info_chip_metadata": "EXIF",
"shared_link_info_chip_upload": "Upload",
"shared_link_error_server_url_fetch": "Không thể kết nối địa chỉ máy chủ",
"shared_link_expired": "Đã hết hạn",
"shared_link_expires_day": "Hết hạn trong {} ngày",
"shared_link_expires_days": "Hết hạn trong {} ngày",
"shared_link_expires_hour": "Hết hạn trong {} giờ",
"shared_link_expires_hours": "Hết hạn trong {} giờ",
"shared_link_expires_minute": "Hết hạn trong {} phút",
"shared_link_expires_minutes": "Hết hạn trong {} phút",
"shared_link_expires_never": "Hết hạn ∞\n",
"shared_link_expires_second": "Hết hạn trong {} giây",
"shared_link_expires_seconds": "Hết hạn trong {} giây",
"shared_link_info_chip_download": "Tải xuống",
"shared_link_info_chip_metadata": "Dữ liệu EXIF",
"shared_link_info_chip_upload": "Tải lên",
"shared_link_manage_links": "Quản lý liên kết được chia sẻ",
"share_done": "Hoàn tất",
"share_invite": "Mời vào album",
@ -441,11 +442,11 @@
"tab_controller_nav_search": "Tìm kiếm",
"tab_controller_nav_sharing": "Chia sẻ",
"theme_setting_asset_list_storage_indicator_title": "Hiện thị trạng thái sao lưu ảnh trên hình thu nhỏ ",
"theme_setting_asset_list_tiles_per_row_title": "Số ảnh và video trên một dòng ({})",
"theme_setting_asset_list_tiles_per_row_title": "Số lượng ảnh trên một dòng ({})",
"theme_setting_dark_mode_switch": "Chế độ tối",
"theme_setting_image_viewer_quality_subtitle": "Điều chỉnh chất lượng của trình xem ảnh",
"theme_setting_image_viewer_quality_title": "Chất lượng trình xem ảnh",
"theme_setting_system_theme_switch": "Tư động (Theo cài đặt hệ thống)",
"theme_setting_system_theme_switch": "T động (Theo cài đặt hệ thống)",
"theme_setting_theme_subtitle": "Chọn cài đặt giao diện ứng dụng",
"theme_setting_theme_title": "Giao diện",
"theme_setting_three_stage_loading_subtitle": "Tải ba giai doạn có thể tăng hiệu năng tải ảnh nhưng sẽ tốn dữ liệu mạng đáng kể.",
@ -473,7 +474,7 @@
"version_announcement_overlay_text_2": "vui lòng dành thời gian của bạn để đến thăm",
"version_announcement_overlay_text_3": "và đảm bảo cài đặt docker-compose và tệp .env của bạn đã cập nhật để tránh bất kỳ cấu hình sai sót, đặc biệt nếu bạn dùng WatchTower hoặc bất kỳ cơ chế nào xử lý việc cập nhật ứng dụng máy chủ của bạn tự động.",
"version_announcement_overlay_title": "Phiên bản máy chủ có bản cập nhật mới",
"viewer_remove_from_stack": "Loại bỏ khỏi ngăn xếp",
"viewer_stack_use_as_main_asset": "Sử dụng làm ảnh bìa ngăn xếp",
"viewer_unstack": "Hoàn tác ngăn xếp"
"viewer_remove_from_stack": "Xoá khỏi nhóm",
"viewer_stack_use_as_main_asset": "Đặt làm lựa chọn hàng đầu",
"viewer_unstack": "Huỷ xếp nhóm"
}

View File

@ -80,7 +80,7 @@
"backup_controller_page_backup_sub": "已备份的照片和视频",
"backup_controller_page_cancel": "取消",
"backup_controller_page_created": "创建时间: {}",
"backup_controller_page_desc_backup": "打开前台备份,以在程序运行时自动备份。",
"backup_controller_page_desc_backup": "打开前台备份,以在程序运行时自动备份新项目。",
"backup_controller_page_excluded": "已排除:",
"backup_controller_page_failed": "失败({}",
"backup_controller_page_filename": "文件名称: {} [{}]",
@ -121,7 +121,7 @@
"cache_settings_statistics_shared": "共享相册缩略图",
"cache_settings_statistics_thumbnail": "缩略图",
"cache_settings_statistics_title": "缓存使用情况",
"cache_settings_subtitle": "控制 Immich 的缓存行为",
"cache_settings_subtitle": "控制 Immich app 的缓存行为",
"cache_settings_thumbnail_size": "缩略图缓存大小({} 项)",
"cache_settings_tile_subtitle": "设置本地存储行为",
"cache_settings_tile_title": "本地存储",
@ -134,7 +134,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": "{} 项",
@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "详情",
"exif_bottom_sheet_location": "位置",
"exif_bottom_sheet_location_add": "添加位置信息",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "正在处理",
"experimental_settings_new_asset_list_title": "启用实验性照片网格",
"experimental_settings_subtitle": "使用风险自负!",
@ -207,7 +208,7 @@
"home_page_upload_err_limit": "一次最多只能上传 30 个项目,跳过",
"image_viewer_page_state_provider_download_error": "下载出现错误",
"image_viewer_page_state_provider_download_success": "下载成功",
"image_viewer_page_state_provider_share_error": "共享",
"image_viewer_page_state_provider_share_error": "共享错",
"library_page_albums": "相册",
"library_page_archive": "归档",
"library_page_device_albums": "设备上的相册",
@ -248,7 +249,7 @@
"login_form_next_button": "下一个",
"login_form_password_hint": "密码",
"login_form_save_login": "保持登录",
"login_form_server_empty": "输入服务器地址",
"login_form_server_empty": "输入服务器地址",
"login_form_server_error": "无法连接到服务器。",
"login_password_changed_error": "更新密码时出错\n",
"login_password_changed_success": "密码更新成功",
@ -267,7 +268,7 @@
"map_settings_date_range_option_all": "所有",
"map_settings_date_range_option_day": "过去24小时",
"map_settings_date_range_option_days": "{}天前",
"map_settings_date_range_option_year": "去年",
"map_settings_date_range_option_year": "1年前",
"map_settings_date_range_option_years": "{}年前",
"map_settings_dialog_cancel": "取消",
"map_settings_dialog_save": "保存",
@ -343,7 +344,7 @@
"search_page_your_activity": "您的活动",
"search_page_your_map": "足迹",
"search_result_page_new_search_hint": "搜索新的",
"search_suggestion_list_smart_search_hint_1": "默认情况下启用智能搜索要搜索元数据,请使用相关语法",
"search_suggestion_list_smart_search_hint_1": "默认情况下启用智能搜索要搜索元数据,请使用相关语法",
"search_suggestion_list_smart_search_hint_2": "m:您的搜索关键词",
"select_additional_user_for_sharing_page_suggestions": "建议",
"select_user_for_sharing_page_err_album": "创建相册失败",

View File

@ -80,7 +80,7 @@
"backup_controller_page_backup_sub": "已备份的照片和视频",
"backup_controller_page_cancel": "取消",
"backup_controller_page_created": "创建时间: {}",
"backup_controller_page_desc_backup": "打开前台备份,以在程序运行时自动备份。",
"backup_controller_page_desc_backup": "打开前台备份,以在程序运行时自动备份新项目。",
"backup_controller_page_excluded": "已排除:",
"backup_controller_page_failed": "失败({}",
"backup_controller_page_filename": "文件名称: {} [{}]",
@ -121,7 +121,7 @@
"cache_settings_statistics_shared": "共享相册缩略图",
"cache_settings_statistics_thumbnail": "缩略图",
"cache_settings_statistics_title": "缓存使用情况",
"cache_settings_subtitle": "控制 Immich 的缓存行为",
"cache_settings_subtitle": "控制 Immich app 的缓存行为",
"cache_settings_thumbnail_size": "缩略图缓存大小({} 项)",
"cache_settings_tile_subtitle": "设置本地存储行为",
"cache_settings_tile_title": "本地存储",
@ -134,7 +134,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": "{} 项",
@ -185,6 +185,7 @@
"exif_bottom_sheet_details": "详情",
"exif_bottom_sheet_location": "位置",
"exif_bottom_sheet_location_add": "添加位置信息",
"exif_bottom_sheet_people": "PEOPLE",
"experimental_settings_new_asset_list_subtitle": "正在处理",
"experimental_settings_new_asset_list_title": "启用实验性照片网格",
"experimental_settings_subtitle": "使用风险自负!",
@ -192,7 +193,7 @@
"favorites_page_no_favorites": "未找到收藏项目",
"favorites_page_title": "收藏",
"home_page_add_to_album_conflicts": "已向相册 {album} 中添加 {added} 项。\n其中 {failed} 项在相册中已存在。",
"home_page_add_to_album_err_local": "暂不能将本地项目添加到相册中,跳过",
"home_page_add_to_album_err_local": "暂不能将本地项目添加到相册中,跳过",
"home_page_add_to_album_success": "已向相册 {album} 中添加 {added} 项。",
"home_page_album_err_partner": "暂无法将同伴的项目添加到相册,跳过",
"home_page_archive_err_local": "暂无法归档本地项目,跳过",
@ -207,7 +208,7 @@
"home_page_upload_err_limit": "一次最多只能上传 30 个项目,跳过",
"image_viewer_page_state_provider_download_error": "下载出现错误",
"image_viewer_page_state_provider_download_success": "下载成功",
"image_viewer_page_state_provider_share_error": "共享",
"image_viewer_page_state_provider_share_error": "共享错",
"library_page_albums": "相册",
"library_page_archive": "归档",
"library_page_device_albums": "设备上的相册",
@ -248,7 +249,7 @@
"login_form_next_button": "下一个",
"login_form_password_hint": "密码",
"login_form_save_login": "保持登录",
"login_form_server_empty": "输入服务器地址",
"login_form_server_empty": "输入服务器地址",
"login_form_server_error": "无法连接到服务器。",
"login_password_changed_error": "更新密码时出错\n",
"login_password_changed_success": "密码更新成功",
@ -267,7 +268,7 @@
"map_settings_date_range_option_all": "所有",
"map_settings_date_range_option_day": "过去24小时",
"map_settings_date_range_option_days": "{}天前",
"map_settings_date_range_option_year": "去年",
"map_settings_date_range_option_year": "1年前",
"map_settings_date_range_option_years": "{}年前",
"map_settings_dialog_cancel": "取消",
"map_settings_dialog_save": "保存",
@ -343,7 +344,7 @@
"search_page_your_activity": "您的活动",
"search_page_your_map": "足迹",
"search_result_page_new_search_hint": "搜索新的",
"search_suggestion_list_smart_search_hint_1": "默认情况下启用智能搜索要搜索元数据,请使用相关语法",
"search_suggestion_list_smart_search_hint_1": "默认情况下启用智能搜索要搜索元数据,请使用相关语法",
"search_suggestion_list_smart_search_hint_2": "m:您的搜索关键词",
"select_additional_user_for_sharing_page_suggestions": "建议",
"select_user_for_sharing_page_err_album": "创建相册失败",

View File

@ -180,4 +180,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 64c9b5291666c0ca3caabdfe9865c141ac40321d
COCOAPODS: 1.12.1
COCOAPODS: 1.11.3

View File

@ -19,7 +19,7 @@ platform :ios do
desc "iOS Beta"
lane :beta do
increment_version_number(
version_number: "1.97.0"
version_number: "1.98.0"
)
increment_build_number(
build_number: latest_testflight_build_number + 1,

View File

@ -1,26 +1,19 @@
import 'dart:async';
import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:video_player/video_player.dart';
import 'package:immich_mobile/shared/models/store.dart' as store;
import 'package:wakelock_plus/wakelock_plus.dart';
/// Provides the initialized video player controller
/// If the asset is local, use the local file
/// Otherwise, use a video player with a URL
ChewieController? useChewieController(
Asset asset, {
ChewieController useChewieController({
required VideoPlayerController controller,
EdgeInsets controlsSafeAreaMinimum = const EdgeInsets.only(
bottom: 100,
),
bool showOptions = true,
bool showControlsOnInitialize = false,
bool autoPlay = true,
bool autoInitialize = true,
bool allowFullScreen = false,
bool allowedScreenSleep = false,
bool showControls = true,
@ -33,7 +26,7 @@ ChewieController? useChewieController(
}) {
return use(
_ChewieControllerHook(
asset: asset,
controller: controller,
placeholder: placeholder,
showOptions: showOptions,
controlsSafeAreaMinimum: controlsSafeAreaMinimum,
@ -43,7 +36,6 @@ ChewieController? useChewieController(
hideControlsTimer: hideControlsTimer,
showControlsOnInitialize: showControlsOnInitialize,
showControls: showControls,
autoInitialize: autoInitialize,
allowedScreenSleep: allowedScreenSleep,
onPlaying: onPlaying,
onPaused: onPaused,
@ -52,13 +44,12 @@ ChewieController? useChewieController(
);
}
class _ChewieControllerHook extends Hook<ChewieController?> {
final Asset asset;
class _ChewieControllerHook extends Hook<ChewieController> {
final VideoPlayerController controller;
final EdgeInsets controlsSafeAreaMinimum;
final bool showOptions;
final bool showControlsOnInitialize;
final bool autoPlay;
final bool autoInitialize;
final bool allowFullScreen;
final bool allowedScreenSleep;
final bool showControls;
@ -70,14 +61,13 @@ class _ChewieControllerHook extends Hook<ChewieController?> {
final VoidCallback? onVideoEnded;
const _ChewieControllerHook({
required this.asset,
required this.controller,
this.controlsSafeAreaMinimum = const EdgeInsets.only(
bottom: 100,
),
this.showOptions = true,
this.showControlsOnInitialize = false,
this.autoPlay = true,
this.autoInitialize = true,
this.allowFullScreen = false,
this.allowedScreenSleep = false,
this.showControls = true,
@ -94,28 +84,33 @@ class _ChewieControllerHook extends Hook<ChewieController?> {
}
class _ChewieControllerHookState
extends HookState<ChewieController?, _ChewieControllerHook> {
ChewieController? chewieController;
VideoPlayerController? videoPlayerController;
@override
void initHook() async {
super.initHook();
unawaited(_initialize());
}
extends HookState<ChewieController, _ChewieControllerHook> {
late ChewieController chewieController = ChewieController(
videoPlayerController: hook.controller,
controlsSafeAreaMinimum: hook.controlsSafeAreaMinimum,
showOptions: hook.showOptions,
showControlsOnInitialize: hook.showControlsOnInitialize,
autoPlay: hook.autoPlay,
allowFullScreen: hook.allowFullScreen,
allowedScreenSleep: hook.allowedScreenSleep,
showControls: hook.showControls,
customControls: hook.customControls,
placeholder: hook.placeholder,
hideControlsTimer: hook.hideControlsTimer,
);
@override
void dispose() {
chewieController?.dispose();
videoPlayerController?.dispose();
chewieController.dispose();
super.dispose();
}
@override
ChewieController? build(BuildContext context) {
ChewieController build(BuildContext context) {
return chewieController;
}
/*
/// Initializes the chewie controller and video player controller
Future<void> _initialize() async {
if (hook.asset.isLocal && hook.asset.livePhotoVideoId == null) {
@ -141,32 +136,14 @@ class _ChewieControllerHookState
);
}
videoPlayerController!.addListener(() {
final value = videoPlayerController!.value;
if (value.isPlaying) {
WakelockPlus.enable();
hook.onPlaying?.call();
} else if (!value.isPlaying) {
WakelockPlus.disable();
hook.onPaused?.call();
}
if (value.position == value.duration) {
WakelockPlus.disable();
hook.onVideoEnded?.call();
}
});
await videoPlayerController!.initialize();
setState(() {
chewieController = ChewieController(
videoPlayerController: videoPlayerController!,
controlsSafeAreaMinimum: hook.controlsSafeAreaMinimum,
showOptions: hook.showOptions,
showControlsOnInitialize: hook.showControlsOnInitialize,
autoPlay: hook.autoPlay,
autoInitialize: hook.autoInitialize,
allowFullScreen: hook.allowFullScreen,
allowedScreenSleep: hook.allowedScreenSleep,
showControls: hook.showControls,
@ -174,6 +151,6 @@ class _ChewieControllerHookState
placeholder: hook.placeholder,
hideControlsTimer: hook.hideControlsTimer,
);
});
}
*/
}

View File

@ -0,0 +1,51 @@
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/services/asset.service.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'asset_people.provider.g.dart';
/// Maintains the list of people for an asset.
@riverpod
class AssetPeopleNotifier extends _$AssetPeopleNotifier {
final log = Logger('AssetPeopleNotifier');
@override
Future<List<PersonWithFacesResponseDto>> build(Asset asset) async {
if (!asset.isRemote) {
return [];
}
final list = await ref
.watch(assetServiceProvider)
.getRemotePeopleOfAsset(asset.remoteId!);
if (list == null) {
return [];
}
// explicitly a sorted slice to make it deterministic
// named people will be at the beginning, and names are sorted
// ascendingly
list.sort((a, b) {
final aNotEmpty = a.name.isNotEmpty;
final bNotEmpty = b.name.isNotEmpty;
if (aNotEmpty && !bNotEmpty) {
return -1;
} else if (!aNotEmpty && bNotEmpty) {
return 1;
} else if (!aNotEmpty && !bNotEmpty) {
return 0;
}
return a.name.compareTo(b.name);
});
return list;
}
Future<void> refresh() async {
// invalidate the state this way we don't have to
// duplicate the code from build.
ref.invalidateSelf();
}
}

View File

@ -0,0 +1,189 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'asset_people.provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$assetPeopleNotifierHash() =>
r'192a4ee188f781000fe43f1675c49e1081ccc631';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$AssetPeopleNotifier extends BuildlessAutoDisposeAsyncNotifier<
List<PersonWithFacesResponseDto>> {
late final Asset asset;
Future<List<PersonWithFacesResponseDto>> build(
Asset asset,
);
}
/// Maintains the list of people for an asset.
///
/// Copied from [AssetPeopleNotifier].
@ProviderFor(AssetPeopleNotifier)
const assetPeopleNotifierProvider = AssetPeopleNotifierFamily();
/// Maintains the list of people for an asset.
///
/// Copied from [AssetPeopleNotifier].
class AssetPeopleNotifierFamily
extends Family<AsyncValue<List<PersonWithFacesResponseDto>>> {
/// Maintains the list of people for an asset.
///
/// Copied from [AssetPeopleNotifier].
const AssetPeopleNotifierFamily();
/// Maintains the list of people for an asset.
///
/// Copied from [AssetPeopleNotifier].
AssetPeopleNotifierProvider call(
Asset asset,
) {
return AssetPeopleNotifierProvider(
asset,
);
}
@override
AssetPeopleNotifierProvider getProviderOverride(
covariant AssetPeopleNotifierProvider provider,
) {
return call(
provider.asset,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'assetPeopleNotifierProvider';
}
/// Maintains the list of people for an asset.
///
/// Copied from [AssetPeopleNotifier].
class AssetPeopleNotifierProvider extends AutoDisposeAsyncNotifierProviderImpl<
AssetPeopleNotifier, List<PersonWithFacesResponseDto>> {
/// Maintains the list of people for an asset.
///
/// Copied from [AssetPeopleNotifier].
AssetPeopleNotifierProvider(
Asset asset,
) : this._internal(
() => AssetPeopleNotifier()..asset = asset,
from: assetPeopleNotifierProvider,
name: r'assetPeopleNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$assetPeopleNotifierHash,
dependencies: AssetPeopleNotifierFamily._dependencies,
allTransitiveDependencies:
AssetPeopleNotifierFamily._allTransitiveDependencies,
asset: asset,
);
AssetPeopleNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.asset,
}) : super.internal();
final Asset asset;
@override
Future<List<PersonWithFacesResponseDto>> runNotifierBuild(
covariant AssetPeopleNotifier notifier,
) {
return notifier.build(
asset,
);
}
@override
Override overrideWith(AssetPeopleNotifier Function() create) {
return ProviderOverride(
origin: this,
override: AssetPeopleNotifierProvider._internal(
() => create()..asset = asset,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
asset: asset,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<AssetPeopleNotifier,
List<PersonWithFacesResponseDto>> createElement() {
return _AssetPeopleNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is AssetPeopleNotifierProvider && other.asset == asset;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, asset.hashCode);
return _SystemHash.finish(hash);
}
}
mixin AssetPeopleNotifierRef
on AutoDisposeAsyncNotifierProviderRef<List<PersonWithFacesResponseDto>> {
/// The parameter `asset` of this provider.
Asset get asset;
}
class _AssetPeopleNotifierProviderElement
extends AutoDisposeAsyncNotifierProviderElement<AssetPeopleNotifier,
List<PersonWithFacesResponseDto>> with AssetPeopleNotifierRef {
_AssetPeopleNotifierProviderElement(super.provider);
@override
Asset get asset => (origin as AssetPeopleNotifierProvider).asset;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -2,6 +2,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/providers/db.provider.dart';
import 'package:isar/isar.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'asset_stack.provider.g.dart';
class AssetStackNotifier extends StateNotifier<List<Asset>> {
final Asset _asset;
@ -49,3 +52,8 @@ final assetStackProvider =
.sortByFileCreatedAtDesc()
.findAll();
});
@riverpod
int assetStackIndex(AssetStackIndexRef ref, Asset asset) {
return -1;
}

View File

@ -0,0 +1,158 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'asset_stack.provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$assetStackIndexHash() => r'0f2df55e929767c8c698bd432b5e6e351d000a16';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
/// See also [assetStackIndex].
@ProviderFor(assetStackIndex)
const assetStackIndexProvider = AssetStackIndexFamily();
/// See also [assetStackIndex].
class AssetStackIndexFamily extends Family<int> {
/// See also [assetStackIndex].
const AssetStackIndexFamily();
/// See also [assetStackIndex].
AssetStackIndexProvider call(
Asset asset,
) {
return AssetStackIndexProvider(
asset,
);
}
@override
AssetStackIndexProvider getProviderOverride(
covariant AssetStackIndexProvider provider,
) {
return call(
provider.asset,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'assetStackIndexProvider';
}
/// See also [assetStackIndex].
class AssetStackIndexProvider extends AutoDisposeProvider<int> {
/// See also [assetStackIndex].
AssetStackIndexProvider(
Asset asset,
) : this._internal(
(ref) => assetStackIndex(
ref as AssetStackIndexRef,
asset,
),
from: assetStackIndexProvider,
name: r'assetStackIndexProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$assetStackIndexHash,
dependencies: AssetStackIndexFamily._dependencies,
allTransitiveDependencies:
AssetStackIndexFamily._allTransitiveDependencies,
asset: asset,
);
AssetStackIndexProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.asset,
}) : super.internal();
final Asset asset;
@override
Override overrideWith(
int Function(AssetStackIndexRef provider) create,
) {
return ProviderOverride(
origin: this,
override: AssetStackIndexProvider._internal(
(ref) => create(ref as AssetStackIndexRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
asset: asset,
),
);
}
@override
AutoDisposeProviderElement<int> createElement() {
return _AssetStackIndexProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is AssetStackIndexProvider && other.asset == asset;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, asset.hashCode);
return _SystemHash.finish(hash);
}
}
mixin AssetStackIndexRef on AutoDisposeProviderRef<int> {
/// The parameter `asset` of this provider.
Asset get asset;
}
class _AssetStackIndexProviderElement extends AutoDisposeProviderElement<int>
with AssetStackIndexRef {
_AssetStackIndexProviderElement(super.provider);
@override
Asset get asset => (origin as AssetStackIndexProvider).asset;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -0,0 +1,44 @@
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:video_player/video_player.dart';
part 'video_player_controller_provider.g.dart';
@riverpod
Future<VideoPlayerController> videoPlayerController(
VideoPlayerControllerRef ref, {
required Asset asset,
}) async {
late VideoPlayerController controller;
if (asset.isLocal && asset.livePhotoVideoId == null) {
// Use a local file for the video player controller
final file = await asset.local!.file;
if (file == null) {
throw Exception('No file found for the video');
}
controller = VideoPlayerController.file(file);
} else {
// Use a network URL for the video player controller
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
final String videoUrl = asset.livePhotoVideoId != null
? '$serverEndpoint/asset/file/${asset.livePhotoVideoId}'
: '$serverEndpoint/asset/file/${asset.remoteId}';
final url = Uri.parse(videoUrl);
final accessToken = Store.get(StoreKey.accessToken);
controller = VideoPlayerController.networkUrl(
url,
httpHeaders: {"x-immich-user-token": accessToken},
);
}
await controller.initialize();
ref.onDispose(() {
controller.dispose();
});
return controller;
}

View File

@ -0,0 +1,164 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'video_player_controller_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$videoPlayerControllerHash() =>
r'72b45de66542021717807655e25ec92d78d80eec';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
/// See also [videoPlayerController].
@ProviderFor(videoPlayerController)
const videoPlayerControllerProvider = VideoPlayerControllerFamily();
/// See also [videoPlayerController].
class VideoPlayerControllerFamily
extends Family<AsyncValue<VideoPlayerController>> {
/// See also [videoPlayerController].
const VideoPlayerControllerFamily();
/// See also [videoPlayerController].
VideoPlayerControllerProvider call({
required Asset asset,
}) {
return VideoPlayerControllerProvider(
asset: asset,
);
}
@override
VideoPlayerControllerProvider getProviderOverride(
covariant VideoPlayerControllerProvider provider,
) {
return call(
asset: provider.asset,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'videoPlayerControllerProvider';
}
/// See also [videoPlayerController].
class VideoPlayerControllerProvider
extends AutoDisposeFutureProvider<VideoPlayerController> {
/// See also [videoPlayerController].
VideoPlayerControllerProvider({
required Asset asset,
}) : this._internal(
(ref) => videoPlayerController(
ref as VideoPlayerControllerRef,
asset: asset,
),
from: videoPlayerControllerProvider,
name: r'videoPlayerControllerProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$videoPlayerControllerHash,
dependencies: VideoPlayerControllerFamily._dependencies,
allTransitiveDependencies:
VideoPlayerControllerFamily._allTransitiveDependencies,
asset: asset,
);
VideoPlayerControllerProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.asset,
}) : super.internal();
final Asset asset;
@override
Override overrideWith(
FutureOr<VideoPlayerController> Function(VideoPlayerControllerRef provider)
create,
) {
return ProviderOverride(
origin: this,
override: VideoPlayerControllerProvider._internal(
(ref) => create(ref as VideoPlayerControllerRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
asset: asset,
),
);
}
@override
AutoDisposeFutureProviderElement<VideoPlayerController> createElement() {
return _VideoPlayerControllerProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is VideoPlayerControllerProvider && other.asset == asset;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, asset.hashCode);
return _SystemHash.finish(hash);
}
}
mixin VideoPlayerControllerRef
on AutoDisposeFutureProviderRef<VideoPlayerController> {
/// The parameter `asset` of this provider.
Asset get asset;
}
class _VideoPlayerControllerProviderElement
extends AutoDisposeFutureProviderElement<VideoPlayerController>
with VideoPlayerControllerRef {
_VideoPlayerControllerProviderElement(super.provider);
@override
Asset get asset => (origin as VideoPlayerControllerProvider).asset;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

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