Compare commits

..

1 Commits

Author SHA1 Message Date
midzelis
f068ca9911 feat: sqlite thumbnail storage 2026-02-01 18:18:35 +00:00
286 changed files with 3717 additions and 6033 deletions

View File

@@ -26,81 +26,7 @@
"vitest.explorer",
"ms-playwright.playwright",
"ms-azuretools.vscode-docker"
],
"settings": {
"tasks": {
"version": "2.0.0",
"tasks": [
{
"label": "Fix Permissions, Install Dependencies",
"type": "shell",
"command": "[ -f /immich-devcontainer/container-start.sh ] && /immich-devcontainer/container-start.sh || exit 0",
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true,
"clear": false,
"group": "Devcontainer tasks",
"close": true
},
"runOptions": {
"runOn": "default"
},
"problemMatcher": []
},
{
"label": "Immich API Server (Nest)",
"dependsOn": ["Fix Permissions, Install Dependencies"],
"type": "shell",
"command": "[ -f /immich-devcontainer/container-start-backend.sh ] && /immich-devcontainer/container-start-backend.sh || exit 0",
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true,
"clear": false,
"group": "Devcontainer tasks",
"close": true
},
"runOptions": {
"runOn": "folderOpen"
},
"problemMatcher": []
},
{
"label": "Immich Web Server (Vite)",
"dependsOn": ["Fix Permissions, Install Dependencies"],
"type": "shell",
"command": "[ -f /immich-devcontainer/container-start-frontend.sh ] && /immich-devcontainer/container-start-frontend.sh || exit 0",
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true,
"clear": false,
"group": "Devcontainer tasks",
"close": true
},
"runOptions": {
"runOn": "folderOpen"
},
"problemMatcher": []
},
{
"label": "Build Immich CLI",
"type": "shell",
"command": "pnpm --filter cli build:dev"
}
]
}
}
]
}
},
"features": {

View File

@@ -591,9 +591,9 @@ jobs:
- name: Lint with ruff
run: |
uv run ruff check --output-format=github immich_ml
- name: Format with ruff
- name: Check black formatting
run: |
uv run ruff format --check immich_ml
uv run black --check immich_ml
- name: Run mypy type checking
run: |
uv run mypy --strict immich_ml/

80
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,80 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Fix Permissions, Install Dependencies",
"type": "shell",
"command": "[ -f /immich-devcontainer/container-start.sh ] && /immich-devcontainer/container-start.sh || exit 0",
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true,
"clear": false,
"group": "Devcontainer tasks",
"close": true
},
"runOptions": {
"runOn": "default"
},
"problemMatcher": []
},
{
"label": "Immich API Server (Nest)",
"dependsOn": ["Fix Permissions, Install Dependencies"],
"type": "shell",
"command": "[ -f /immich-devcontainer/container-start-backend.sh ] && /immich-devcontainer/container-start-backend.sh || exit 0",
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true,
"clear": false,
"group": "Devcontainer tasks",
"close": true
},
"runOptions": {
"runOn": "default"
},
"problemMatcher": []
},
{
"label": "Immich Web Server (Vite)",
"dependsOn": ["Fix Permissions, Install Dependencies"],
"type": "shell",
"command": "[ -f /immich-devcontainer/container-start-frontend.sh ] && /immich-devcontainer/container-start-frontend.sh || exit 0",
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true,
"clear": false,
"group": "Devcontainer tasks",
"close": true
},
"runOptions": {
"runOn": "default"
},
"problemMatcher": []
},
{
"label": "Immich Server and Web",
"dependsOn": ["Immich Web Server (Vite)", "Immich API Server (Nest)"],
"runOptions": {
"runOn": "folderOpen"
},
"problemMatcher": []
},
{
"label": "Build Immich CLI",
"type": "shell",
"command": "pnpm --filter cli build:dev"
}
]
}

View File

@@ -23,21 +23,9 @@ We generally discourage PRs entirely generated by an LLM. For any part generated
From time to time, we put a feature freeze on parts of the codebase. For us, this means we won't accept most PRs that make changes in that area. Exempted from this are simple bug fixes that require only minor changes. We will close feature PRs that target a feature-frozen area, even if that feature is highly requested and you put a lot of work into it. Please keep that in mind, and if you're ever uncertain if a PR would be accepted, reach out to us first (e.g., in the aforementioned `#contributing` channel). We hate to throw away work. Currently, we have feature freezes on:
- Sharing/Asset ownership
- (External) libraries
* Sharing/Asset ownership
* (External) libraries
## Non-code contributions
If you want to contribute to Immich but you don't feel comfortable programming in our tech stack, there are other ways you can help the team.
### Translations
All our translations are done through [Weblate](https://hosted.weblate.org/projects/immich). These rely entirely on the community; if you speak a language that isn't fully translated yet, submitting translations there is greatly appreciated!
### Datasets
Help us improve our [Immich Datasets](https://datasets.immich.app) by submitting photos and videos taken from a variety of devices, including smartphones, DSLRs, and action cameras, as well as photos with unique features, such as panoramas, burst photos, and photo spheres. These datasets will be publically available for anyone to use, do not submit private/sensitive photos.
### Community support
If you like helping others, answering Q&A discussions here on GitHub and replying to people on our Discord is also always appreciated.
If you want to contribute to Immich but you don't feel comfortable programming in our tech stack, there are other ways you can help the team. All our translations are done through [Weblate](https://hosted.weblate.org/projects/immich). These rely entirely on the community; if you speak a language that isn't fully translated yet, submitting translations there is greatly appreciated! If you like helping others, answering Q&A discussions here on GitHub and replying to people on our Discord is also always appreciated.

View File

@@ -1,6 +1,6 @@
{
"name": "@immich/cli",
"version": "2.5.4",
"version": "2.5.2",
"description": "Command Line Interface (CLI) for Immich",
"type": "module",
"exports": "./dist/index.js",

View File

@@ -97,7 +97,7 @@ services:
command: ['./run.sh', '-disable-reporting']
ports:
- 3000:3000
image: grafana/grafana:12.3.2-ubuntu@sha256:6cca4b429a1dc0d37d401dee54825c12d40056c3c6f3f56e3f0d6318ce77749b
image: grafana/grafana:12.3.1-ubuntu@sha256:d57f1365197aec34c4d80869d8ca45bb7787c7663904950dab214dfb40c1c2fd
volumes:
- grafana-data:/var/lib/grafana

View File

@@ -140,8 +140,7 @@ For advanced users or automated recovery scenarios, you can restore a database b
```bash title='Backup'
# Replace <DB_USERNAME> with the database username - usually postgres unless you have changed it.
# Replace <DB_DATABASE_NAME> with the database name - usually immich unless you have changed it.
docker exec -t immich_postgres pg_dump --clean --if-exists --dbname=<DB_DATABASE_NAME> --username=<DB_USERNAME> | gzip > "/path/to/backup/dump.sql.gz"
docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=<DB_USERNAME> | gzip > "/path/to/backup/dump.sql.gz"
```
```bash title='Restore'
@@ -154,10 +153,9 @@ docker start immich_postgres # Start Postgres server
sleep 10 # Wait for Postgres server to start up
# Check the database user if you deviated from the default
# Replace <DB_USERNAME> with the database username - usually postgres unless you have changed it.
# Replace <DB_DATABASE_NAME> with the database name - usually immich unless you have changed it.
gunzip --stdout "/path/to/backup/dump.sql.gz" \
| sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \
| docker exec -i immich_postgres psql --dbname=<DB_DATABASE_NAME> --username=<DB_USERNAME> --single-transaction --set ON_ERROR_STOP=on # Restore Backup
| docker exec -i immich_postgres psql --dbname=postgres --username=<DB_USERNAME> # Restore Backup
docker compose up -d # Start remainder of Immich apps
```
@@ -166,8 +164,7 @@ docker compose up -d # Start remainder of Immich apps
```powershell title='Backup'
# Replace <DB_USERNAME> with the database username - usually postgres unless you have changed it.
# Replace <DB_DATABASE_NAME> with the database name - usually immich unless you have changed it.
[System.IO.File]::WriteAllLines("C:\absolute\path\to\backup\dump.sql", (docker exec -t immich_postgres pg_dump --clean --if-exists --dbname=<DB_DATABASE_NAME> --username=<DB_USERNAME>))
[System.IO.File]::WriteAllLines("C:\absolute\path\to\backup\dump.sql", (docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=<DB_USERNAME>))
```
```powershell title='Restore'
@@ -182,9 +179,8 @@ sleep 10 # Wait for Postgres server to
docker exec -it immich_postgres bash # Enter the Docker shell and run the following command
# If your backup ends in `.gz`, replace `cat` with `gunzip --stdout`
# Replace <DB_USERNAME> with the database username - usually postgres unless you have changed it.
# Replace <DB_DATABASE_NAME> with the database name - usually immich unless you have changed it.
cat "/dump.sql" | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" | psql --dbname=<DB_DATABASE_NAME> --username=<DB_USERNAME> --single-transaction --set ON_ERROR_STOP=on
cat "/dump.sql" | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" | psql --dbname=postgres --username=<DB_USERNAME>
exit # Exit the Docker shell
docker compose up -d # Start remainder of Immich apps
```
@@ -192,10 +188,6 @@ docker compose up -d # Start remainder of Immich ap
</TabItem>
</Tabs>
:::warning
The backup and restore process changed in v2.5.0, if you have a backup created with an older version of Immich, use the documentation version selector to find manual restore instructions for your backup.
:::
:::note
For the database restore to proceed properly, it requires a completely fresh install (i.e., the Immich server has never run since creating the Docker containers). If the Immich app has run, you may encounter Postgres conflicts (relation already exists, violated foreign key constraints, etc.). In this case, delete the `DB_DATA_LOCATION` folder to reset the database.
:::
@@ -204,10 +196,6 @@ For the database restore to proceed properly, it requires a completely fresh ins
Some deployment methods make it difficult to start the database without also starting the server. In these cases, set the environment variable `DB_SKIP_MIGRATIONS=true` before starting the services. This prevents the server from running migrations that interfere with the restore process. Remove this variable and restart services after the database is restored.
:::
:::tip
The provided restore process ensures your database is never in a broken state by committing all changes in one transaction. This may be undesirable behaviour in some circumstances, you can disable it by removing `--single-transaction --set ON_ERROR_STOP=on` from the command.
:::
## Filesystem
Immich stores two types of content in the filesystem: (a) original, unmodified assets (photos and videos), and (b) generated content. We recommend backing up the entire contents of `UPLOAD_LOCATION`, but only the original content is critical, which is stored in the following folders:

View File

@@ -56,13 +56,11 @@ Once you have a new OAuth client application configured, Immich can be configure
| Setting | Type | Default | Description |
| ---------------------------------------------------- | ------- | -------------------- | ----------------------------------------------------------------------------------- |
| Enabled | boolean | false | Enable/disable OAuth |
| `issuer_url` | URL | (required) | Required. Self-discovery URL for client (from previous step) |
| `client_id` | string | (required) | Required. Client ID (from previous step) |
| `client_secret` | string | (required) | Required. Client Secret (previous step) |
| `scope` | string | openid email profile | Full list of scopes to send with the request (space delimited) |
| `id_token_signed_response_alg` | string | RS256 | The algorithm used to sign the id token (examples: RS256, HS256) |
| `userinfo_signed_response_alg` | string | none | The algorithm used to sign the userinfo response (examples: RS256, HS256) |
| Request timeout | string | 30,000 (30 seconds) | Number of milliseconds to wait for http requests to complete before giving up |
| Issuer URL | URL | (required) | Required. Self-discovery URL for client (from previous step) |
| Client ID | string | (required) | Required. Client ID (from previous step) |
| Client Secret | string | (required) | Required. Client Secret (previous step) |
| Scope | string | openid email profile | Full list of scopes to send with the request (space delimited) |
| Signing Algorithm | string | RS256 | The algorithm used to sign the id token (examples: RS256, HS256) |
| Storage Label Claim | string | preferred_username | Claim mapping for the user's storage label**¹** |
| Role Claim | string | immich_role | Claim mapping for the user's role. (should return "user" or "admin")**¹** |
| Storage Quota Claim | string | immich_quota | Claim mapping for the user's storage**¹** |

View File

@@ -98,6 +98,7 @@ entryPoints:
respondingTimeouts:
readTimeout: 600s
idleTimeout: 600s
writeTimeout: 600s
```
The second part is in the `docker-compose.yml` file where immich is in. Add the Traefik specific labels like in the example.

View File

@@ -90,13 +90,10 @@ To see local changes to `@immich/ui` in Immich, do the following:
#### Setup
1. [Install mise](https://mise.jdx.dev/installing-mise.html).
2. Change to the immich (root) directory and trust the mise config with `mise trust`.
3. Install tools with mise: `mise install`.
4. Change to the `mobile/` directory.
5. Run `flutter pub get` to install the dependencies.
6. Run `make translation` to generate the translation file.
7. Run `flutter run` to start the app.
1. Setup Flutter toolchain using FVM.
2. Run `flutter pub get` to install the dependencies.
3. Run `make translation` to generate the translation file.
4. Run `fvm flutter run` to start the app.
#### Translation

View File

@@ -183,7 +183,7 @@ For example to get a list of files that would be uploaded for further
processing:
```bash
immich upload --dry-run --json-output . | tail -n +6 | jq .newFiles[]
immich upload --dry-run . | tail -n +6 | jq .newFiles[]
```
### Obtain the API Key

View File

@@ -1,7 +1,7 @@
[
{
"label": "v2.5.4",
"url": "https://docs.v2.5.4.archive.immich.app"
"label": "v2.5.2",
"url": "https://docs.v2.5.2.archive.immich.app"
},
{
"label": "v2.4.1",

View File

@@ -70,7 +70,7 @@ services:
restart: unless-stopped
redis:
image: redis:6.2-alpine@sha256:46884be93652d02a96a176ccf173d1040bef365c5706aa7b6a1931caec8bfeef
image: redis:6.2-alpine@sha256:37e002448575b32a599109664107e374c8709546905c372a34d64919043b9ceb
database:
image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:6f3e9d2c2177af16c2988ff71425d79d89ca630ec2f9c8db03209ab716542338

View File

@@ -42,7 +42,7 @@ services:
- 2285:2285
redis:
image: redis:6.2-alpine@sha256:46884be93652d02a96a176ccf173d1040bef365c5706aa7b6a1931caec8bfeef
image: redis:6.2-alpine@sha256:37e002448575b32a599109664107e374c8709546905c372a34d64919043b9ceb
database:
image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:6f3e9d2c2177af16c2988ff71425d79d89ca630ec2f9c8db03209ab716542338

View File

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

View File

@@ -473,7 +473,6 @@ describe('/asset', () => {
id: user1Assets[0].id,
exifInfo: expect.objectContaining({
dateTimeOriginal: '2023-11-20T01:11:00+00:00',
timeZone: 'UTC-7',
}),
});
expect(status).toEqual(200);

View File

@@ -1,2 +0,0 @@
export { generateMemoriesFromTimeline, generateMemory } from './memory/model-objects';
export type { MemoryConfig, MemoryYearConfig } from './memory/model-objects';

View File

@@ -1,84 +0,0 @@
import { faker } from '@faker-js/faker';
import { MemoryType, type MemoryResponseDto, type OnThisDayDto } from '@immich/sdk';
import { DateTime } from 'luxon';
import { toAssetResponseDto } from 'src/generators/timeline/rest-response';
import type { MockTimelineAsset } from 'src/generators/timeline/timeline-config';
import { SeededRandom, selectRandomMultiple } from 'src/generators/timeline/utils';
export type MemoryConfig = {
id?: string;
ownerId: string;
year: number;
memoryAt: string;
isSaved?: boolean;
};
export type MemoryYearConfig = {
year: number;
assetCount: number;
};
export function generateMemory(config: MemoryConfig, assets: MockTimelineAsset[]): MemoryResponseDto {
const now = new Date().toISOString();
const memoryId = config.id ?? faker.string.uuid();
return {
id: memoryId,
assets: assets.map((asset) => toAssetResponseDto(asset)),
data: { year: config.year } as OnThisDayDto,
memoryAt: config.memoryAt,
createdAt: now,
updatedAt: now,
isSaved: config.isSaved ?? false,
ownerId: config.ownerId,
type: MemoryType.OnThisDay,
};
}
export function generateMemoriesFromTimeline(
timelineAssets: MockTimelineAsset[],
ownerId: string,
memoryConfigs: MemoryYearConfig[],
seed: number = 42,
): MemoryResponseDto[] {
const rng = new SeededRandom(seed);
const memories: MemoryResponseDto[] = [];
const usedAssetIds = new Set<string>();
for (const config of memoryConfigs) {
const yearAssets = timelineAssets.filter((asset) => {
const assetYear = DateTime.fromISO(asset.fileCreatedAt).year;
return assetYear === config.year && !usedAssetIds.has(asset.id);
});
if (yearAssets.length === 0) {
continue;
}
const countToSelect = Math.min(config.assetCount, yearAssets.length);
const selectedAssets = selectRandomMultiple(yearAssets, countToSelect, rng);
for (const asset of selectedAssets) {
usedAssetIds.add(asset.id);
}
selectedAssets.sort(
(a, b) => DateTime.fromISO(b.fileCreatedAt).diff(DateTime.fromISO(a.fileCreatedAt)).milliseconds,
);
const memoryAt = DateTime.now().set({ year: config.year }).toISO()!;
memories.push(
generateMemory(
{
ownerId,
year: config.year,
memoryAt,
},
selectedAssets,
),
);
}
return memories;
}

View File

@@ -1,65 +0,0 @@
import type { MemoryResponseDto } from '@immich/sdk';
import { BrowserContext } from '@playwright/test';
export type MemoryChanges = {
memoryDeletions: string[];
assetRemovals: Map<string, string[]>;
};
export const setupMemoryMockApiRoutes = async (
context: BrowserContext,
memories: MemoryResponseDto[],
changes: MemoryChanges,
) => {
await context.route('**/api/memories*', async (route, request) => {
const url = new URL(request.url());
const pathname = url.pathname;
if (pathname === '/api/memories' && request.method() === 'GET') {
const activeMemories = memories
.filter((memory) => !changes.memoryDeletions.includes(memory.id))
.map((memory) => {
const removedAssets = changes.assetRemovals.get(memory.id) ?? [];
return {
...memory,
assets: memory.assets.filter((asset) => !removedAssets.includes(asset.id)),
};
})
.filter((memory) => memory.assets.length > 0);
return route.fulfill({
status: 200,
contentType: 'application/json',
json: activeMemories,
});
}
const memoryMatch = pathname.match(/\/api\/memories\/([^/]+)$/);
if (memoryMatch && request.method() === 'GET') {
const memoryId = memoryMatch[1];
const memory = memories.find((m) => m.id === memoryId);
if (!memory || changes.memoryDeletions.includes(memoryId)) {
return route.fulfill({ status: 404 });
}
const removedAssets = changes.assetRemovals.get(memoryId) ?? [];
return route.fulfill({
status: 200,
contentType: 'application/json',
json: {
...memory,
assets: memory.assets.filter((asset) => !removedAssets.includes(asset.id)),
},
});
}
if (/\/api\/memories\/([^/]+)$/.test(pathname) && request.method() === 'DELETE') {
const memoryId = pathname.split('/').pop()!;
changes.memoryDeletions.push(memoryId);
return route.fulfill({ status: 204 });
}
await route.fallback();
});
};

View File

@@ -1,289 +0,0 @@
import { faker } from '@faker-js/faker';
import type { MemoryResponseDto } from '@immich/sdk';
import { test } from '@playwright/test';
import { generateMemoriesFromTimeline } from 'src/generators/memory';
import {
Changes,
createDefaultTimelineConfig,
generateTimelineData,
TimelineAssetConfig,
TimelineData,
} from 'src/generators/timeline';
import { setupBaseMockApiRoutes } from 'src/mock-network/base-network';
import { MemoryChanges, setupMemoryMockApiRoutes } from 'src/mock-network/memory-network';
import { setupTimelineMockApiRoutes, TimelineTestContext } from 'src/mock-network/timeline-network';
import { memoryAssetViewerUtils, memoryGalleryUtils, memoryViewerUtils } from 'src/web/specs/memory/utils';
test.describe.configure({ mode: 'parallel' });
test.describe('Memory Viewer - Gallery Asset Viewer Navigation', () => {
let adminUserId: string;
let timelineRestData: TimelineData;
let memories: MemoryResponseDto[];
const assets: TimelineAssetConfig[] = [];
const testContext = new TimelineTestContext();
const changes: Changes = {
albumAdditions: [],
assetDeletions: [],
assetArchivals: [],
assetFavorites: [],
};
const memoryChanges: MemoryChanges = {
memoryDeletions: [],
assetRemovals: new Map(),
};
test.beforeAll(async () => {
adminUserId = faker.string.uuid();
testContext.adminId = adminUserId;
timelineRestData = generateTimelineData({
...createDefaultTimelineConfig(),
ownerId: adminUserId,
});
for (const timeBucket of timelineRestData.buckets.values()) {
assets.push(...timeBucket);
}
memories = generateMemoriesFromTimeline(
assets,
adminUserId,
[
{ year: 2024, assetCount: 3 },
{ year: 2023, assetCount: 2 },
{ year: 2022, assetCount: 4 },
],
42,
);
});
test.beforeEach(async ({ context }) => {
await setupBaseMockApiRoutes(context, adminUserId);
await setupTimelineMockApiRoutes(context, timelineRestData, changes, testContext);
await setupMemoryMockApiRoutes(context, memories, memoryChanges);
});
test.afterEach(() => {
testContext.slowBucket = false;
changes.albumAdditions = [];
changes.assetDeletions = [];
changes.assetArchivals = [];
changes.assetFavorites = [];
memoryChanges.memoryDeletions = [];
memoryChanges.assetRemovals.clear();
});
test.describe('Asset viewer navigation from gallery', () => {
test('shows both prev/next buttons for middle asset within a memory', async ({ page }) => {
const firstMemory = memories[0];
const middleAsset = firstMemory.assets[1];
await memoryViewerUtils.openMemoryPageWithAsset(page, middleAsset.id);
await memoryGalleryUtils.clickThumbnail(page, middleAsset.id);
await memoryAssetViewerUtils.waitForViewerOpen(page);
await memoryAssetViewerUtils.waitForAssetLoad(page, middleAsset);
await memoryAssetViewerUtils.expectPreviousButtonVisible(page);
await memoryAssetViewerUtils.expectNextButtonVisible(page);
});
test('shows next button when at last asset of first memory (next memory exists)', async ({ page }) => {
const firstMemory = memories[0];
const lastAssetOfFirstMemory = firstMemory.assets.at(-1)!;
await memoryViewerUtils.openMemoryPageWithAsset(page, lastAssetOfFirstMemory.id);
await memoryGalleryUtils.clickThumbnail(page, lastAssetOfFirstMemory.id);
await memoryAssetViewerUtils.waitForViewerOpen(page);
await memoryAssetViewerUtils.waitForAssetLoad(page, lastAssetOfFirstMemory);
await memoryAssetViewerUtils.expectNextButtonVisible(page);
await memoryAssetViewerUtils.expectPreviousButtonVisible(page);
});
test('shows prev button when at first asset of last memory (prev memory exists)', async ({ page }) => {
const lastMemory = memories.at(-1)!;
const firstAssetOfLastMemory = lastMemory.assets[0];
await memoryViewerUtils.openMemoryPageWithAsset(page, firstAssetOfLastMemory.id);
await memoryGalleryUtils.clickThumbnail(page, firstAssetOfLastMemory.id);
await memoryAssetViewerUtils.waitForViewerOpen(page);
await memoryAssetViewerUtils.waitForAssetLoad(page, firstAssetOfLastMemory);
await memoryAssetViewerUtils.expectPreviousButtonVisible(page);
await memoryAssetViewerUtils.expectNextButtonVisible(page);
});
test('can navigate from last asset of memory to first asset of next memory', async ({ page }) => {
const firstMemory = memories[0];
const secondMemory = memories[1];
const lastAssetOfFirst = firstMemory.assets.at(-1)!;
const firstAssetOfSecond = secondMemory.assets[0];
await memoryViewerUtils.openMemoryPageWithAsset(page, lastAssetOfFirst.id);
await memoryGalleryUtils.clickThumbnail(page, lastAssetOfFirst.id);
await memoryAssetViewerUtils.waitForViewerOpen(page);
await memoryAssetViewerUtils.waitForAssetLoad(page, lastAssetOfFirst);
await memoryAssetViewerUtils.clickNextButton(page);
await memoryAssetViewerUtils.waitForAssetLoad(page, firstAssetOfSecond);
await memoryAssetViewerUtils.expectCurrentAssetId(page, firstAssetOfSecond.id);
});
test('can navigate from first asset of memory to last asset of previous memory', async ({ page }) => {
const firstMemory = memories[0];
const secondMemory = memories[1];
const lastAssetOfFirst = firstMemory.assets.at(-1)!;
const firstAssetOfSecond = secondMemory.assets[0];
await memoryViewerUtils.openMemoryPageWithAsset(page, firstAssetOfSecond.id);
await memoryGalleryUtils.clickThumbnail(page, firstAssetOfSecond.id);
await memoryAssetViewerUtils.waitForViewerOpen(page);
await memoryAssetViewerUtils.waitForAssetLoad(page, firstAssetOfSecond);
await memoryAssetViewerUtils.clickPreviousButton(page);
await memoryAssetViewerUtils.waitForAssetLoad(page, lastAssetOfFirst);
});
test('hides prev button at very first asset (first memory, first asset, no prev memory)', async ({ page }) => {
const firstMemory = memories[0];
const veryFirstAsset = firstMemory.assets[0];
await memoryViewerUtils.openMemoryPageWithAsset(page, veryFirstAsset.id);
await memoryGalleryUtils.clickThumbnail(page, veryFirstAsset.id);
await memoryAssetViewerUtils.waitForViewerOpen(page);
await memoryAssetViewerUtils.waitForAssetLoad(page, veryFirstAsset);
await memoryAssetViewerUtils.expectPreviousButtonNotVisible(page);
await memoryAssetViewerUtils.expectNextButtonVisible(page);
});
test('hides next button at very last asset (last memory, last asset, no next memory)', async ({ page }) => {
const lastMemory = memories.at(-1)!;
const veryLastAsset = lastMemory.assets.at(-1)!;
await memoryViewerUtils.openMemoryPageWithAsset(page, veryLastAsset.id);
await memoryGalleryUtils.clickThumbnail(page, veryLastAsset.id);
await memoryAssetViewerUtils.waitForViewerOpen(page);
await memoryAssetViewerUtils.waitForAssetLoad(page, veryLastAsset);
await memoryAssetViewerUtils.expectNextButtonNotVisible(page);
await memoryAssetViewerUtils.expectPreviousButtonVisible(page);
});
});
test.describe('Keyboard navigation', () => {
test('ArrowLeft navigates to previous asset across memory boundary', async ({ page }) => {
const firstMemory = memories[0];
const secondMemory = memories[1];
const lastAssetOfFirst = firstMemory.assets.at(-1)!;
const firstAssetOfSecond = secondMemory.assets[0];
await memoryViewerUtils.openMemoryPageWithAsset(page, firstAssetOfSecond.id);
await memoryGalleryUtils.clickThumbnail(page, firstAssetOfSecond.id);
await memoryAssetViewerUtils.waitForViewerOpen(page);
await memoryAssetViewerUtils.waitForAssetLoad(page, firstAssetOfSecond);
await page.keyboard.press('ArrowLeft');
await memoryAssetViewerUtils.waitForAssetLoad(page, lastAssetOfFirst);
});
test('ArrowRight navigates to next asset across memory boundary', async ({ page }) => {
const firstMemory = memories[0];
const secondMemory = memories[1];
const lastAssetOfFirst = firstMemory.assets.at(-1)!;
const firstAssetOfSecond = secondMemory.assets[0];
await memoryViewerUtils.openMemoryPageWithAsset(page, lastAssetOfFirst.id);
await memoryGalleryUtils.clickThumbnail(page, lastAssetOfFirst.id);
await memoryAssetViewerUtils.waitForViewerOpen(page);
await memoryAssetViewerUtils.waitForAssetLoad(page, lastAssetOfFirst);
await page.keyboard.press('ArrowRight');
await memoryAssetViewerUtils.waitForAssetLoad(page, firstAssetOfSecond);
});
});
});
test.describe('Memory Viewer - Single Asset Memory Edge Cases', () => {
let adminUserId: string;
let timelineRestData: TimelineData;
let memories: MemoryResponseDto[];
const assets: TimelineAssetConfig[] = [];
const testContext = new TimelineTestContext();
const changes: Changes = {
albumAdditions: [],
assetDeletions: [],
assetArchivals: [],
assetFavorites: [],
};
const memoryChanges: MemoryChanges = {
memoryDeletions: [],
assetRemovals: new Map(),
};
test.beforeAll(async () => {
adminUserId = faker.string.uuid();
testContext.adminId = adminUserId;
timelineRestData = generateTimelineData({
...createDefaultTimelineConfig(),
ownerId: adminUserId,
});
for (const timeBucket of timelineRestData.buckets.values()) {
assets.push(...timeBucket);
}
memories = generateMemoriesFromTimeline(
assets,
adminUserId,
[
{ year: 2024, assetCount: 2 },
{ year: 2023, assetCount: 1 },
{ year: 2022, assetCount: 2 },
],
123,
);
});
test.beforeEach(async ({ context }) => {
await setupBaseMockApiRoutes(context, adminUserId);
await setupTimelineMockApiRoutes(context, timelineRestData, changes, testContext);
await setupMemoryMockApiRoutes(context, memories, memoryChanges);
});
test.afterEach(() => {
testContext.slowBucket = false;
changes.albumAdditions = [];
changes.assetDeletions = [];
changes.assetArchivals = [];
changes.assetFavorites = [];
memoryChanges.memoryDeletions = [];
memoryChanges.assetRemovals.clear();
});
test('single asset memory shows both prev/next when surrounded by other memories', async ({ page }) => {
const singleAssetMemory = memories[1];
const singleAsset = singleAssetMemory.assets[0];
await memoryViewerUtils.openMemoryPageWithAsset(page, singleAsset.id);
await memoryGalleryUtils.clickThumbnail(page, singleAsset.id);
await memoryAssetViewerUtils.waitForViewerOpen(page);
await memoryAssetViewerUtils.waitForAssetLoad(page, singleAsset);
await memoryAssetViewerUtils.expectPreviousButtonVisible(page);
await memoryAssetViewerUtils.expectNextButtonVisible(page);
});
});

View File

@@ -1,123 +0,0 @@
import type { AssetResponseDto } from '@immich/sdk';
import { expect, Page } from '@playwright/test';
function getAssetIdFromUrl(url: URL): string | null {
const pathMatch = url.pathname.match(/\/memory\/photos\/([^/]+)/);
if (pathMatch) {
return pathMatch[1];
}
return url.searchParams.get('id');
}
export const memoryViewerUtils = {
locator(page: Page) {
return page.locator('#memory-viewer');
},
async waitForMemoryLoad(page: Page) {
await expect(this.locator(page)).toBeVisible();
await expect(page.locator('#memory-viewer img').first()).toBeVisible();
},
async openMemoryPage(page: Page) {
await page.goto('/memory');
await this.waitForMemoryLoad(page);
},
async openMemoryPageWithAsset(page: Page, assetId: string) {
await page.goto(`/memory?id=${assetId}`);
await this.waitForMemoryLoad(page);
},
};
export const memoryGalleryUtils = {
locator(page: Page) {
return page.locator('#gallery-memory');
},
thumbnailWithAssetId(page: Page, assetId: string) {
return page.locator(`#gallery-memory [data-thumbnail-focus-container][data-asset="${assetId}"]`);
},
async scrollToGallery(page: Page) {
const showGalleryButton = page.getByLabel('Show gallery');
if (await showGalleryButton.isVisible()) {
await showGalleryButton.click();
}
await expect(this.locator(page)).toBeInViewport();
},
async clickThumbnail(page: Page, assetId: string) {
await this.scrollToGallery(page);
await this.thumbnailWithAssetId(page, assetId).click();
},
async getAllThumbnails(page: Page) {
await this.scrollToGallery(page);
return page.locator('#gallery-memory [data-thumbnail-focus-container]');
},
};
export const memoryAssetViewerUtils = {
locator(page: Page) {
return page.locator('#immich-asset-viewer');
},
async waitForViewerOpen(page: Page) {
await expect(this.locator(page)).toBeVisible();
},
async waitForAssetLoad(page: Page, asset: AssetResponseDto) {
const viewer = this.locator(page);
const imgLocator = viewer.locator(`img[draggable="false"][src*="/api/assets/${asset.id}/thumbnail?size=preview"]`);
const videoLocator = viewer.locator(`video[poster*="/api/assets/${asset.id}/thumbnail?size=preview"]`);
await imgLocator.or(videoLocator).waitFor({ timeout: 10_000 });
},
nextButton(page: Page) {
return page.getByLabel('View next asset');
},
previousButton(page: Page) {
return page.getByLabel('View previous asset');
},
async expectNextButtonVisible(page: Page) {
await expect(this.nextButton(page)).toBeVisible();
},
async expectNextButtonNotVisible(page: Page) {
await expect(this.nextButton(page)).toHaveCount(0);
},
async expectPreviousButtonVisible(page: Page) {
await expect(this.previousButton(page)).toBeVisible();
},
async expectPreviousButtonNotVisible(page: Page) {
await expect(this.previousButton(page)).toHaveCount(0);
},
async clickNextButton(page: Page) {
await this.nextButton(page).click();
},
async clickPreviousButton(page: Page) {
await this.previousButton(page).click();
},
async closeViewer(page: Page) {
await page.keyboard.press('Escape');
await expect(this.locator(page)).not.toBeVisible();
},
getCurrentAssetId(page: Page): string | null {
const url = new URL(page.url());
return getAssetIdFromUrl(url);
},
async expectCurrentAssetId(page: Page, expectedAssetId: string) {
await expect.poll(() => this.getCurrentAssetId(page)).toBe(expectedAssetId);
},
};

View File

@@ -5,7 +5,7 @@
"acknowledge": "أُدرك ذلك",
"action": "عملية",
"action_common_update": "تحديث",
"action_description": "مجموعة من الفعاليات التي ستنفذ على الأصول التي تم تصفيتها",
"action_description": "مجموعة من الفعاليات التي يجب تنفيذها على الأصول التي تم تصفيتها",
"actions": "عمليات",
"active": "نشط",
"active_count": "فعال: {count}",
@@ -272,7 +272,7 @@
"oauth_auto_register": "التسجيل التلقائي",
"oauth_auto_register_description": "التسجيل التلقائي للمستخدمين الجدد بعد تسجيل الدخول باستخدام OAuth",
"oauth_button_text": "نص الزر",
"oauth_client_secret_description": "مطلوب للعميل السري، او اذا PKCE(مفتاح الاثبات لتبادل الكود) ليس مدعوم من العميل العام.",
"oauth_client_secret_description": "مطلوب اذاPKCE(مفتاح الاثبات لتبادل الكود) لم يتم توفيره من مزود OAuth",
"oauth_enable_description": "تسجيل الدخول باستخدام OAuth",
"oauth_mobile_redirect_uri": "عنوان URI لإعادة التوجيه على الهاتف",
"oauth_mobile_redirect_uri_override": "تجاوز عنوان URI لإعادة التوجيه على الهاتف",
@@ -572,9 +572,6 @@
"asset_list_layout_sub_title": "تصميم",
"asset_list_settings_subtitle": "إعدادات تخطيط شبكة الصور",
"asset_list_settings_title": "شبكة الصور",
"asset_not_found_on_device_android": "الاصل لم يتم ايجاده في الجهاز",
"asset_not_found_on_device_ios": "الأصل لم يتم ايجاده في الجهاز. اذا تستخدم خدمة iCloud, فالأصل قد لا يتم الوصول له بسبب ملف متضارب مخزون في iCloud",
"asset_not_found_on_icloud": "الأصل لم يتم ايجاده في الجهاز, الأصل قد لا يتم الوصول له بسبب ملف متضارب مخزون في iCloud",
"asset_offline": "المحتوى غير اتصال",
"asset_offline_description": "لم يعد هذا الأصل الخارجي موجودًا على القرص. يرجى الاتصال بمسؤول Immich للحصول على المساعدة.",
"asset_restored_successfully": "تم استعادة الاصل بنجاح",
@@ -653,7 +650,7 @@
"backup_controller_page_background_turn_off": "قم بإيقاف تشغيل خدمة الخلفية",
"backup_controller_page_background_turn_on": "قم بتشغيل خدمة الخلفية",
"backup_controller_page_background_wifi": "فقط على Wi-Fi",
"backup_controller_page_backup": "نسخ احتياطي",
"backup_controller_page_backup": "دعم",
"backup_controller_page_backup_selected": "المحدد: ",
"backup_controller_page_backup_sub": "النسخ الاحتياطي للصور ومقاطع الفيديو",
"backup_controller_page_created": "انشئ في :{date}",
@@ -1195,6 +1192,7 @@
"features": "الميزات",
"features_in_development": "الميزات قيد التطوير",
"features_setting_description": "إدارة ميزات التطبيق",
"file_name": "اسم الملف: {file_name}",
"file_name_or_extension": "اسم الملف أو امتداده",
"file_size": "حجم الملف",
"filename": "اسم الملف",
@@ -2297,7 +2295,6 @@
"upload_details": "تفاصيل الرفع",
"upload_dialog_info": "هل تريد النسخ الاحتياطي للأصول (الأصول) المحددة إلى الخادم؟",
"upload_dialog_title": "تحميل الأصول",
"upload_error_with_count": "خطأ في رفع {count, plural, one {# اصل} other {# اصول}}",
"upload_errors": "إكتمل الرفع مع {count, plural, one {# خطأ} other {# أخطاء}}, قم بتحديث الصفحة لرؤية المحتويات الجديدة التي تم رفعها.",
"upload_finished": "تم الانتهاء من الرفع",
"upload_progress": "متبقية {remaining, number} - معالجة {processed, number}/{total, number}",

View File

@@ -380,6 +380,7 @@
"favorite": "У абраным",
"favorite_or_unfavorite_photo": "Дадаць або выдаліць фота з абранага",
"favorites": "Абраныя",
"file_name": "Назва файла: {file_name}",
"filename": "Назва файла",
"filetype": "Тып файла",
"filter": "Фільтр",

View File

@@ -272,7 +272,7 @@
"oauth_auto_register": "Автоматична регистрация",
"oauth_auto_register_description": "Автоматично регистриране на нови потребители след влизане с OAuth",
"oauth_button_text": "Текст на бутона",
"oauth_client_secret_description": "Задължително за поверителен клиент или когато PKCE (Proof Key for Code Exchange) не се поддържа за публичен клиент.",
"oauth_client_secret_description": "Изисква се, когато доставчика на OAuth не поддържа PKCE (Proof Key for Code Exchange)",
"oauth_enable_description": "Влизане с OAuth",
"oauth_mobile_redirect_uri": "URI за мобилно пренасочване",
"oauth_mobile_redirect_uri_override": "URI пренасочване за мобилни устройства",
@@ -383,7 +383,7 @@
"transcoding_hardware_acceleration": "Хардуерно ускорение",
"transcoding_hardware_acceleration_description": "Експериментално: много по-бързо транскодиране, но може да понижи качеството при същия битрейт",
"transcoding_hardware_decoding": "Хардуерно декодиране",
"transcoding_hardware_decoding_setting_description": "Активира ускорение от край до край, вместо само да ускорява кодирането. Може да не работи с всички видеоклипове.",
"transcoding_hardware_decoding_setting_description": "Прилага се само за NVENC, QSV и RKMPP. Активира ускорение от край до край, вместо само да ускорява кодирането. Може да не работи с всички видеоклипове.",
"transcoding_max_b_frames": "Максимални B-фрейма",
"transcoding_max_b_frames_description": "По-високите стойности подобряват ефективността на компресията, но забавят разкодирането. Може да не е съвместим с хардуерното ускорение на по-стари устройства. 0 деактивира B-фрейма, докато -1 задава тази стойност автоматично.",
"transcoding_max_bitrate": "Максимален битрейт",
@@ -572,9 +572,6 @@
"asset_list_layout_sub_title": "Разположение",
"asset_list_settings_subtitle": "Настройки на мрежата на разполагане на снимки",
"asset_list_settings_title": "Разполагане на снимки",
"asset_not_found_on_device_android": "Активът не е намерен на устройството",
"asset_not_found_on_device_ios": "Обектът не е намерен на устройството. Ако използвате iCloud, обектът може да е недостъпен поради повреден файл, съхранен в iCloud",
"asset_not_found_on_icloud": "Обектът не е намерен в iCloud. Обектът може да е недостъпен поради повреден файл, съхранен в iCloud",
"asset_offline": "Елементът е офлайн",
"asset_offline_description": "Този външен актив вече не се намира на диска. Моля, свържете се с администратора на Immich за помощ.",
"asset_restored_successfully": "Успешно възстановен обект",
@@ -1152,7 +1149,7 @@
},
"errors_text": "Грешки",
"exclusion_pattern": "Шаблон за изключение",
"exif": "Еxif",
"exif": "Exif",
"exif_bottom_sheet_description": "Добави Описание...",
"exif_bottom_sheet_description_error": "Неуспешно обновяване на описание",
"exif_bottom_sheet_details": "ПОДРОБНОСТИ",
@@ -1195,6 +1192,7 @@
"features": "Функции",
"features_in_development": "Функции в процес на разработка",
"features_setting_description": "Управление на функциите на приложението",
"file_name": "Име на файла: {file_name}",
"file_name_or_extension": "Име на файл или разширение",
"file_size": "Размер на файла",
"filename": "Име на файл",
@@ -1217,7 +1215,7 @@
"free_up_space_description": "Преместете архивираните снимки и видеа в кошчето на устройството, за да освободите място. Копията на сървъра ще бъдат запазени.",
"free_up_space_settings_subtitle": "Освобождаване на място за съхранение на устройството",
"full_path": "Пълен път: {path}",
"gcast_enabled": "Gооgle Cast",
"gcast_enabled": "Google Cast",
"gcast_enabled_description": "За да работи тази функция зарежда външни ресурси от Google.",
"general": "Общи",
"geolocation_instruction_location": "Изберете обект с GPS координати за да използвате тях или изберете място директно от картата",
@@ -1406,7 +1404,7 @@
"login_form_api_exception": "Грешка в комуникацията. Моля, провери URL на сървъра и опитай пак.",
"login_form_back_button_text": "Обратно",
"login_form_email_hint": "youremail@email.com",
"login_form_endpoint_hint": "http://yоur-server-ip:port",
"login_form_endpoint_hint": "http://your-server-ip:port",
"login_form_endpoint_url": "URL адрес на сървъра",
"login_form_err_http": "Моля, определи протокола http:// или https://",
"login_form_err_invalid_email": "Невалиден имейл адрес",
@@ -2297,7 +2295,6 @@
"upload_details": "Детайли за качването",
"upload_dialog_info": "Искате ли да архивирате на сървъра избраните обекти?",
"upload_dialog_title": "Качи обект",
"upload_error_with_count": "Грешка при зареждане на {count, plural, one {# обект} other {# обекта}}",
"upload_errors": "Качването е завъшено с {count, plural, one {# грешка} other {# грешки}}, обновете страницата за да видите новите елементи.",
"upload_finished": "Качването завърши",
"upload_progress": "Остават {remaining, number} - Обработени {processed, number}/{total, number}",

View File

@@ -40,9 +40,7 @@
"add_to_albums_count": "অ্যালবামে যোগ করুন ({count})",
"add_to_bottom_bar": "এ যোগ করুন",
"add_to_shared_album": "শেয়ার করা অ্যালবামে যোগ করুন",
"add_upload_to_stack": "আপলোড স্ট্যাকে যোগ করুন",
"add_url": "লিঙ্ক যোগ করুন",
"add_workflow_step": "কাজের ধাপ যোগ করুন",
"added_to_archive": "আর্কাইভ এ যোগ করা হয়েছে",
"added_to_favorites": "ফেভারিটে যোগ করা হয়েছে",
"added_to_favorites_count": "পছন্দের তালিকায় {count, number} যোগ করা হয়েছে",
@@ -75,7 +73,6 @@
"confirm_reprocess_all_faces": "আপনি কি নিশ্চিত যে আপনি সমস্ত মুখ পুনরায় প্রক্রিয়া করতে চান? এটি নামযুক্ত ব্যক্তিদেরও মুছে ফেলবে।",
"confirm_user_password_reset": "আপনি কি নিশ্চিত যে আপনি {user} এর পাসওয়ার্ড রিসেট করতে চান?",
"confirm_user_pin_code_reset": "আপনি কি নিশ্চিত যে আপনি {user} এর পিন কোড রিসেট করতে চান?",
"copy_config_to_clipboard_description": "বর্তমান সিস্টেম কনফিগারেশন একটি JSON অবজেক্ট হিসেবে ক্লিপবোর্ডে কপি করুন",
"create_job": "job তৈরি করুন",
"cron_expression": "ক্রোন এক্সপ্রেশন",
"cron_expression_description": "ক্রোন ফর্ম্যাট ব্যবহার করে স্ক্যানিং ব্যবধান সেট করুন। আরও তথ্যের জন্য দয়া করে দেখুন যেমন <link>Crontab Guru</link>",
@@ -83,8 +80,6 @@
"disable_login": "লগইন অক্ষম করুন",
"duplicate_detection_job_description": "অনুরূপ ছবি সনাক্ত করতে সম্পদগুলিতে মেশিন লার্নিং চালান। স্মার্ট অনুসন্ধানের উপর নির্ভর করে",
"exclusion_pattern_description": "এক্সক্লুশন প্যাটার্ন ব্যবহার করে আপনি আপনার লাইব্রেরি স্ক্যান করার সময় ফাইল এবং ফোল্ডারগুলিকে উপেক্ষা করতে পারবেন। যদি আপনার এমন ফোল্ডার থাকে যেখানে এমন ফাইল থাকে যা আপনি আমদানি করতে চান না, যেমন RAW ফাইল।",
"export_config_as_json_description": "বর্তমান সিস্টেম কনফিগারেশন একটি JSON ফাইল হিসেবে ডাউনলোড করুন",
"external_libraries_page_description": "অ্যাডমিন external লাইব্রেরি পেজ",
"face_detection": "মুখ সনাক্তকরণ",
"face_detection_description": "মেশিন লার্নিং ব্যবহার করে অ্যাসেটে থাকা মুখ/চেহারা গুলি সনাক্ত করুন। ভিডিও গুলির জন্য, শুধুমাত্র থাম্বনেইল বিবেচনা করা হয়। \"রিফ্রেশ\" (পুনরায়) সমস্ত অ্যাসেট প্রক্রিয়া করে। \"রিসেট\" করার মাধ্যমে অতিরিক্তভাবে সমস্ত বর্তমান মুখের ডেটা সাফ করে। \"অনুপস্থিত\" অ্যাসেটগুলিকে সারিবদ্ধ করে যা এখনও প্রক্রিয়া করা হয়নি। সনাক্ত করা মুখগুলিকে ফেসিয়াল রিকগনিশনের জন্য সারিবদ্ধ করা হবে, ফেসিয়াল ডিটেকশন সম্পূর্ণ হওয়ার পরে, বিদ্যমান বা নতুন ব্যক্তিদের মধ্যে গোষ্ঠীবদ্ধ করে।",
"facial_recognition_job_description": "শনাক্ত করা মুখগুলিকে মানুষের মধ্যে গোষ্ঠীভুক্ত/গ্রুপ করুন। মুখ সনাক্তকরণ সম্পূর্ণ হওয়ার পরে এই ধাপটি চলে। \"রিসেট\" (পুনরায়) সমস্ত মুখকে ক্লাস্টার করে। \"অনুপস্থিত/মিসিং\" মুখগুলিকে সারিতে রাখে যেগুলো কোনও ব্যক্তিকে এসাইন/বরাদ্দ করা হয়নি।",
@@ -104,8 +99,6 @@
"image_preview_description": "স্ট্রিপড মেটাডেটা সহ মাঝারি আকারের ছবি, একটি একক সম্পদ দেখার সময় এবং মেশিন লার্নিংয়ের জন্য ব্যবহৃত হয়",
"image_preview_quality_description": "১-১০০ এর মধ্যে প্রিভিউ কোয়ালিটি। বেশি হলে ভালো, কিন্তু বড় ফাইল তৈরি হয় এবং অ্যাপের প্রতিক্রিয়াশীলতা কমাতে পারে। কম মান সেট করলে মেশিন লার্নিং কোয়ালিটির উপর প্রভাব পড়তে পারে।",
"image_preview_title": "প্রিভিউ সেটিংস",
"image_progressive": "প্রগ্রেসিভ",
"image_progressive_description": "ধীরে ধীরে লোড হওয়ার সুবিধার্থে JPEG ছবিগুলো প্রগ্রেসিভভাবে এনকোড করুন। WebP ছবির ক্ষেত্রে এটি কোনো প্রভাব ফেলবে না",
"image_quality": "গুণমান",
"image_resolution": "রেজোলিউশন",
"image_resolution_description": "উচ্চ রেজোলিউশনের ক্ষেত্রে আরও বিস্তারিত তথ্য সংরক্ষণ করা সম্ভব কিন্তু এনকোড করতে বেশি সময় লাগে, ফাইলের আকার বড় হয় এবং অ্যাপের প্রতিক্রিয়াশীলতা কমাতে পারে।",
@@ -114,7 +107,6 @@
"image_thumbnail_description": "মেটাডেটা বাদ দেওয়া ছোট থাম্বনেইল, মূল টাইমলাইনের মতো ছবির গ্রুপ দেখার সময় ব্যবহৃত হয়",
"image_thumbnail_quality_description": "থাম্বনেইলের মান ১-১০০। বেশি হলে ভালো, কিন্তু বড় ফাইল তৈরি হয় এবং অ্যাপের প্রতিক্রিয়াশীলতা কমাতে পারে।",
"image_thumbnail_title": "থাম্বনেল সেটিংস",
"import_config_from_json_description": "একটি JSON কনফিগ ফাইল আপলোড করে সিস্টেম কনফিগারেশন ইমপোর্ট করুন।",
"job_concurrency": "{job} কনকারেন্সি",
"job_created": "Job তৈরি হয়েছে",
"job_not_concurrency_safe": "এই কাজটি সমান্তরালভাবে চালানো নিরাপদ নয়",
@@ -122,20 +114,14 @@
"job_settings_description": "কাজের সমান্তরালতা পরিচালনা করুন",
"jobs_delayed": "{jobCount, plural, other {# বিলম্বিত}}",
"jobs_failed": "{jobCount, plural, other {# ব্যর্থ}}",
"jobs_over_time": "সময় অনুযায়ী কাজসমূহ",
"library_created": "লাইব্রেরি তৈরি করা হয়েছেঃ {library}",
"library_deleted": "লাইব্রেরি মুছে ফেলা হয়েছে",
"library_details": "লাইব্রেরির বিবরণ",
"library_folder_description": "ইমপোর্ট করার জন্য একটি ফোল্ডার নির্দিষ্ট করুন। এই ফোল্ডার এবং এর ভেতরের সমস্ত ফোল্ডার ছবি ও ভিডিওর জন্য স্ক্যান করা হবে।",
"library_remove_exclusion_pattern_prompt": "আপনি কি নিশ্চিত যে আপনি এই এক্সক্লুশন প্যাটার্নটি মুছে ফেলতে চান?",
"library_remove_folder_prompt": "আপনি কি নিশ্চিত যে আপনি এই ইমপোর্ট ফোল্ডারটি মুছে ফেলতে চান?",
"library_scanning": "পর্যায়ক্রমিক স্ক্যানিং",
"library_scanning_description": "পর্যায়ক্রমিক লাইব্রেরি স্ক্যানিং কনফিগার করুন",
"library_scanning_enable_description": "পর্যায়ক্রমিক লাইব্রেরি স্ক্যানিং সক্ষম করুন",
"library_settings": "বহিরাগত লাইব্রেরি",
"library_settings_description": "বহিরাগত লাইব্রেরি সেটিংস পরিচালনা করুন",
"library_tasks_description": "নতুন এবং/অথবা পরিবর্তিত সম্পদের জন্য বহিরাগত লাইব্রেরি স্ক্যান করুন",
"library_updated": "আপডেটকৃত লাইব্রেরি।",
"library_watching_enable_description": "ফাইল পরিবর্তনের জন্য বহিরাগত লাইব্রেরিগুলি দেখুন",
"library_watching_settings": "লাইব্রেরি দেখা (পরীক্ষামূলক)",
"library_watching_settings_description": "পরিবর্তিত ফাইলগুলির জন্য স্বয়ংক্রিয়ভাবে নজর রাখুন",
@@ -147,190 +133,9 @@
"machine_learning_availability_checks_enabled": "প্রাপ্যতা পরীক্ষা সক্ষম করুন",
"machine_learning_availability_checks_interval": "চেক ব্যবধান",
"machine_learning_availability_checks_interval_description": "প্রাপ্যতা পরীক্ষাগুলির মধ্যে ব্যবধান মিলিসেকেন্ডে",
"machine_learning_availability_checks_timeout": "অনুরোধের সময়সীমা শেষ",
"machine_learning_availability_checks_timeout_description": "প্রাপ্যতার পরীক্ষার জন্য মিলিসেকেন্ডে সময়সীমা।",
"machine_learning_clip_model": "CLIP মডেল",
"machine_learning_clip_model_description": "<link>এখানে</link> তালিকাভুক্ত একটি CLIP মডেলের নাম। মনে রাখবেন, মডেল পরিবর্তনের পর সব ছবির জন্য অবশ্যই Smart Search কাজটি আবার চালাতে হবে।",
"machine_learning_duplicate_detection": "পুনরাবৃত্তি সনাক্তকরণ",
"machine_learning_duplicate_detection_enabled": "পুনরাবৃত্তি শনাক্তকরণ চালু করুন",
"machine_learning_duplicate_detection_enabled_description": "নিষ্ক্রিয় থাকলেও হুবহু একই সম্পদগুলোর ডুপ্লিকেট সরিয়ে ফেলা হবে।",
"machine_learning_duplicate_detection_setting_description": "সম্ভাব্য ডুপ্লিকেট খুঁজে বের করতে CLIP এম্বেডিং ব্যবহার করুন।",
"machine_learning_enabled": "Machine Learning সক্ষম করুন",
"machine_learning_enabled_description": "নিষ্ক্রিয় থাকলে নিচের সেটিংস নির্বিশেষে সমস্ত ML বৈশিষ্ট্য নিষ্ক্রিয় করা হবে।",
"machine_learning_facial_recognition": "ফেসিয়াল রিকগনিশন",
"machine_learning_facial_recognition_description": "ছবিতে মুখ সনাক্ত করুন, চিনুন এবং গ্রুপ করুন।",
"machine_learning_facial_recognition_model": "ফেসিয়াল রিকগনিশন মডেল",
"machine_learning_facial_recognition_model_description": "মডেলগুলি আকারের অধঃক্রম অনুযায়ী তালিকাভুক্ত করা হয়েছে। বড় মডেলগুলি ধীরগতির এবং বেশি মেমরি ব্যবহার করে, তবে উন্নত ফলাফল প্রদান করে। মনে রাখবেন যে একটি মডেল পরিবর্তন করার পর আপনাকে সমস্ত ছবির জন্য ফেস ডিটেকশন (Face Detection) কাজটি পুনরায় চালাতে হবে।",
"machine_learning_facial_recognition_setting": "ফেসিয়াল রিকগনিশন সক্ষম করুন",
"machine_learning_facial_recognition_setting_description": "নিষ্ক্রিয় থাকলে, ফেসিয়াল রিকগনিশনের জন্য ছবিগুলো এনকোড করা হবে না এবং এক্সপ্লোর পেজের পিপল (People) সেকশনটি পূর্ণ হবে না।",
"machine_learning_max_detection_distance": "সর্বোচ্চ শনাক্তকরণ দূরত্ব",
"machine_learning_max_detection_distance_description": "দুটি ছবিকে ডুপ্লিকেট হিসেবে গণ্য করার জন্য তাদের মধ্যকার সর্বোচ্চ দূরত্ব, যার পরিসীমা .০০১-.১। মান যত বেশি হবে তত বেশি ডুপ্লিকেট শনাক্ত হবে, তবে এতে ভুল শনাক্তকরণের (false positives) সম্ভাবনা থাকতে পারে।",
"machine_learning_max_recognition_distance": "সর্বোচ্চ চিহ্নিতকরণ দূরত্ব",
"machine_learning_max_recognition_distance_description": "দুটি মুখকে একই ব্যক্তি হিসেবে গণ্য করার জন্য তাদের মধ্যকার সর্বোচ্চ দূরত্ব, যার পরিসীমা -২। এই মান কমালে দু’জন ভিন্ন ব্যক্তিকে একই ব্যক্তি হিসেবে চিহ্নিত করার সম্ভাবনা কমে, আর মান বাড়ালে একই ব্যক্তিকে দু’জন ভিন্ন ব্যক্তি হিসেবে চিহ্নিত করার সম্ভাবনা কমে। মনে রাখবেন যে, দু’জন ব্যক্তিকে একত্রিত করা (merge) অপেক্ষাকৃত সহজ কিন্তু একজনকে দু’ভাগে ভাগ করা কঠিন, তাই সম্ভব হলে থ্রেশহোল্ড (threshold) কম রাখাই ভালো।",
"machine_learning_min_detection_score": "সর্বনিম্ন শনাক্তকরণ স্কোর",
"machine_learning_min_detection_score_description": "ছবিতে মুখ শনাক্ত করার জন্য -১ এর মধ্যে সর্বনিম্ন কনফিডেন্স স্কোর। মান যত কম হবে তত বেশি মুখ শনাক্ত হবে, তবে এতে ভুল শনাক্তকরণের (false positives) সম্ভাবনা থাকতে পারে।",
"machine_learning_min_recognized_faces": "সর্বনিম্ন স্বীকৃত মুখের সংখ্যা",
"machine_learning_min_recognized_faces_description": "একজন ব্যক্তি হিসেবে তৈরি হওয়ার জন্য স্বীকৃত মুখের সর্বনিম্ন সংখ্যা। এটি বাড়ালে ফেসিয়াল রিকগনিশন আরও নিখুঁত হয়, তবে এতে কোনো মুখ কোনো ব্যক্তির সাথে সংযুক্ত না হওয়ার সম্ভাবনাও বৃদ্ধি পায়।",
"machine_learning_ocr": "OCR",
"machine_learning_ocr_description": "ছবিতে টেক্সট (Text) শনাক্ত করতে মেশিন লার্নিং ব্যবহার করুন।",
"machine_learning_ocr_enabled": "OCR সক্ষম করুন",
"machine_learning_ocr_enabled_description": "নিষ্ক্রিয় থাকলে, ছবিগুলোতে টেক্সট শনাক্তকরণ করা হবে না।",
"machine_learning_ocr_max_resolution": "সর্বোচ্চ রেজোলিউশন(Resolution)",
"machine_learning_ocr_max_resolution_description": "এই রেজোলিউশনের উপরের প্রিভিউগুলোর অ্যাসপেক্ট রেশিও (আকার ও অনুপাত) ঠিক রেখে রিসাইজ করা হবে। মান যত বেশি হবে ফলাফল তত বেশি নিখুঁত হবে, তবে এটি প্রসেস করতে সময় বেশি লাগবে এবং মেমরি বেশি ব্যবহার করবে।",
"machine_learning_ocr_min_detection_score": "সর্বনিম্ন শনাক্তকরণ স্কোর",
"machine_learning_ocr_min_detection_score_description": "টেক্সট শনাক্ত করার জন্য -১ এর মধ্যে ন্যূনতম কনফিডেন্স স্কোর। মান যত কম হবে তত বেশি টেক্সট শনাক্ত হবে, তবে এতে ভুল শনাক্তকরণের (false positives) সম্ভাবনা থাকতে পারে।",
"machine_learning_ocr_min_recognition_score": "সর্বনিম্ন চিহ্নিতকরণ (Recognition)স্কোর",
"machine_learning_ocr_min_score_recognition_description": "শনাক্তকৃত টেক্সট চিহ্নিত করার জন্য -১ এর মধ্যে ন্যূনতম কনফিডেন্স স্কোর। মান যত কম হবে তত বেশি টেক্সট চিহ্নিত হবে, তবে এতে ভুল শনাক্তকরণের (false positives) সম্ভাবনা থাকতে পারে।",
"machine_learning_ocr_model": "OCR মডেল",
"machine_learning_ocr_model_description": "সার্ভার মডেলগুলো মোবাইল মডেলের তুলনায় বেশি নির্ভুল, তবে এগুলো প্রসেস করতে সময় বেশি লাগে এবং মেমরি বেশি ব্যবহার করে।",
"machine_learning_settings": "মেশিন লার্নিং সেটিংস (Machine Learning Settings)",
"machine_learning_settings_description": "মেশিন লার্নিং বৈশিষ্ট্য এবং সেটিংস পরিচালনা করুন",
"machine_learning_smart_search": "স্মার্ট সার্চ (Smart Search)",
"machine_learning_smart_search_description": "CLIP এমবেডিং (embeddings) ব্যবহার করে ছবির বিষয়বস্তু অনুযায়ী অনুসন্ধান করুন",
"machine_learning_smart_search_enabled": "স্মার্ট সার্চ সক্ষম করুন",
"machine_learning_smart_search_enabled_description": "নিষ্ক্রিয় থাকলে, স্মার্ট সার্চের জন্য ছবিগুলো এনকোড (encode) করা হবে না।",
"machine_learning_url_description": "মেশিন লার্নিং সার্ভারের URL। যদি একের বেশি URL প্রদান করা হয়, তবে একটি সফলভাবে সাড়া না দেওয়া পর্যন্ত প্রতিটি সার্ভারে এক এক করে চেষ্টা করা হবে (প্রথম থেকে শেষ ক্রমানুসারে)। যে সার্ভারগুলো সাড়া দেবে না, সেগুলো পুনরায় সচল হওয়া পর্যন্ত সাময়িকভাবে উপেক্ষা করা হবে।",
"maintenance_delete_backup": "ব্যাকআপ (Backup)মুছুন",
"maintenance_delete_backup_description": "এই ফাইলটি চিরতরে মুছে ফেলা হবে।",
"maintenance_delete_error": "ব্যাকআপ মুছতে ব্যর্থ হয়েছে।",
"maintenance_restore_backup": "ব্যাকআপ পুনরুদ্ধার(Restore) করুন",
"maintenance_restore_backup_description": "Immich মুছে ফেলা হবে এবং নির্বাচিত ব্যাকআপ থেকে পুনরুদ্ধার করা হবে। কার্যক্রম চালিয়ে যাওয়ার আগে একটি ব্যাকআপ তৈরি করা হবে।",
"maintenance_restore_backup_different_version": "এই ব্যাকআপটি Immich-এর একটি ভিন্ন সংস্করণের মাধ্যমে তৈরি করা হয়েছিল!",
"maintenance_restore_backup_unknown_version": "ব্যাকআপ সংস্করণ নির্ধারণ করা সম্ভব হয়নি।",
"maintenance_restore_database_backup": "ডেটাবেস ব্যাকআপ পুনরুদ্ধার করুন",
"maintenance_restore_database_backup_description": "একটি ব্যাকআপ ফাইল ব্যবহার করে ডেটাবেসকে পূর্ববর্তী অবস্থায় ফিরিয়ে আনুন।",
"maintenance_settings": "রক্ষণাবেক্ষণ (Maintenance)",
"maintenance_settings_description": "Immich-কে রক্ষণাবেক্ষণ মোডে (maintenance mode) রাখুন।",
"maintenance_start": "রক্ষণাবেক্ষণ মোডে পরিবর্তন করুন",
"maintenance_start_error": "রক্ষণাবেক্ষণ মোড চালু করতে ব্যর্থ হয়েছে।",
"maintenance_upload_backup": "ডেটাবেস ব্যাকআপ ফাইল আপলোড করুন",
"maintenance_upload_backup_error": "ব্যাকআপ আপলোড করা যায়নি, এটি কি কোনো .sql/.sql.gz ফাইল?",
"manage_concurrency": "কনকারেন্সি পরিচালনা করুন (Manage Concurrency)",
"manage_concurrency_description": "জব কনকারেন্সি পরিচালনা করতে 'জবস' (Jobs) পাতায় যান।",
"manage_log_settings": "লগ সেটিংস পরিচালনা করুন",
"map_dark_style": "ডার্ক স্টাইল (Dark style)",
"map_enable_description": "ম্যাপ ফিচারগুলো সক্রিয় করুন (Enable map features)",
"map_gps_settings": "ম্যাপ এবং জিপিএস সেটিংস (Map & GPS Settings)",
"map_gps_settings_description": "ম্যাপ এবং জিপিএস (রিভার্স জিওকোডিং) সেটিংস পরিচালনা করুন (Manage Map & GPS (Reverse Geocoding) Settings)",
"map_implications": "ম্যাপ ফিচারটি একটি এক্সটার্নাল টাইল সার্ভিসের (tiles.immich.cloud) ওপর নির্ভর করে।",
"map_light_style": "লাইট স্টাইল (Light style)",
"map_manage_reverse_geocoding_settings": "<link>রিভার্স জিওকোডিং</link> সেটিংস পরিচালনা করুন",
"map_reverse_geocoding": "রিভার্স জিওকোডিং (Reverse Geocoding)",
"map_reverse_geocoding_enable_description": "রিভার্স জিওকোডিং সক্রিয় করুন (Enable reverse geocoding)",
"map_reverse_geocoding_settings": "রিভার্স জিওকোডিং সেটিংস (Reverse Geocoding Settings)",
"map_settings": "মানচিত্র (Map)",
"map_settings_description": "মানচিত্রের সেটিংস পরিচালনা করুন (Manage map settings)",
"map_style_description": "একটি style.json ম্যাপ থিমের URL (URL to a style.json map theme)",
"memory_cleanup_job": "মেমরি ক্লিনআপ (Memory cleanup)",
"memory_generate_job": "স্মৃতি তৈরি করা(Memory generation)",
"metadata_extraction_job": "মেটাডেটা এক্সট্র্যাক্ট করুন (Extract metadata)",
"metadata_extraction_job_description": "প্রতিটি অ্যাসেট (Asset) থেকে মেটাডেটা তথ্য এক্সট্র্যাক্ট করুন, যেমন: জিপিএস (GPS), চেহারা (faces) এবং রেজোলিউশন (resolution)।",
"metadata_faces_import_setting": "ফেস ইম্পোর্ট সক্রিয় করুন (Enable face import)",
"metadata_faces_import_setting_description": "ছবির EXIF ডেটা এবং সাইডকার (sidecar) ফাইল থেকে চেহারা (faces) ইম্পোর্ট করুন।",
"metadata_settings": "মেটাডেটা সেটিংস (Metadata Settings)",
"metadata_settings_description": "মেটাডেটা সেটিংস পরিচালনা করুন (Manage metadata settings)",
"migration_job": "মাইগ্রেশন (Migration)",
"migration_job_description": "অ্যাসেট এবং ফেস থাম্বনেইলগুলোকে সর্বশেষ ফোল্ডার স্ট্রাকচারে মাইগ্রেট করুন। (Migrate thumbnails for assets and faces to the latest folder structure)",
"nightly_tasks_database_cleanup_setting": "ডেটাবেস ক্লিনআপ টাস্কসমূহ (Database cleanup tasks)",
"nightly_tasks_database_cleanup_setting_description": "ডেটাবেস থেকে পুরোনো এবং মেয়াদোত্তীর্ণ ডেটা মুছে ফেলুন",
"nightly_tasks_generate_memories_setting": "মেমোরিজ তৈরি করুন (Generate memories)",
"nightly_tasks_generate_memories_setting_description": "অ্যাসেটগুলো থেকে নতুন মেমোরিজ তৈরি করুন",
"nightly_tasks_missing_thumbnails_setting": "হারিয়ে যাওয়া থাম্বনেইলগুলো তৈরি করুন",
"nightly_tasks_missing_thumbnails_setting_description": "থাম্বনেইল নেই এমন ফাইলগুলোকে কিউতে (Queue) যোগ করুন",
"nightly_tasks_settings": "নাইটলি টাস্ক সেটিংস (Nightly Tasks Settings)",
"nightly_tasks_settings_description": "নাইটলি টাস্ক পরিচালনা করুন (Manage nightly tasks)",
"nightly_tasks_start_time_setting": "শুরু করার সময় (Start time)",
"nightly_tasks_start_time_setting_description": "সার্ভার যখন নাইটলি টাস্ক (nightly tasks) চালানো শুরু করে সেই সময়",
"nightly_tasks_sync_quota_usage_setting": "কোটা ব্যবহারের তথ্য সিঙ্ক করুন (Sync quota usage)",
"nightly_tasks_sync_quota_usage_setting_description": "বর্তমান ব্যবহারের ওপর ভিত্তি করে ব্যবহারকারীর স্টোরেজ কোটা আপডেট করুন।",
"no_paths_added": "কোনো পাথ যোগ করা হয়নি (No paths added)",
"no_pattern_added": "কোনো প্যাটার্ন যোগ করা হয়নি (No pattern added)",
"note_apply_storage_label_previous_assets": "দ্রষ্টব্য: পূর্বে আপলোড করা অ্যাসেটগুলোতে স্টোরেজ লেবেল (Storage Label) প্রয়োগ করতে নিচের কমান্ডটি রান করুন—",
"note_cannot_be_changed_later": "সতর্কবার্তা: এটি পরবর্তীতে পরিবর্তন করা যাবে না!",
"notification_email_from_address": "প্রেরকের ঠিকানা (From address)",
"notification_email_from_address_description": "প্রেরকের ইমেল ঠিকানা, উদাহরণস্বরূপ: \"Immich Photo Server noreply@example.com\"। নিশ্চিত করুন যে আপনি এমন একটি ঠিকানা ব্যবহার করছেন যা থেকে ইমেল পাঠানোর অনুমতি আপনার আছে।",
"notification_email_host_description": "ইমেল সার্ভারের হোস্ট (যেমন: smtp.immich.app)",
"notification_email_ignore_certificate_errors": "সার্টিফিকেট ত্রুটিগুলো উপেক্ষা করুন (Ignore certificate errors)",
"notification_email_ignore_certificate_errors_description": "TLS সার্টিফিকেট ভ্যালিডেশন ত্রুটিগুলো উপেক্ষা করুন (প্রস্তাবিত নয়)",
"notification_email_password_description": "ইমেল সার্ভারে অথেন্টিকেশন বা সত্যতা যাচাইয়ের জন্য ব্যবহৃত পাসওয়ার্ড",
"notification_email_port_description": "ইমেল সার্ভারের পোর্ট (যেমন: ২৫, ৪৬৫, অথবা ৫৮৭)",
"notification_email_secure": "SMTPS (স্মার্ট মেইল ট্রান্সফার প্রোটোকল সিকিউর)",
"notification_email_secure_description": "SMTPS (SMTP over TLS) ব্যবহার করুন",
"notification_email_sent_test_email_button": "টেস্ট ইমেল পাঠান এবং সেভ করুন",
"oauth_enable_description": "OAuth-এর মাধ্যমে লগইন করুন",
"oauth_mobile_redirect_uri": "মোবাইল রিডাইরেক্ট ইউআরআই (URI)",
"oauth_mobile_redirect_uri_override": "মোবাইল রিডাইরেক্ট ইউআরআই (URI) ওভাররাইড",
"oauth_mobile_redirect_uri_override_description": "যখন OAuth প্রোভাইডার মোবাইল ইউআরআই (URI) অনুমতি দেয় না, যেমন ''{callback}'', তখন এটি সক্রিয় করুন।",
"oauth_role_claim": "রোল ক্লেইম (Role Claim)",
"oauth_role_claim_description": "এই ক্লেইমটির উপস্থিতির ওপর ভিত্তি করে স্বয়ংক্রিয়ভাবে অ্যাডমিন অ্যাক্সেস প্রদান করুন। ক্লেইমটিতে 'user' অথবা 'admin' যেকোনো একটি থাকতে পারে।",
"oauth_settings": "OAuth",
"oauth_settings_description": "OAuth লগইন সেটিংস ম্যানেজ করুন",
"oauth_settings_more_details": "এই ফিচারের ব্যাপারে আরও বিস্তারিত জানতে, <link>ডকুমেন্টস</link> দেখুন।",
"oauth_storage_label_claim": "স্টোরেজ লেবেল ক্লেইম (Storage label claim)",
"oauth_storage_label_claim_description": "এই ক্লেইম-এর ভ্যালু অনুযায়ী ব্যবহারকারীর স্টোরেজ লেবেল স্বয়ংক্রিয়ভাবে সেট করুন।",
"oauth_storage_quota_claim": "স্টোরেজ কোটা ক্লেইম (Storage quota claim)",
"oauth_storage_quota_claim_description": "এই ক্লেইম-এর ভ্যালু অনুযায়ী ব্যবহারকারীর স্টোরেজ কোটা স্বয়ংক্রিয়ভাবে সেট করুন।",
"oauth_storage_quota_default": "ডিফল্ট স্টোরেজ কোটা (GiB)",
"oauth_storage_quota_default_description": "ক্লেইম না দেওয়া থাকলে যে স্টোরেজ কোটা (GiB-তে) ব্যবহার করা হবে।",
"oauth_timeout": "রিকোয়েস্ট টাইম-আউট (Request Timeout)",
"oauth_timeout_description": "মিলিসেকেন্ডে রিকোয়েস্টের টাইম-আউট (Timeout for requests in milliseconds)",
"ocr_job_description": "ছবি থেকে টেক্সট শনাক্ত করতে মেশিন লার্নিং ব্যবহার করুন",
"password_enable_description": "ইমেল এবং পাসওয়ার্ড দিয়ে লগইন করুন",
"password_settings": "পাসওয়ার্ড লগইন (Password Login)",
"password_settings_description": "পাসওয়ার্ড লগইন সেটিংস ম্যানেজ করুন",
"paths_validated_successfully": "সবগুলো পাথ (path) সফলভাবে যাচাই করা হয়েছে",
"person_cleanup_job": "পারসন ক্লিনআপ (Person Cleanup)",
"queue_details": "কিউ ডিটেইলস (Queue Details)",
"queues": "জব কিউ (Job Queues)",
"queues_page_description": "অ্যাডমিন জব কিউ (Job Queues) পেজ",
"quota_size_gib": "কোটা সাইজ (GiB)",
"refreshing_all_libraries": "সবগুলো লাইব্রেরি রিফ্রেশ করা হচ্ছে",
"registration": "অ্যাডমিন রেজিস্ট্রেশন (Admin Registration)",
"registration_description": "যেহেতু আপনি এই সিস্টেমের প্রথম ব্যবহারকারী, তাই আপনাকে অ্যাডমিন (Admin) হিসেবে নিযুক্ত করা হবে। আপনি সমস্ত প্রশাসনিক কাজের জন্য দায়ী থাকবেন এবং পরবর্তী ব্যবহারকারীরা আপনার মাধ্যমেই তৈরি হবে।",
"remove_failed_jobs": "ব্যর্থ হওয়া কাজগুলো মুছে ফেলুন (Remove failed jobs)",
"require_password_change_on_login": "প্রথমবার লগইন করার সময় ব্যবহারকারীর পাসওয়ার্ড পরিবর্তন করা বাধ্যতামূলক করুন",
"reset_settings_to_default": "সেটিংস রিসেট করে ডিফল্ট অবস্থায় ফিরিয়ে আনুন (Reset settings to default)",
"reset_settings_to_recent_saved": "সম্প্রতি সেভ করা সেটিংসে রিসেট করুন (Reset settings to the recent saved settings)",
"scanning_library": "লাইব্রেরি স্ক্যান করা হচ্ছে (Scanning library)",
"search_jobs": "জব সার্চ করুন…",
"send_welcome_email": "স্বাগত ইমেল পাঠান",
"server_external_domain_settings": "এক্সটার্নাল ডোমেইন (External Domain)",
"server_external_domain_settings_description": "পাবলিক শেয়ারিং লিঙ্কের জন্য ডোমেইন (http(s):// সহ)",
"server_public_users": "পাবলিক ইউজার (Public Users)",
"server_public_users_description": "শেয়ার করা অ্যালবামে কোনো ব্যবহারকারীকে যোগ করার সময় সমস্ত ব্যবহারকারীর (নাম এবং ইমেল) তালিকা দেখানো হয়। এটি নিষ্ক্রিয় (Disabled) করা হলে, ব্যবহারকারীর তালিকা শুধুমাত্র অ্যাডমিনদের জন্য উপলব্ধ হবে।",
"server_settings": "সার্ভার সেটিংস (Server Settings)",
"server_settings_description": "সার্ভার সেটিংস ম্যানেজ করুন (Manage server settings)",
"server_stats_page_description": "অ্যাডমিন সার্ভার স্ট্যাটিস্টিকস (Server Statistics) পেজ",
"server_welcome_message": "স্বাগত বার্তা (Welcome message)",
"server_welcome_message_description": "লগইন পেজে প্রদর্শিত একটি বার্তা।",
"settings_page_description": "অ্যাডমিন সেটিংস পেজ",
"sidecar_job": "সাইডকার মেটাডেটা (Sidecar Metadata)",
"sidecar_job_description": "ফাইলসিস্টেম থেকে সাইডকার মেটাডেটা অনুসন্ধান বা সিঙ্ক্রোনাইজ করুন",
"slideshow_duration_description": "প্রতিটি ছবি দেখানোর সময়কাল (সেকেন্ডে)",
"smart_search_job_description": "স্মার্ট সার্চের সুবিধার্থে অ্যাসেটগুলোর ওপর মেশিন লার্নিং পরিচালনা করুন",
"storage_template_date_time_description": "অ্যাসেট তৈরির সময়কাল (Timestamp) তারিখ ও সময়ের তথ্যের জন্য ব্যবহৃত হয়",
"storage_template_date_time_sample": "নমুনা সময় {date}",
"storage_template_enable_description": "স্টোরেজ টেমপ্লেট ইঞ্জিন সক্রিয় করুন",
"storage_template_hash_verification_enabled": "হ্যাশ ভেরিফিকেশন (Hash Verification) সক্রিয় করা হয়েছে",
"storage_template_hash_verification_enabled_description": "হ্যাশ ভেরিফিকেশন (Hash Verification) সক্রিয় করে; এর প্রভাব সম্পর্কে নিশ্চিত না হয়ে এটি নিষ্ক্রিয় করবেন না",
"storage_template_migration": "স্টোরেজ টেমপ্লেট মাইগ্রেশন (Storage Template Migration)",
"storage_template_migration_description": "পূর্বে আপলোড করা অ্যাসেটগুলোতে বর্তমান <link>{template}</link> প্রয়োগ করুন",
"storage_template_migration_info": "স্টোরেজ টেমপ্লেটটি সমস্ত এক্সটেনশনকে ছোট হাতের অক্ষরে (lowercase) রূপান্তর করবে। টেমপ্লেটের পরিবর্তনগুলো কেবল নতুন অ্যাসেটগুলোর ক্ষেত্রে প্রযোজ্য হবে। পূর্বে আপলোড করা অ্যাসেটগুলোতে এই টেমপ্লেটটি ভূতাপেক্ষভাবে (retroactively) প্রয়োগ করতে <link>{job}</link> রান করুন।",
"storage_template_migration_job": "স্টোরেজ টেমপ্লেট মাইগ্রেশন জব",
"storage_template_more_details": "এই ফিচারটি সম্পর্কে আরও বিস্তারিত জানতে, <template-link>Storage Template</template-link> এবং এর <implications-link>প্রভাবগুলো (implications)</implications-link> দেখুন।",
"storage_template_onboarding_description_v2": "এটি সক্রিয় থাকলে, ফিচারটি ব্যবহারকারীর নির্ধারিত টেমপ্লেট অনুযায়ী ফাইলগুলোকে স্বয়ংক্রিয়ভাবে অর্গানাইজ (Auto-organize) করবে। আরও তথ্যের জন্য অনুগ্রহ করে <link>ডকুমেন্টেশন</link> দেখুন।",
"storage_template_path_length": "আনুমানিক পাথ লেন্থ লিমিট (Path length limit): <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "স্টোরেজ টেমপ্লেট (Storage Template)",
"storage_template_settings_description": "আপলোড করা অ্যাসেটের ফোল্ডার স্ট্রাকচার এবং ফাইল নেম ম্যানেজ করুন",
"storage_template_user_label": "<code>{label}</code> হলো ব্যবহারকারীর স্টোরেজ লেবেল (Storage Label)",
"theme_settings_description": "ইমিচ (Immich) ওয়েব ইন্টারফেসের কাস্টমাইজেশন ম্যানেজ করুন",
"thumbnail_generation_job": "থাম্বনেইল তৈরি করুন (Generate Thumbnails)",
"thumbnail_generation_job_description": "প্রতিটি অ্যাসেটের জন্য বড়, ছোট এবং ব্লার (অস্পষ্ট) থাম্বনেইল তৈরি করুন, সেই সাথে প্রতিটি ব্যক্তির জন্যও থাম্বনেইল তৈরি করুন।"
},
"yes": "হ্যাঁ",
"you_dont_have_any_shared_links": "আপনার কোনো শেয়ার করা লিঙ্ক নেই (You don't have any shared links)",
"your_wifi_name": "আপনার ওয়াই-ফাই এর নাম (Your Wi-Fi name)",
"zero_to_clear_rating": "অ্যাসেট রেটিং মুছে ফেলতে চাপুন",
"zoom_image": "ছবি জুম করুন (Zoom Image)",
"zoom_to_bounds": "বাউন্ডস অনুযায়ী জুম করুন (Zoom to bounds)"
"machine_learning_duplicate_detection_enabled": "পুনরাবৃত্তি শনাক্তকরণ চালু করুন"
}
}

View File

@@ -572,9 +572,6 @@
"asset_list_layout_sub_title": "Disseny",
"asset_list_settings_subtitle": "Configuració del disseny de la graella de fotos",
"asset_list_settings_title": "Graella de fotos",
"asset_not_found_on_device_android": "No s'ha trobat l'actiu al dispositiu",
"asset_not_found_on_device_ios": "No s'ha trobat l'element al dispositiu. Si utilitzes l'iCloud, pot ser que no s'hi pugui accedir perquè el fitxer guardat a l'iCloud és corrupte",
"asset_not_found_on_icloud": "No s'ha trobat l'element a l'iCloud. Pot ser que no s'hi pugui accedir perquè el fitxer guardat a l'iCloud és corrupte",
"asset_offline": "Element fora de línia",
"asset_offline_description": "Aquest recurs extern ja no es troba al disc. Poseu-vos en contacte amb el vostre administrador d'Immich per obtenir ajuda.",
"asset_restored_successfully": "Element recuperat correctament",
@@ -1195,6 +1192,7 @@
"features": "Característiques",
"features_in_development": "Funcions en desenvolupament",
"features_setting_description": "Administrar les funcions de l'aplicació",
"file_name": "Nom de l'arxiu: {file_name}",
"file_name_or_extension": "Nom de l'arxiu o extensió",
"file_size": "Mida del fitxer",
"filename": "Nom del fitxer",
@@ -2297,7 +2295,6 @@
"upload_details": "Detalls de la Pujada",
"upload_dialog_info": "Vols fer còpia de seguretat dels elements seleccionats al servidor?",
"upload_dialog_title": "Puja elements",
"upload_error_with_count": "Error en la càrrega de {count, plural, one {# actiu} other {# actius}}",
"upload_errors": "Càrrega completada amb {count, plural, one {# error} other {# errors}}, actualitzeu la pàgina per veure els nous elements carregats.",
"upload_finished": "Pujada finalitzada",
"upload_progress": "Restant {remaining, number} - Processat {processed, number}/{total, number}",

View File

@@ -1195,6 +1195,7 @@
"features": "Funkce",
"features_in_development": "Funkce ve vývoji",
"features_setting_description": "Správa funkcí aplikace",
"file_name": "Název souboru: {file_name}",
"file_name_or_extension": "Název nebo přípona souboru",
"file_size": "Velikost souboru",
"filename": "Název souboru",

View File

@@ -1189,6 +1189,7 @@
"features": "Funktioner",
"features_in_development": "Funktioner under udvikling",
"features_setting_description": "Administrer app-funktioner",
"file_name": "Filnavn: {file_name}",
"file_name_or_extension": "Filnavn eller filtype",
"file_size": "Fil størrelse",
"filename": "Filnavn",

View File

@@ -526,8 +526,8 @@
"allowed": "Erlaubt",
"alt_text_qr_code": "QR-Code Bild",
"always_keep": "Immer behalten",
"always_keep_photos_hint": "Speicherfreigabe wird alle Fotos auf dem Gerät behalten.",
"always_keep_videos_hint": "Speicherfreigabe wird alle Videos auf dem Gerät behalten.",
"always_keep_photos_hint": "Speicher freigeben wird alle Fotos auf dem Gerät behalten",
"always_keep_videos_hint": "Speicher freigeben wird alle Videos auf dem Gerät behalten",
"anti_clockwise": "Gegen den Uhrzeigersinn",
"api_key": "API-Schlüssel",
"api_key_description": "Dieser Wert wird nur einmal angezeigt. Bitte kopiere ihn, bevor du das Fenster schließt.",
@@ -537,7 +537,7 @@
"app_bar_signout_dialog_content": "Bist du dir sicher, dass du dich abmelden möchtest?",
"app_bar_signout_dialog_ok": "Ja",
"app_bar_signout_dialog_title": "Abmelden",
"app_download_links": "App Download-Links",
"app_download_links": "App Download Links",
"app_settings": "App-Einstellungen",
"app_stores": "App Stores",
"app_update_available": "App Update verfügbar",
@@ -572,9 +572,6 @@
"asset_list_layout_sub_title": "Layout",
"asset_list_settings_subtitle": "Einstellungen für das Fotogitter-Layout",
"asset_list_settings_title": "Fotogitter",
"asset_not_found_on_device_android": "Datei auf Gerät nicht gefunden",
"asset_not_found_on_device_ios": "Datei auf Gerät nicht gefunden. Wenn Du iCloud verwendest, kann die Datei möglicherweise nicht auffindbar sein aufgrund schlechter Dateispeicherung von iCloud",
"asset_not_found_on_icloud": "Datei in iCloud nicht gefunden. Die Datei kann möglicherweise nicht auffindbar sein aufgrund schlechter Dateispeicherung in iCloud",
"asset_offline": "Datei offline",
"asset_offline_description": "Diese externe Datei ist nicht mehr auf dem Datenträger vorhanden. Bitte wende dich an deinen Immich-Administrator, um Hilfe zu erhalten.",
"asset_restored_successfully": "Datei erfolgreich wiederhergestellt",
@@ -610,14 +607,14 @@
"assets_were_part_of_album_count": "{count, plural, one {# Datei ist} other {# Dateien sind}} bereits im Album vorhanden",
"assets_were_part_of_albums_count": "{count, plural, one {Datei war} other {Dateien waren}} bereits in den Alben",
"authorized_devices": "Verwendete Geräte",
"automatic_endpoint_switching_subtitle": "Verbinden Sie sich lokal über ein bestimmtes WiFi, wenn es verfügbar ist, und verwenden Sie andere Verbindungsmöglichkeiten",
"automatic_endpoint_switching_subtitle": "Verbinden Sie sich lokal über ein bestimmtes WLAN, wenn es verfügbar ist, und verwenden Sie andere Verbindungsmöglichkeiten anderswo",
"automatic_endpoint_switching_title": "Automatische URL-Umschaltung",
"autoplay_slideshow": "Automatische Diashow",
"back": "Zurück",
"back_close_deselect": "Zurück, Schließen oder Abwählen",
"background_backup_running_error": "Sicherung läuft im Hintergrund. Manuelle Sicherung kann nicht gestartet werden",
"background_location_permission": "Hintergrund Standortfreigabe",
"background_location_permission_content": "Um im Hintergrund zwischen den Netzwerken wechseln zu können, muss Immich *immer* Zugriff auf den genauen Standort haben, damit die App den Namen des WiFi-Netzwerks ermitteln kann",
"background_location_permission_content": "Um im Hintergrund zwischen den Netzwerken wechseln zu können, muss Immich *immer* Zugriff auf den genauen Standort haben, damit die App den Namen des WLAN-Netzwerks ermitteln kann",
"background_options": "Hintergrund Optionen",
"backup": "Sicherung",
"backup_album_selection_page_albums_device": "Alben auf dem Gerät ({count})",
@@ -652,7 +649,7 @@
"backup_controller_page_background_is_on": "Automatische Sicherung im Hintergrund ist aktiviert",
"backup_controller_page_background_turn_off": "Hintergrundservice ausschalten",
"backup_controller_page_background_turn_on": "Hintergrundservice einschalten",
"backup_controller_page_background_wifi": "Nur im WiFi",
"backup_controller_page_background_wifi": "Nur im WLAN",
"backup_controller_page_backup": "Sicherung",
"backup_controller_page_backup_selected": "Ausgewählt: ",
"backup_controller_page_backup_sub": "Gesicherte Fotos und Videos",
@@ -754,7 +751,7 @@
"charging_requirement_mobile_backup": "Backup im Hintergrund erfordert Aufladen des Geräts",
"check_corrupt_asset_backup": "Auf beschädigte Asset-Backups überprüfen",
"check_corrupt_asset_backup_button": "Überprüfung durchführen",
"check_corrupt_asset_backup_description": "Führe diese Prüfung nur mit aktivierten WiFi durch, nachdem alle Dateien gesichert worden sind. Dieser Vorgang kann ein paar Minuten dauern.",
"check_corrupt_asset_backup_description": "Führe diese Prüfung nur mit aktivierten WLAN durch, nachdem alle Dateien gesichert worden sind. Dieser Vorgang kann ein paar Minuten dauern.",
"check_logs": "Logs prüfen",
"checksum": "Prüfsumme",
"choose_matching_people_to_merge": "Wähle passende Personen zum Zusammenführen",
@@ -991,12 +988,12 @@
"edit_title": "Titel bearbeiten",
"edit_user": "Nutzer bearbeiten",
"edit_workflow": "Workflow bearbeiten",
"editor": "Bearbeiten",
"editor": "Bearbeiter",
"editor_close_without_save_prompt": "Die Änderungen werden nicht gespeichert",
"editor_close_without_save_title": "Editor schließen?",
"editor_confirm_reset_all_changes": "Alle Änderungen zurücksetzen?",
"editor_flip_horizontal": "Horizontal spiegeln",
"editor_flip_vertical": "Vertikal spiegeln",
"editor_flip_horizontal": "horizontal spiegeln",
"editor_flip_vertical": "vertikal spiegeln",
"editor_orientation": "Ausrichtung",
"editor_reset_all_changes": "Änderungen zurücksetzen",
"editor_rotate_left": "Um 90° gegen den Uhrzeigersinn drehen",
@@ -1012,7 +1009,7 @@
"enabled": "Aktiviert",
"end_date": "Enddatum",
"enqueued": "Eingereiht",
"enter_wifi_name": "WiFi-Name eingeben",
"enter_wifi_name": "WLAN-Name eingeben",
"enter_your_pin_code": "PIN-Code eingeben",
"enter_your_pin_code_subtitle": "Gib deinen PIN-Code ein, um auf den gesperrten Ordner zuzugreifen",
"error": "Fehler",
@@ -1195,6 +1192,7 @@
"features": "Funktionen",
"features_in_development": "Feature in Entwicklung",
"features_setting_description": "Funktionen der App verwalten",
"file_name": "Dateiname: {file_name}",
"file_name_or_extension": "Dateiname oder -erweiterung",
"file_size": "Dateigröße",
"filename": "Dateiname",
@@ -1223,7 +1221,7 @@
"geolocation_instruction_location": "Klicke auf eine Datei mit GPS Koordinaten um diesen Standort zu verwenden oder wähle einen Standort direkt auf der Karte",
"get_help": "Hilfe erhalten",
"get_people_error": "Fehler beim Laden der Personen",
"get_wifiname_error": "WiFi-Name konnte nicht ermittelt werden. Vergewissere dich, dass die erforderlichen Berechtigungen erteilt wurden und du mit einem WiFi-Netzwerk verbunden bist",
"get_wifiname_error": "WLAN-Name konnte nicht ermittelt werden. Vergewissere dich, dass die erforderlichen Berechtigungen erteilt wurden und du mit einem WLAN-Netzwerk verbunden bist",
"getting_started": "Erste Schritte",
"go_back": "Zurück",
"go_to_folder": "Gehe zu Ordner",
@@ -1387,7 +1385,7 @@
"local_network_sheet_info": "Die App stellt über diese URL eine Verbindung zum Server her, wenn sie das angegebene WLAN-Netzwerk verwendet",
"location": "Standort",
"location_permission": "Standort Genehmigung",
"location_permission_content": "Um die automatische Umschaltfunktion nutzen zu können, benötigt Immich genaue Standortberechtigung, damit es den Namen des aktuellen WiFi-Netzwerks ermitteln kann",
"location_permission_content": "Um die automatische Umschaltfunktion nutzen zu können, benötigt Immich genaue Standortberechtigung, damit es den Namen des aktuellen WLAN-Netzwerks ermitteln kann",
"location_picker_choose_on_map": "Auf der Karte auswählen",
"location_picker_latitude_error": "Gültigen Breitengrad eingeben",
"location_picker_latitude_hint": "Breitengrad eingeben",
@@ -2202,7 +2200,7 @@
"theme_setting_asset_list_storage_indicator_title": "Fortschrittsbalken der Sicherung auf dem Vorschaubild",
"theme_setting_asset_list_tiles_per_row_title": "Anzahl der Elemente pro Reihe ({count})",
"theme_setting_colorful_interface_subtitle": "Primärfarbe auf App-Hintergrund anwenden.",
"theme_setting_colorful_interface_title": "Farbige Benutzeroberfläche",
"theme_setting_colorful_interface_title": "Farbige UI-Oberfläche",
"theme_setting_image_viewer_quality_subtitle": "Einstellen der Qualität des Detailbildbetrachters",
"theme_setting_image_viewer_quality_title": "Qualität des Bildbetrachters",
"theme_setting_primary_color_subtitle": "Farbauswahl für primäre Aktionen und Akzente.",
@@ -2297,7 +2295,6 @@
"upload_details": "Upload Details",
"upload_dialog_info": "Willst du die ausgewählten Elemente auf dem Server sichern?",
"upload_dialog_title": "Element hochladen",
"upload_error_with_count": "Uploadfehler für {count, plural, one {# asset} other {# assets}}",
"upload_errors": "Hochladen mit {count, plural, one {# Fehler} other {# Fehlern}} abgeschlossen, aktualisiere die Seite, um neu hochgeladene Dateien zu sehen.",
"upload_finished": "Upload fertig",
"upload_progress": "{remaining, number} verbleibend - {processed, number}/{total, number} verarbeitet",
@@ -2375,7 +2372,7 @@
"welcome": "Willkommen",
"welcome_to_immich": "Willkommen bei Immich",
"width": "Breite",
"wifi_name": "WiFi-Name",
"wifi_name": "WLAN-Name",
"workflow_delete_prompt": "Bist du sicher, dass du diesen Workflow löschen willst?",
"workflow_deleted": "Workflow gelöscht",
"workflow_description": "Workflow-Beschreibung",
@@ -2394,7 +2391,7 @@
"years_ago": "Vor {years, plural, one {einem Jahr} other {# Jahren}}",
"yes": "Ja",
"you_dont_have_any_shared_links": "Du hast keine geteilten Links",
"your_wifi_name": "Dein WiFi-Name",
"your_wifi_name": "Dein WLAN-Name",
"zero_to_clear_rating": "drücke 0 um die Dateibewertung zurückzusetzen",
"zoom_image": "Bild vergrößern",
"zoom_to_bounds": "Auf Grenzen zoomen"

View File

@@ -1181,6 +1181,7 @@
"features": "Χαρακτηριστικά",
"features_in_development": "Λειτουργίες υπό Ανάπτυξη",
"features_setting_description": "Διαχειριστείτε τα χαρακτηριστικά της εφαρμογής",
"file_name": "Όνομα αρχείου: {file_name}",
"file_name_or_extension": "Όνομα αρχείου ή επέκταση",
"file_size": "Μέγεθος αρχείου",
"filename": "Ονομασία αρχείου",

View File

@@ -782,8 +782,6 @@
"client_cert_import": "Import",
"client_cert_import_success_msg": "Client certificate is imported",
"client_cert_invalid_msg": "Invalid certificate file or wrong password",
"client_cert_password_message": "Enter the password for this certificate",
"client_cert_password_title": "Certificate Password",
"client_cert_remove_msg": "Client certificate is removed",
"client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate import/removal is available only before login",
"client_cert_title": "SSL client certificate [EXPERIMENTAL]",
@@ -1197,9 +1195,8 @@
"features": "Features",
"features_in_development": "Features in Development",
"features_setting_description": "Manage the app features",
"file_name": "File name: {file_name}",
"file_name_or_extension": "File name or extension",
"file_name_text": "File name",
"file_name_with_value": "File name: {file_name}",
"file_size": "File size",
"filename": "Filename",
"filetype": "Filetype",

View File

@@ -104,8 +104,6 @@
"image_preview_description": "Mez-granda bildo, sen metadatumoj, uzata por montri unuopan bildon, kaj por maŝin-lernado",
"image_preview_quality_description": "Kvalito de antaŭvido, inter 1 kaj 100. Pli alta numero indikas pli altan kvaliton, sed ankaŭ kreas pli grandajn dosierojn, kiuj povas malrapidigi uzadon de la apo. Tro malalta numero povas noci la maŝin-lernadon.",
"image_preview_title": "Agordoj pri antaŭvidoj",
"image_progressive": "Poiome",
"image_progressive_description": "Kodigi JPEG-bildojn por poioma vidigo dum ŝargado. Tio ŝanĝas nenion por WebP-bildoj.",
"image_quality": "Kvalito",
"image_resolution": "Distingivo",
"image_resolution_description": "Alta distingivo povas konservi pli da detaloj en bildoj sed postulas pli da tempo por trakti, donas pli grandajn dosierojn por stokie, kaj povas malrapidigi uzadon de la apo.",
@@ -272,7 +270,7 @@
"oauth_auto_register": "Registri aŭtomate",
"oauth_auto_register_description": "Aŭtomate registri novajn uzantojn tuj post ensaluto per OAuth",
"oauth_button_text": "Teksto de butono",
"oauth_client_secret_description": "Bezonata por privata kliento, aŭ se PKCE (Proof Key for Code Exchange) ne estas subtenata de publika kliento.",
"oauth_client_secret_description": "Bezonata kaze ke la provizanto de OAuth ne subtenas PKCE (Proof Key for Code Exchange)",
"oauth_enable_description": "Ensaluti per OAuth",
"oauth_mobile_redirect_uri": "Resenda URI por poŝ-aparatoj",
"oauth_mobile_redirect_uri_override": "Insisti pri resenda URI por poŝ-aparatoj",
@@ -356,8 +354,8 @@
"theme_settings_description": "Administri tajloradon de la reta interfaco de Immich",
"thumbnail_generation_job": "Generi bildetojn",
"thumbnail_generation_job_description": "Kreas grandan, malgrandan, kaj malklaran bildetojn por ĉiu elemento, kune kun bildeto por ĉiu homo",
"transcoding_acceleration_api": "API de pliradidigo",
"transcoding_acceleration_api_description": "La API, kiu interagos kun via aparato por plirapidigi la transkodadon. Tiu ĉi agordaĵo indikas preferon  kaze de malsukceso, ĝi retropaŝas al softvara transkodado. VP9 povas funkcii aŭ ne, depende de viaj aparatoj.",
"transcoding_acceleration_api": "API de akcelado",
"transcoding_acceleration_api_description": "La API, kiu interagos kun via aparato por akceli la transkodadon. Tiu ĉi agordaĵo indikas preferon  kaze de malsukceso, ĝi retropaŝas al softvara transkodado. VP9 povas funkcii aŭ ne, depende de viaj aparatoj.",
"transcoding_acceleration_nvenc": "NVENC (postulas GPU de NVIDIA)",
"transcoding_acceleration_qsv": "Quick Sync (postulas ĉefprocesoron Intel de minimume 7-a generacio)",
"transcoding_acceleration_rkmpp": "RKMPP (nur por SOC-oj de Rockchip)",
@@ -371,29 +369,6 @@
"transcoding_advanced_options_description": "Agordoj, kiujn plej multaj uzantoj ne bezonas ŝanĝi",
"transcoding_audio_codec": "Sonkodeko",
"transcoding_audio_codec_description": "Opus estas la plej altkvalita elekto, sed ĝi ne kongruas kun malnovaj aparatoj kaj softvaroj.",
"transcoding_bitrate_description": "Videoj kun bitrapido pli alta ol maksimumo, aŭ ne en akceptita formato",
"transcoding_codecs_learn_more": "Per lerni pli pri la terminaro uzata ĉi tie, legu la dokumentaron de FFmpeg pri <h264-link>kodeko H.264</h264-link>, <hevc-link>kodeko HEVC</hevc-link> kaj <vp9-link>kodeko VP9</vp9-link>.",
"transcoding_constant_quality_mode": "Reĝimo de konstanta kvalito",
"transcoding_constant_quality_mode_description": "ICQ estas pli bona ol CQP, sed kelkaj aparatoj de plirapidigo ne subtenas ĝin. Ŝalti tion ĉi privilegiigas la elektitan reĝimon dum uzo de kodado bazita sur kvalito. Ignorita de NVENC ĉar ĝi ne subtenas ICQ.",
"transcoding_constant_rate_factor": "Konstanta rapida faktoro (-crf)",
"transcoding_constant_rate_factor_description": "Nivelo de video-kvalito. Tipaj valoroj estas 23 por H.264, 28 por HEVC, 31 por VP9, kaj 35 por AV1. Pli malalta cifero indikas pli altan kvaliton, sed kreas pli pezajn dosierojn.",
"transcoding_disabled_description": "Ne transkodigi videojn. Tio povas perturbi vidigon en kelkaj klientoj",
"transcoding_encoding_options": "Agordoj de kodigo",
"transcoding_encoding_options_description": "Administri agordojn pri kodekoj, distingivo, kvalito, ktp. por la kodigitaj videoj",
"transcoding_hardware_acceleration": "Aparata plirapidigo",
"transcoding_hardware_acceleration_description": "Eksperimenta: pli rapida kodado, sed eble kun malpli bona kvalito je sama bitrapido",
"transcoding_hardware_decoding": "Aparata malkodado",
"transcoding_hardware_decoding_setting_description": "Ŝaltas tutvojan plirapidigon anstataŭ nur pliradidan kodadon. Povus ne funkcii por kelkaj videoj.",
"transcoding_max_b_frames": "Makimuma nombro de B-kadroj",
"transcoding_max_b_frames_description": "Pli alta valoro indikas pli efikan densigon, sed malpli rapidan kodadon. Eble ne funkcios kun pli malnova aparata plirapidigo. Valoro de 0 malŝaltas B-kadrojn. Valoro de -1 indikas aŭtomate elektitan valoron.",
"transcoding_max_bitrate": "Maksimuma bitrapido",
"transcoding_max_bitrate_description": "Agordi maksimuman bitrapidon rezultas je dosieroj kun pli antaŭvidebla grandeco, kun nur malgranda perdo de kvalito. Por 720p, tipaj valoroj estas 2600 kbit/s por VP9 aŭ HEVC, aŭ 4500 kbit/s por H.264. Valoro de 0 indikas 'malŝaltita'. Defaŭlta unuo estas k (t.e. kbit/s), do '5000', '5000k' kaj '5M' estas ekvivalentaj.",
"transcoding_max_keyframe_interval": "Maksimuma intervalo inter ĉefaj kadroj",
"transcoding_max_keyframe_interval_description": "Agordas la maksimuman distancon inter ĉefaj kadroj. Malaltaj valoroj malhelpas densigon, sed povas plibonigi kvaliton en scenoj kun rapidaj movoj. Valoro de 0 indikas aŭtomatan agordigon.",
"transcoding_optimal_description": "La videoj havas distingivon pli altan ol tiu celita, aŭ ne havas akcepteblan formaton",
"transcoding_policy": "Politiko de transkodado",
"transcoding_policy_description": "Kriterioj por indiki ĉu video estas transkodita aŭ ne",
"transcoding_preferred_hardware_device": "Preferita aparato",
"transcoding_settings_description": "Administri transkodadon de videoj",
"trash_settings_description": "Administri agordojn pri rubaĵoj",
"user_settings_description": "Administri agordojn pri uzantoj"

View File

@@ -5,7 +5,7 @@
"acknowledge": "Aceptar",
"action": "Acción",
"action_common_update": "Actualizar",
"action_description": "Un conjunto de acciones a realizar en los recursos filtrados",
"action_description": "Un conjunto de acciones a realizar en los activos filtrados",
"actions": "Acciones",
"active": "Activo",
"active_count": "Activo: {count}",
@@ -182,10 +182,10 @@
"machine_learning_ocr_min_recognition_score": "Puntuación mínima de reconocimiento",
"machine_learning_ocr_min_score_recognition_description": "Puntuación mínima de confianza para que el texto detectado sea reconocido de 0 a 1. Los valores más bajos reconocerán más texto, pero pueden producir falsos positivos.",
"machine_learning_ocr_model": "Modelo de OCR",
"machine_learning_ocr_model_description": "Los modelos del servidor son más precisos que los modelos móviles, pero tardan más en procesar y consumen más memoria.",
"machine_learning_ocr_model_description": "Los modelos del servidor son más precisos que los modelos para móviles móviles, pero tardan más en procesar y consumen más memoria.",
"machine_learning_settings": "Configuración de aprendizaje automático",
"machine_learning_settings_description": "Administrar funciones y configuraciones de aprendizaje automático",
"machine_learning_smart_search": "Búsqueda inteligente",
"machine_learning_smart_search": "Busqueda inteligente",
"machine_learning_smart_search_description": "Busque imágenes semánticamente utilizando incrustaciones CLIP (Contrastive Language-Image Pre-Training)",
"machine_learning_smart_search_enabled": "Habilitar búsqueda inteligente",
"machine_learning_smart_search_enabled_description": "Al desactivarlo las imágenes no se procesarán para usar la búsqueda inteligente.",
@@ -272,7 +272,7 @@
"oauth_auto_register": "Registro automático",
"oauth_auto_register_description": "Registre automáticamente nuevos usuarios después de iniciar sesión con OAuth",
"oauth_button_text": "Texto del botón",
"oauth_client_secret_description": "Requerido para clientes confidenciales, o si PKCE (Prueba de clave para el intercambio de códigos) no es compatible con clientes públicos.",
"oauth_client_secret_description": "Requerido si PKCE (Prueba de clave para el intercambio de códigos) no es compatible con el proveedor OAuth",
"oauth_enable_description": "Iniciar sesión con OAuth",
"oauth_mobile_redirect_uri": "URI de redireccionamiento móvil",
"oauth_mobile_redirect_uri_override": "Sobreescribir URI de redirección móvil",
@@ -354,7 +354,7 @@
"theme_custom_css_settings_description": "Las Hojas de Estilo (CSS) permiten personalizar el diseño de Immich.",
"theme_settings": "Ajustes del tema",
"theme_settings_description": "Gestionar la personalización de la interfaz web de Immich",
"thumbnail_generation_job": "Generar miniaturas",
"thumbnail_generation_job": "Generar Miniaturas",
"thumbnail_generation_job_description": "Genere miniaturas grandes, pequeñas y borrosas para cada archivo, así como miniaturas para cada persona",
"transcoding_acceleration_api": "API Aceleración",
"transcoding_acceleration_api_description": "La API que interactuará con su dispositivo para acelerar la transcodificación. Esta configuración es el \"mejor esfuerzo\": recurrirá a la transcodificación del software en caso de error. VP9 puede funcionar o no dependiendo de su hardware.",
@@ -572,9 +572,6 @@
"asset_list_layout_sub_title": "Disposición",
"asset_list_settings_subtitle": "Configuraciones del diseño de la cuadrícula de fotos",
"asset_list_settings_title": "Cuadrícula de fotos",
"asset_not_found_on_device_android": "Activo no encontrado en el dispositivo",
"asset_not_found_on_device_ios": "No se encuentra el recurso en el dispositivo. Si usa iCloud, es posible que no pueda acceder al recurso debido a un archivo defectuoso almacenado en iCloud",
"asset_not_found_on_icloud": "No se ha encontrado el recurso en iCloud. Es posible que no se pueda acceder al recurso debido a un archivo defectuoso almacenado en iCloud",
"asset_offline": "Archivos sin conexión",
"asset_offline_description": "Este activo externo ya no se encuentra en el disco. Por favor, póngase en contacto con su administrador de Immich para obtener ayuda.",
"asset_restored_successfully": "Elementos restaurados exitosamente",
@@ -585,7 +582,7 @@
"asset_uploaded": "Subido",
"asset_uploading": "Subiendo…",
"asset_viewer_settings_subtitle": "Administra las configuraciones de tu visor de fotos",
"asset_viewer_settings_title": "Visor de archivos",
"asset_viewer_settings_title": "Visor de Archivos",
"assets": "elementos",
"assets_added_count": "{count, plural, one {# elemento añadido} other {# elementos añadidos}}",
"assets_added_to_album_count": "{count, plural, one {# elemento añadido} other {# elementos añadidos}} al álbum",
@@ -609,7 +606,7 @@
"assets_trashed_from_server": "{count} recurso(s) enviado(s) a la papelera desde el servidor de Immich",
"assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}} ya forma parte del álbum",
"assets_were_part_of_albums_count": "{count, plural, one {El elemento ya es} other {Los elementos ya son}} parte de los álbumes",
"authorized_devices": "Dispositivos autorizados",
"authorized_devices": "Dispositivos Autorizados",
"automatic_endpoint_switching_subtitle": "Conectarse localmente a través de la Wi-Fi designada cuando esté disponible y usar conexiones alternativas en otros lugares",
"automatic_endpoint_switching_title": "Cambio automático de URL",
"autoplay_slideshow": "Presentación con reproducción automática",
@@ -619,7 +616,7 @@
"background_location_permission": "Permiso de ubicación en segundo plano",
"background_location_permission_content": "Para poder cambiar de red mientras se ejecuta en segundo plano, Immich debe tener *siempre* acceso a la ubicación precisa para que la aplicación pueda leer el nombre de la red Wi-Fi",
"background_options": "Opciones de segundo plano",
"backup": "Copia de seguridad",
"backup": "Copia de Seguridad",
"backup_album_selection_page_albums_device": "Álbumes en el dispositivo ({count})",
"backup_album_selection_page_albums_tap": "Toque para incluir, doble toque para excluir",
"backup_album_selection_page_assets_scatter": "Los elementos pueden dispersarse en varios álbumes. De este modo, los álbumes pueden ser incluidos o excluidos durante el proceso de copia de seguridad.",
@@ -1173,8 +1170,8 @@
"explorer": "Explorador",
"export": "Exportar",
"export_as_json": "Exportar a JSON",
"export_database": "Exportar base de datos",
"export_database_description": "Exportar la base de datos SQLite",
"export_database": "Exportar Base de Datos",
"export_database_description": "Exportar la Base de Datos SQLite",
"extension": "Extensión",
"external": "Externo",
"external_libraries": "Bibliotecas externas",
@@ -1195,6 +1192,7 @@
"features": "Características",
"features_in_development": "Funciones en Desarrollo",
"features_setting_description": "Administrar las funciones de la aplicación",
"file_name": "Nombre de archivo:{file_name}",
"file_name_or_extension": "Nombre del archivo o extensión",
"file_size": "Tamaño del archivo",
"filename": "Nombre del archivo",
@@ -1238,7 +1236,7 @@
"group_places_by": "Agrupar lugares por...",
"group_year": "Agrupar por año",
"haptic_feedback_switch": "Activar respuesta háptica",
"haptic_feedback_title": "Respuesta háptica",
"haptic_feedback_title": "Respuesta Háptica",
"has_quota": "Cuota asignada",
"hash_asset": "Generar hash del archivo",
"hashed_assets": "Archivos con hash generado",
@@ -1866,7 +1864,7 @@
"reset_pin_code_description": "Si olvidaste tu código PIN, puedes comunicarte con el administrador del servidor para restablecerlo",
"reset_pin_code_success": "Código PIN restablecido correctamente",
"reset_pin_code_with_password": "Siempre puedes restablecer tu código PIN usando tu contraseña",
"reset_sqlite": "Restablecer la base de datos SQLite",
"reset_sqlite": "Restablecer la Base de Datos SQLite",
"reset_sqlite_confirmation": "¿Estás seguro que deseas restablecer la base de datos SQLite? Deberás cerrar sesión y volver a iniciarla para resincronizar los datos",
"reset_sqlite_success": "Restablecer exitosamente la base de datos SQLite",
"reset_to_default": "Restablecer los valores predeterminados",
@@ -1888,7 +1886,7 @@
"role_viewer": "Visor",
"running": "En ejecución",
"save": "Guardar",
"save_to_gallery": "Guardar en la galería",
"save_to_gallery": "Guardado en la galería",
"saved": "Guardado",
"saved_api_key": "Clave API guardada",
"saved_profile": "Perfil guardado",
@@ -2179,8 +2177,8 @@
"sync": "Sincronizar",
"sync_albums": "Sincronizar álbumes",
"sync_albums_manual_subtitle": "Sincroniza todos los videos y fotos subidos con los álbumes seleccionados a respaldar",
"sync_local": "Sincronización local",
"sync_remote": "Sincronización remota",
"sync_local": "Sincronización Local",
"sync_remote": "Sincronización Remota",
"sync_status": "Estado de la sincronización",
"sync_status_subtitle": "Ver y gestionar el estado de la sincronización",
"sync_upload_album_setting_subtitle": "Crea y sube tus fotos y videos a los álbumes seleccionados en Immich",
@@ -2297,7 +2295,6 @@
"upload_details": "Cargar Detalles",
"upload_dialog_info": "¿Quieres hacer una copia de seguridad al servidor de los elementos seleccionados?",
"upload_dialog_title": "Subir elementos",
"upload_error_with_count": "Error al cargar {count, plural, one {# asset} other {# assets}}",
"upload_errors": "Subida completada con {count, plural, one {# error} other {# errores}}, actualice la página para ver los nuevos recursos de la subida.",
"upload_finished": "Carga finalizada",
"upload_progress": "Restante {remaining, number} - Procesado {processed, number}/{total, number}",

View File

@@ -572,9 +572,6 @@
"asset_list_layout_sub_title": "Asetus",
"asset_list_settings_subtitle": "Fotoruudustiku asetuse sätted",
"asset_list_settings_title": "Fotoruudustik",
"asset_not_found_on_device_android": "Üksust ei leitud seadmest",
"asset_not_found_on_device_ios": "Üksust ei leitud seadmest. Kui kasutad iCloud'i, võib üksus olla iCloud'is oleva vigase faili tõttu kättesaamatu",
"asset_not_found_on_icloud": "Üksust ei leitud iCloud'ist. Üksus võib olla iCloud'is oleva vigase faili tõttu kättesaamatu",
"asset_offline": "Üksus pole kättesaadav",
"asset_offline_description": "Seda välise kogu üksust ei leitud kettalt. Abi saamiseks palun võta ühendust oma Immich'i administraatoriga.",
"asset_restored_successfully": "Üksus edukalt taastatud",
@@ -782,8 +779,6 @@
"client_cert_import": "Impordi",
"client_cert_import_success_msg": "Klientsertifikaat on imporditud",
"client_cert_invalid_msg": "Vigane sertifikaadi fail või vale parool",
"client_cert_password_message": "Sisesta sertifikaadi salasõna",
"client_cert_password_title": "Sertifikaadi salasõna",
"client_cert_remove_msg": "Klientsertifikaat on eemaldatud",
"client_cert_subtitle": "Toetab ainult PKCS12 (.p12, .pfx) formaati. Sertifikaadi importimine/eemaldamine on saadaval ainult enne sisselogimist",
"client_cert_title": "SSL klientsertifikaat [EKSPERIMENTAALNE]",
@@ -993,12 +988,12 @@
"edit_title": "Muuda pealkirja",
"edit_user": "Muuda kasutajat",
"edit_workflow": "Muuda töövoogu",
"editor": "Redaktor",
"editor": "Muutja",
"editor_close_without_save_prompt": "Muudatusi ei salvestata",
"editor_close_without_save_title": "Sulge redaktor?",
"editor_close_without_save_title": "Sulge muutja?",
"editor_confirm_reset_all_changes": "Kas oled kindel, et soovid kõik muudatused tühistada?",
"editor_flip_horizontal": "Peegelda horisontaalselt",
"editor_flip_vertical": "Peegelda vertikaalselt",
"editor_flip_horizontal": "Pööra horisontaalselt",
"editor_flip_vertical": "Pööra vertikaalselt",
"editor_orientation": "Orientatsioon",
"editor_reset_all_changes": "Tühista muudatused",
"editor_rotate_left": "Pööra 90° vastupäeva",
@@ -1197,9 +1192,8 @@
"features": "Funktsioonid",
"features_in_development": "Arendusjärgus olevad funktsioonid",
"features_setting_description": "Halda rakenduse funktsioone",
"file_name": "Failinimi: {file_name}",
"file_name_or_extension": "Failinimi või -laiend",
"file_name_text": "Faili nimi",
"file_name_with_value": "Faili nimi: {file_name}",
"file_size": "Failisuurus",
"filename": "Failinimi",
"filetype": "Failitüüp",
@@ -1565,7 +1559,7 @@
"new_pin_code": "Uus PIN-kood",
"new_pin_code_subtitle": "See on sul esimene kord lukustatud kausta kasutada. Turvaliseks ligipääsuks loo PIN-kood",
"new_timeline": "Uus ajajoon",
"new_update": "Uus versioon",
"new_update": "Uus uuendus",
"new_user_created": "Uus kasutaja lisatud",
"new_version_available": "UUS VERSIOON SAADAVAL",
"newest_first": "Uuemad eespool",
@@ -1603,7 +1597,6 @@
"no_results_description": "Proovi sünonüümi või üldisemat märksõna",
"no_shared_albums_message": "Lisa album, et fotosid ja videosid teistega jagada",
"no_uploads_in_progress": "Üleslaadimisi käimas ei ole",
"none": "Puudub",
"not_allowed": "Keelatud",
"not_available": "Pole saadaval",
"not_in_any_album": "Pole üheski albumis",
@@ -2301,7 +2294,6 @@
"upload_details": "Üleslaadimise üksikasjad",
"upload_dialog_info": "Kas soovid valitud üksuse(d) serverisse varundada?",
"upload_dialog_title": "Üksuse üleslaadimine",
"upload_error_with_count": "Viga {count, plural, one {# üksuse} other {# üksuse}} üleslaadimisel",
"upload_errors": "Üleslaadimine lõpetatud {count, plural, one {# veaga} other {# veaga}}, uute üksuste nägemiseks värskenda lehte.",
"upload_finished": "Üleslaadimine lõpetatud",
"upload_progress": "Ootel {remaining, number} - Töödeldud {processed, number}/{total, number}",

View File

@@ -518,6 +518,7 @@
"external_libraries": "کتابخانه‌های خارجی",
"favorite": "علاقه‌مندی",
"favorites": "علاقه‌مندی‌ها",
"file_name": "نام فایل",
"file_name_or_extension": "نام فایل یا پسوند",
"filename": "نام فایل",
"filetype": "نوع فایل",

View File

@@ -1177,6 +1177,7 @@
"features": "Ominaisuudet",
"features_in_development": "Kehityksessä olevat ominaisuudet",
"features_setting_description": "Hallitse sovelluksen ominaisuuksia",
"file_name": "Tiedoston nimi: {file_name}",
"file_name_or_extension": "Tiedostonimi tai tiedostopääte",
"file_size": "Tiedostokoko",
"filename": "Tiedostonimi",

View File

@@ -572,9 +572,6 @@
"asset_list_layout_sub_title": "Disposition",
"asset_list_settings_subtitle": "Paramètres de disposition de la grille de photos",
"asset_list_settings_title": "Grille de photos",
"asset_not_found_on_device_android": "Média introuvable sur l'appareil",
"asset_not_found_on_device_ios": "Média introuvable sur l'appareil. Si vous utilisez iCloud, le média peut être inaccessible en raison d'un fichier corrompu stocké sur iCloud",
"asset_not_found_on_icloud": "Média introuvable sur iCloud. Le média est peut-être inaccessible en raison d'un fichier corrompu stocké sur iCloud",
"asset_offline": "Média hors ligne",
"asset_offline_description": "Ce média externe n'est plus accessible sur le disque. Veuillez contacter votre administrateur Immich pour obtenir de l'aide.",
"asset_restored_successfully": "Élément restauré avec succès",
@@ -867,7 +864,7 @@
"custom_locale": "Paramètres régionaux personnalisés",
"custom_locale_description": "Afficher les dates et nombres en fonction des paramètres régionaux",
"custom_url": "URL personnalisée",
"cutoff_date_description": "Conservez les photos depuis les derniers…",
"cutoff_date_description": "Conservez les photos depuis le dernier…",
"cutoff_day": "{count, plural, one {jour} other {jours}}",
"cutoff_year": "{count, plural, one {année} other {années}}",
"daily_title_text_date": "E, dd MMM",
@@ -1195,6 +1192,7 @@
"features": "Fonctionnalités",
"features_in_development": "Fonctionnalités en développement",
"features_setting_description": "Gérer les fonctionnalités de l'application",
"file_name": "Nom du fichier : {file_name}",
"file_name_or_extension": "Nom du fichier ou extension",
"file_size": "Taille du fichier",
"filename": "Nom du fichier",
@@ -2228,7 +2226,7 @@
"to_parent": "Aller au dossier parent",
"to_select": "pour faire une sélection",
"to_trash": "Corbeille",
"toggle_settings": "Afficher/masquer les paramètres",
"toggle_settings": "Inverser les paramètres",
"toggle_theme_description": "Changer le thème",
"total": "Total",
"total_usage": "Utilisation globale",
@@ -2297,7 +2295,6 @@
"upload_details": "Détails des envois",
"upload_dialog_info": "Voulez-vous sauvegarder la sélection vers le serveur?",
"upload_dialog_title": "Envoyer le média",
"upload_error_with_count": "Erreur de chargement pour {count, plural, one {# média} other {# médias}}",
"upload_errors": "L'envoi s'est complété avec {count, plural, one {# erreur} other {# erreurs}}. Rafraîchissez la page pour voir les nouveaux médias envoyés.",
"upload_finished": "Envoi fini",
"upload_progress": "{remaining, number} restant(s) - {processed, number} traité(s)/{total, number}",

View File

@@ -572,9 +572,6 @@
"asset_list_layout_sub_title": "Leagan Amach",
"asset_list_settings_subtitle": "Socruithe leagan amach eangach grianghraf",
"asset_list_settings_title": "Eangach Grianghraf",
"asset_not_found_on_device_android": "Níor aimsíodh an tsócmhainn ar an ngléas",
"asset_not_found_on_device_ios": "Sócmhainn gan teacht ar an ngléas. Má tá iCloud in úsáid agat, b'fhéidir nach bhfuil an tsócmhainn inrochtana mar gheall ar chomhad lochtach atá stóráilte ar iCloud",
"asset_not_found_on_icloud": "Sócmhainn gan teacht ar iCloud. Bfhéidir nach bhfuil an tsócmhainn inrochtana mar gheall ar chomhad lochtach atá stóráilte ar iCloud",
"asset_offline": "Sócmhainn As Líne",
"asset_offline_description": "Níl an tsócmhainn sheachtrach seo le fáil ar dhiosca a thuilleadh. Téigh i dteagmháil le riarthóir do Immich le haghaidh cabhrach.",
"asset_restored_successfully": "Athchóiríodh an tsócmhainn go rathúil",
@@ -1195,6 +1192,7 @@
"features": "Gnéithe",
"features_in_development": "Gnéithe i bhForbairt",
"features_setting_description": "Bainistigh gnéithe an aip",
"file_name": "Ainm comhaid: {file_name}",
"file_name_or_extension": "Ainm comhaid nó síneadh",
"file_size": "Méid comhaid",
"filename": "Ainm comhaid",
@@ -2297,7 +2295,6 @@
"upload_details": "Sonraí Uaslódála",
"upload_dialog_info": "Ar mhaith leat cúltaca den Shócmhainn/na Sócmhainní roghnaithe a dhéanamh chuig an bhfreastalaí?",
"upload_dialog_title": "Uaslódáil Sócmhainn",
"upload_error_with_count": "Earráid uaslódála le haghaidh {count, plural, one {# sócmhainn} other {# sócmhainní}}",
"upload_errors": "Uaslódáil críochnaithe le {count, plural, one {# earráid} other {# earráidí}}, athnuachan an leathanach chun sócmhainní uaslódála nua a fheiceáil.",
"upload_finished": "Uaslódáil críochnaithe",
"upload_progress": "Fágtha {remaining, number} - Próiseáilte {processed, number}/{total, number}",

View File

@@ -1139,6 +1139,7 @@
"features": "Funcións",
"features_in_development": "Funcionalidades en Desenvolvemento",
"features_setting_description": "Xestionar as funcións da aplicación",
"file_name": "Nome do ficheiro",
"file_name_or_extension": "Nome do ficheiro ou extensión",
"file_size": "Tamaño do arquivo",
"filename": "Nome do ficheiro",

View File

@@ -1129,6 +1129,7 @@
"features": "Funktione",
"features_in_development": "Feature isch in Entwicklig",
"features_setting_description": "Funkione i de App verwalte",
"file_name": "Dateiname",
"file_name_or_extension": "Dateiname oder -erwiiterig",
"file_size": "Dateigrössi",
"filename": "Dateiname",

View File

@@ -1127,6 +1127,7 @@
"features": "תכונות",
"features_in_development": "תכונות בפיתוח",
"features_setting_description": "ניהול תכונות היישום",
"file_name": "שם הקובץ",
"file_name_or_extension": "שם קובץ או סיומת",
"file_size": "גודל קובץ",
"filename": "שם קובץ",

View File

@@ -1112,6 +1112,7 @@
"features": "विशेषताएँ",
"features_in_development": "विकास में सुविधाएँ",
"features_setting_description": "ऐप सुविधाओं का प्रबंधन करें",
"file_name": "फ़ाइल का नाम",
"file_name_or_extension": "फ़ाइल का नाम या एक्सटेंशन",
"file_size": "फ़ाइल का साइज़",
"filename": "फ़ाइल का नाम",

View File

@@ -1094,6 +1094,7 @@
"features": "Značajke",
"features_in_development": "Značajke u razvoju",
"features_setting_description": "Upravljajte značajkama aplikacije",
"file_name": "Naziv datoteke",
"file_name_or_extension": "Naziv ili ekstenzija datoteke",
"file_size": "Veličina datoteke",
"filename": "Naziv datoteke",

View File

@@ -272,7 +272,7 @@
"oauth_auto_register": "Automatikus regisztráció",
"oauth_auto_register_description": "Új felhasználók automatikus regisztrálása az OAuth használatával történő bejelentkezés után",
"oauth_button_text": "Gomb szövege",
"oauth_client_secret_description": "Bizalmas kliens esetén kötelező, vagy ha az OAuth szolgáltató nem támogatja a PKCE-t (Proof Key for Code Exchange) nyilvános kliensnél.",
"oauth_client_secret_description": "Kötelező, ha az OAuth szolgáltató nem támogatja a PKCE-t (Proof Key for Code Exchange)",
"oauth_enable_description": "Bejelentkezés OAuth használatával",
"oauth_mobile_redirect_uri": "Mobil átirányítási URI",
"oauth_mobile_redirect_uri_override": "Mobil átirányítási URI felülírás",
@@ -351,7 +351,7 @@
"template_settings": "Értesítés sablonok",
"template_settings_description": "Egyéni sablonok kezelése az értesítésekhez",
"theme_custom_css_settings": "Egyéni CSS",
"theme_custom_css_settings_description": "Cascading Style Sheet stíluslapokkal az Immich stílusa megváltoztatható.",
"theme_custom_css_settings_description": "CSS Stíluslapokkal az Immich stílusa megváltoztatható.",
"theme_settings": "Téma beállítások",
"theme_settings_description": "Az Immich webes felületének testreszabása",
"thumbnail_generation_job": "Bélyegképek generálása",
@@ -359,7 +359,7 @@
"transcoding_acceleration_api": "Gyorsító API",
"transcoding_acceleration_api_description": "Az átkódolás felgyorsításához használt eszközödhöz tartozó API. Ez a beállítás „legtöbb, amit megtehetünk” alapon működik: probléma esetén visszaáll szoftveres átkódolásra. A VP9 a hardvertől függően vagy működik, vagy nem.",
"transcoding_acceleration_nvenc": "NVENC (NVIDIA GPU-t igényel)",
"transcoding_acceleration_qsv": "Quick Sync (7. generációs vagy újabb Intel CPU-t igényel)",
"transcoding_acceleration_qsv": "Gyors Szinkronizálás (7. generációs vagy újabb Intel CPU-t igényel)",
"transcoding_acceleration_rkmpp": "RKMPP (csak Rockchip SOC-on)",
"transcoding_acceleration_vaapi": "VAAPI",
"transcoding_accepted_audio_codecs": "Elfogadott audio kodekek",
@@ -451,9 +451,6 @@
"admin_password": "Admin jelszó",
"administration": "Adminisztráció",
"advanced": "Haladó",
"advanced_settings_clear_image_cache": "Fényképek gyorsítótárának kiürítése",
"advanced_settings_clear_image_cache_error": "Fényképek gyorsítótárának kiürítése sikertelen",
"advanced_settings_clear_image_cache_success": "{size} sikeresen felszabadítva",
"advanced_settings_enable_alternate_media_filter_subtitle": "Ezzel a beállítással a szinkronizálás során alternatív kritériumok alapján szűrheted a fájlokat. Csak akkor próbáld ki, ha problémáid vannak azzal, hogy az alkalmazás nem ismeri fel az összes albumot.",
"advanced_settings_enable_alternate_media_filter_title": "[KÍSÉRLETI] Alternatív eszköz album szinkronizálási szűrő használata",
"advanced_settings_log_level_title": "Naplózás szintje: {level}",
@@ -517,7 +514,6 @@
"all": "Mind",
"all_albums": "Minden album",
"all_people": "Minden személy",
"all_photos": "Minden fénykép",
"all_videos": "Minden videó",
"allow_dark_mode": "Sötét téma engedélyezése",
"allow_edits": "Módosítások engedélyezése",
@@ -525,9 +521,6 @@
"allow_public_user_to_upload": "Engedélyezi a feltöltést publikus felhasználó számára",
"allowed": "Engedélyezett",
"alt_text_qr_code": "QR kód kép",
"always_keep": "Tartsa meg mindig",
"always_keep_photos_hint": "A tárhely-felszabadítás nem törli az eszközön található fényképeket.",
"always_keep_videos_hint": "A tárhely-felszabadítás nem törli az eszközön található videókat.",
"anti_clockwise": "Óramutató járásával ellentétes irány",
"api_key": "API kulcs",
"api_key_description": "Ez csak most az egyszer jelenik meg. Az ablak bezárása előtt feltétlenül másold.",
@@ -572,9 +565,6 @@
"asset_list_layout_sub_title": "Elrendezés",
"asset_list_settings_subtitle": "Fotórács elrendezése",
"asset_list_settings_title": "Fotórács",
"asset_not_found_on_device_android": "Az elem nem található az eszközön",
"asset_not_found_on_device_ios": "Az elem nem található az eszközön. Ha az iCloud-ot használod, az elem lehet hogy azért nem elérhető, mert rossz fájl van tárolva az iCloud-on",
"asset_not_found_on_icloud": "Az elem nem található az iCloud-on. Lehet, hogy azért nem elérhető, mert rossz fájl van az iCloud-on tárolva",
"asset_offline": "Elem offline",
"asset_offline_description": "Ez a külső elem már nem elérhető a lemezen. Kérlek, lépj kapcsolatba az Immich adminisztrátorával.",
"asset_restored_successfully": "Elem sikeresen helyreállítva",
@@ -615,11 +605,11 @@
"autoplay_slideshow": "Automatikus diavetítés",
"back": "Vissza",
"back_close_deselect": "Vissza, bezárás, vagy kijelölés törlése",
"background_backup_running_error": "Háttérben futó mentés folyamatban, a kézi mentés nem indítható",
"background_backup_running_error": "Háttérben futó mentés folyamatban, kézi mentés nem indítható",
"background_location_permission": "Háttérben történő helymeghatározási engedély",
"background_location_permission_content": "Hálózatok automatikus váltásához az Immich-nek *mindenképpen* hozzá kell férnie a pontos helyzethez, hogy az alkalmazás le tudja kérni a Wi-Fi hálózat nevét",
"background_options": "Háttérbeli futás beállításai",
"backup": "Biztonsági Mentés",
"backup": "Mentés",
"backup_album_selection_page_albums_device": "Ezen az eszközön lévő albumok ({count})",
"backup_album_selection_page_albums_tap": "Koppints a hozzáadáshoz, duplán koppints 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.",
@@ -698,7 +688,6 @@
"blurred_background": "Homályos háttér",
"bugs_and_feature_requests": "Hibabejelentés és új funkció kérése",
"build": "Felépítés",
"build_image": "Kép elkészítése",
"bulk_delete_duplicates_confirmation": "Biztosan kitörölsz {count, plural, one {# duplikált elemet} other {# duplikált elemet}}? A művelet a legnagyobb méretű elemet tartja meg minden hasonló csoportból és minden másik duplikált elemet kitöröl. Ez a művelet nem visszavonható!",
"bulk_keep_duplicates_confirmation": "Biztosan meg szeretnél tartani {count, plural, other {# egyező elemet}}? Ez a művelet az elemek törlése nélkül megszünteti az összes duplikált csoportosítást.",
"bulk_trash_duplicates_confirmation": "Biztosan kitörölsz {count, plural, one {# duplikált fájlt} other {# duplikált fájlt}}? Ez a művelet megtartja minden csoportból a legnagyobb méretű elemet, és kitöröl minden másik duplikáltat.",
@@ -764,12 +753,11 @@
"cleanup_deleted_assets": "{count} elem áthelyezve az eszköz lomtárába",
"cleanup_deleting": "Lomtárba helyezés...",
"cleanup_found_assets": "{count} feltöltött elem találva",
"cleanup_found_assets_with_size": "{count} feltöltött elem találva ({size})",
"cleanup_icloud_shared_albums_excluded": "Megosztott iCloud albumok nem kerülnek átnézésre",
"cleanup_no_assets_found": "Nincs elem ezekkel a kritériumokkal. Tárhely felszabadításakor csak olyan elemeket törölhet, amelyek már fel lettek töltve a szerverre",
"cleanup_no_assets_found": "Nincs feltöltött elem ezekkel a kritériumokkal",
"cleanup_preview_title": "Törlendő elemek ({count})",
"cleanup_step3_description": "Szerverre feltöltött elemek keresése dátum és egyéb megadott szűrési kritériumok szerint.",
"cleanup_step4_summary": "{count} {date} előtti elem eltávolításra fog kerülni erről az eszközről. Az elemek továbbra is elérhetők lesznek az Immich alkalmazásban.",
"cleanup_step3_description": "Szerverre feltöltött képek és videók keresése dátum és egyéb megadott szűrési kritériumok szerint",
"cleanup_step4_summary": "{count} {date} előtti elem eltávolításra fog kerülni erről az eszközről",
"cleanup_trash_hint": "A tárhely visszanyeréséhez nyisd meg a beépített galéria alkalmazást és töröld a lomtárat",
"clear": "Törlés",
"clear_all": "Alaphelyzet",
@@ -798,7 +786,7 @@
"comments_are_disabled": "A megjegyzések le vannak tiltva",
"common_create_new_album": "Új album létrehozása",
"completed": "Kész",
"confirm": "Jóváhagyás",
"confirm": "Jóváhagy",
"confirm_admin_password": "Admin jelszó megerősítése",
"confirm_delete_face": "Biztos, hogy törölni szeretnéd a(z) {name} arcát az elemről?",
"confirm_delete_shared_link": "Biztosan törölni szeretnéd ezt a megosztott linket?",
@@ -867,7 +855,7 @@
"custom_locale": "Egyéni területi beállítás",
"custom_locale_description": "Dátumok és számok formázása a nyelv és terület szerint",
"custom_url": "Egyéni URL",
"cutoff_date_description": "Fotók megtartása az elmúlt…",
"cutoff_date_description": "Ennél régebbi fotók és videók eltávolítása",
"cutoff_day": "{count, plural, one {nap} other {nap}}",
"cutoff_year": "{count, plural, one {év} other {év}}",
"daily_title_text_date": "MMM dd (E)",
@@ -891,7 +879,7 @@
"default_locale": "Alapértelmezett területi beállítás",
"default_locale_description": "Dátumok és számok formázása a böngésződ területi beállítása alapján",
"delete": "Törlés",
"delete_action_confirmation_message": "Biztosan törölni szeretnéd ezt az elemet? Így az elem a szerver lomtárába kerül, és megkérdezi, hogy törölni szeretnéd-e a az eszközön is",
"delete_action_confirmation_message": "Biztosan törölni szeretnéd ezt az elemet? Így az elem a szerver lomtárába kerül, és a megkérdezi, hogy törölni szeretnéd-e a helyi másolatot is",
"delete_action_prompt": "{count} törölve",
"delete_album": "Album törlése",
"delete_api_key_prompt": "Biztosan törölni szeretnéd ezt az API kulcsot?",
@@ -1019,7 +1007,6 @@
"error_change_sort_album": "Album sorbarendezésének megváltoztatása sikertelen",
"error_delete_face": "Hiba az arc törlése során",
"error_getting_places": "Hiba a helyek betöltésekor",
"error_loading_albums": "Hiba az albumok betöltésekor",
"error_loading_image": "Hiba a kép betöltése közben",
"error_loading_partners": "Hiba a partnerek betöltésénél: {error}",
"error_retrieving_asset_information": "Hiba az elem adatainak lekérése közben",
@@ -1195,6 +1182,7 @@
"features": "Beállítások",
"features_in_development": "Folyamatban lévő fejlesztések",
"features_setting_description": "Az alkalmazás jellemzőinek kezelése",
"file_name": "Fájlnév: {file_name}",
"file_name_or_extension": "Fájlnév vagy kiterjesztés",
"file_size": "Fájlméret",
"filename": "Fájlnév",
@@ -1214,7 +1202,7 @@
"forgot_pin_code_question": "Elfelejtetted a PIN kódod?",
"forward": "Előre",
"free_up_space": "Tárhely felszabadítása",
"free_up_space_description": "Hely felszabadítása érdekében helyezze át a mentett fotókat és videókat az eszköz kukájába. A szerveren lévő másolatok biztonságban maradnak.",
"free_up_space_description": "Hely felszabadítása érdekében helyezze át a mentett fotókat és videókat az eszköz kukájába. A szerveren lévő másolatok biztonságban maradnak",
"free_up_space_settings_subtitle": "Eszköz tárhely felszabadítása",
"full_path": "Teljes eléréi útvonal: {path}",
"gcast_enabled": "Google Cast",
@@ -1331,15 +1319,9 @@
"json_editor": "JSON szerkesztő",
"json_error": "JSON hiba",
"keep": "Megtart",
"keep_albums": "Albumok megtartása",
"keep_albums_count": "{count} album megtartása",
"keep_all": "Összes megtartása",
"keep_description": "Válaszd ki, mi maradjon az eszközödön tárhely felszabadításakor.",
"keep_favorites": "Kedvencek megtartása",
"keep_on_device": "Maradjon az eszközön",
"keep_on_device_hint": "Válaszd ki az eszközön tartandó elemeket",
"keep_this_delete_others": "Ennek a meghagyása, a többi törlése",
"keeping": "Meg lesz tartva: {items}",
"kept_this_deleted_others": "Ez az elem és a töröltek meg lettek hagyva {count, plural, one {# asset} other {# assets}}",
"keyboard_shortcuts": "Billentyűparancsok",
"language": "Nyelv",
@@ -1416,7 +1398,7 @@
"login_form_failed_get_oauth_server_config": "Nem sikerült az OAuth bejelentkezés. Ellenőrizd a szerver URL-t",
"login_form_failed_get_oauth_server_disable": "OAuth bejelentkezés nem elérhető ezen a szerveren",
"login_form_failed_login": "Hiba a bejelentkezés közben, ellenőrizd a szerver címét, az emailt és a jelszót",
"login_form_handshake_exception": "Handshake hiba történt a szerverrel. Engedélyezd a saját aláírású tanúsítványok használatát a beállításokban, ha ilyen tanúsítványt használsz.",
"login_form_handshake_exception": "SSL Kézfogási Hiba törént. Engedélyezd az önaláírt tanúsítvényokat a beállításokban, hogy ha önaláírt tanúsítványt használsz.",
"login_form_password_hint": "jelszó",
"login_form_save_login": "Maradjon bejelentkezve",
"login_form_server_empty": "Add meg a szerver címét.",
@@ -1440,7 +1422,7 @@
"maintenance_logged_in_as": "Bejelentkezve mint: {user}",
"maintenance_restore_from_backup": "Helyreállítás biztonsági mentésből",
"maintenance_restore_library": "Könyvtár helyreállítása",
"maintenance_restore_library_confirm": "Ha ez jónak tűnik, tovább a biztonsági mentés visszaállítására!",
"maintenance_restore_library_confirm": "Ha ez jónak tűnik,",
"maintenance_restore_library_description": "Adatbázis helyreállítása",
"maintenance_restore_library_folder_has_files": "{folder} {count} mappával rendelkezik",
"maintenance_restore_library_folder_no_files": "{folder}-ból/-ből fájlok hiányoznak!",
@@ -1569,7 +1551,6 @@
"next_memory": "Következő emlék",
"no": "Nem",
"no_actions_added": "Még nincsenek műveletek",
"no_albums_found": "Nem találhatók albumok",
"no_albums_message": "Fotóid és videóid rendszerezéséhez hozz létre egy új albumot",
"no_albums_with_name_yet": "Úgy tűnik, hogy ilyen névvel még nincs albumod.",
"no_albums_yet": "Úgy tűnik, hogy még egy albumod sincs.",
@@ -1599,7 +1580,6 @@
"no_results_description": "Próbálkozz szinonimákkal vagy általánosabb kulcsszavakkal",
"no_shared_albums_message": "Hozz létre egy új albumot, hogy megoszthasd fényképeid és videóid másokkal",
"no_uploads_in_progress": "Nincs folyamatban lévő feltöltés",
"none": "Semelyik",
"not_allowed": "Nem engedélyezett",
"not_available": "N/A",
"not_in_any_album": "Nincs albumban",
@@ -1897,7 +1877,7 @@
"scaffold_body_error_occurred": "Hiba történt",
"scan": "Átfésül",
"scan_all_libraries": "Minden képtár átfésülése",
"scan_library": "Beolvasás",
"scan_library": "Scan",
"scan_settings": "Átfésülési beállítások",
"scanning": "Átfésülés folyamatban",
"scanning_for_album": "Albumok átfésülése...",
@@ -1972,7 +1952,6 @@
"select_all_in": "Összes kijelölése itt: {group}",
"select_avatar_color": "Avatár színének választása",
"select_count": "{count, plural, one {# kiválasztása} other {# kiválasztása}}",
"select_cutoff_date": "Határdátum választása",
"select_face": "Arc kiválasztása",
"select_featured_photo": "Alapértelmezett fénykép kiválasztása",
"select_from_computer": "Kiválasztás a számítógépről",
@@ -2147,7 +2126,7 @@
"sort_recent": "Legújabb fénykép",
"sort_title": "Cím",
"source": "Forrás",
"stack": "Kollázs",
"stack": "Fotók csoportosítása",
"stack_action_prompt": "{count} egymásra helyezve",
"stack_duplicates": "Duplikátumok csoportosítása",
"stack_select_one_photo": "Válassz egy fő képet a csoportból",
@@ -2212,7 +2191,6 @@
"theme_setting_theme_subtitle": "Alkalmazás témájának választása",
"theme_setting_three_stage_loading_subtitle": "A háromlépcsős betöltés javíthatja a betöltési teljesítményt, de jelentősen növeli a hálózati forgalmat",
"theme_setting_three_stage_loading_title": "Háromlépcsős betöltés engedélyezése",
"then": "Akkor",
"they_will_be_merged_together": "Egyesítve lesznek",
"third_party_resources": "Harmadik féltől származó források",
"time": "Idő",
@@ -2297,7 +2275,6 @@
"upload_details": "Feltöltés állapota",
"upload_dialog_info": "Szeretnél mentést készíteni a kiválasztott elem(ek)ről a szerverre?",
"upload_dialog_title": "Elem feltöltése",
"upload_error_with_count": "Feltöltési hiba {count} elemnél",
"upload_errors": "Feltöltés befejezve {count, plural, other {# hibával}}, frissítsd az oldalt az újonnan feltöltött elemek megtekintéséhez.",
"upload_finished": "Feltöltés befejezve",
"upload_progress": "{remaining, number} hátra van - {processed, number}/{total, number} feldolgozva",
@@ -2366,8 +2343,6 @@
"viewer_stack_use_as_main_asset": "Fő elemnek beállítás",
"viewer_unstack": "Csoport megszüntetése",
"visibility_changed": "{count, plural, other {# személy}} láthatósága megváltozott",
"visual": "Vizuális",
"visual_builder": "Vizuális összerakó",
"waiting": "Várakozik",
"waiting_count": "Várakozik: {count}",
"warning": "Figyelmeztetés",

View File

@@ -5,21 +5,21 @@
"acknowledge": "Mengerti",
"action": "Tindakan",
"action_common_update": "Perbarui",
"action_description": "Tindakan yang perlu dijalankan pada aset yang terfilter",
"action_description": "Sebuah kelompok perbuatan untuk melakukan suatu aksi pada aset-aset yang terfiltrasi",
"actions": "Tindakan",
"active": "Aktif",
"active_count": "Aktif: {count}",
"activity": "Aktivitas",
"activity_changed": "Aktivitas {enabled, select, true {aktif} other {nonaktif}}",
"activity_changed": "Aktivitas {enabled, select, true {diaktifkan} other {dinonaktifkan}}",
"add": "Tambahkan",
"add_a_description": "Tambah keterangan",
"add_a_description": "Tambahkan sebuah deskripsi",
"add_a_location": "Tambahkan lokasi",
"add_a_name": "Tambahkan nama",
"add_a_title": "Tambahkan judul",
"add_action": "Tambah tindakan",
"add_action_description": "Klik untuk menambahkan tindakan yang perlu dijalankan",
"add_action": "Tambah aksi",
"add_action_description": "Klik untuk menambahkan aksi yang akan dilakukan",
"add_assets": "Tambahkan aset",
"add_birthday": "Tambahkan tanggal lahir",
"add_birthday": "Tambahkan Tanggal Lahir",
"add_endpoint": "Tambahkan titik akhir",
"add_exclusion_pattern": "Tambahkan pola pengecualian",
"add_filter": "Tambahkan filter",
@@ -104,8 +104,6 @@
"image_preview_description": "Gambar berukuran sedang tanpa metadata, digunakan ketika melihat aset satuan dan untuk pembelajaran mesin",
"image_preview_quality_description": "Kualitas pratinjau dari 1-100. Lebih tinggi lebih baik, tetapi menghasilkan berkas lebih besar dan respons aplikasi. Menetapkan nilai rendah dapat memengaruhi kualitas pembelajaran mesin.",
"image_preview_title": "Pengaturan Pratinjau",
"image_progressive": "Progresif",
"image_progressive_description": "Enkode gambar-gambar JPEG secara progresif untuk memuat tampilan secara bertahap. Ini tidak berpengaruh pada gambar-gambar WebP.",
"image_quality": "Kualitas",
"image_resolution": "Resolusi",
"image_resolution_description": "Resolusi yang lebih tinggi dapat menyimpan lebih banyak detail tetapi memerlukan waktu yang lebih lama untuk di-enkode, memiliki ukuran berkas yang lebih besar, dan dapat mengurangi respons aplikasi.",
@@ -190,21 +188,10 @@
"machine_learning_smart_search_enabled": "Aktifkan pencarian pintar",
"machine_learning_smart_search_enabled_description": "Jika dinonaktifkan, gambar tidak akan dienkode untuk pencarian pintar.",
"machine_learning_url_description": "URL server pembelajaran mesin. Jika lebih dari satu URL disediakan, setiap server akan dicoba satu per satu sampai salah satu berhasil merespons, dari urutan pertama sampai terakhir. Server yang tidak merespons akan diabaikan sementara sampai kembali daring.",
"maintenance_delete_backup": "Hapus Cadangan",
"maintenance_delete_backup_description": "File ini akan dihapus secara permanen.",
"maintenance_delete_error": "Gagal menghapus cadangan.",
"maintenance_restore_backup": "Mengembalikan Cadangan",
"maintenance_restore_backup_description": "Immich akan dihapus dan dikembalikan dari candangan yang dipilih. Sebuah candangan akan dibuat sebelum dilanjutkan.",
"maintenance_restore_backup_different_version": "Cadangan ini dibuat dengan versi Immich yang berbeda!",
"maintenance_restore_backup_unknown_version": "Tidak dapat menentukan versi candangan.",
"maintenance_restore_database_backup": "Mengembalikan cadangan database",
"maintenance_restore_database_backup_description": "Kembalikan ke keadaan database sebelumnya menggunakan sebuah file cadangan",
"maintenance_settings": "Pemeliharaan",
"maintenance_settings_description": "Setel mode pemeliharaan Immich.",
"maintenance_start": "Pindah ke mode pemeliharaan",
"maintenance_start": "Mulai mode pemeliharaan",
"maintenance_start_error": "Gagal memulai mode pemeliharaan.",
"maintenance_upload_backup": "Unggah file candangan database",
"maintenance_upload_backup_error": "Tidak dapat mengunggah cadangan, apakah ini sebuah file .sql/.sql.gz?",
"manage_concurrency": "Kelola Konkurensi",
"manage_concurrency_description": "Pindah ke halaman tugas untuk mengelola konkurensi tugas",
"manage_log_settings": "Kelola pengaturan log",
@@ -272,7 +259,7 @@
"oauth_auto_register": "Pendaftaran otomatis",
"oauth_auto_register_description": "Daftar pengguna baru secara otomatis setelah log masuk dengan OAuth",
"oauth_button_text": "Teks tombol",
"oauth_client_secret_description": "Diperlukan untuk klien yang konfidensial, atau jika PKCE (Proof Key for Code Exchange) tidak didukung untuk klien umum.",
"oauth_client_secret_description": "Diperlukan jika PKCE (Proof Key for Code Exchange) tidak didukung oleh penyedia OAuth",
"oauth_enable_description": "Log masuk dengan OAuth",
"oauth_mobile_redirect_uri": "URI pengalihan ponsel",
"oauth_mobile_redirect_uri_override": "Penimpaan URI penerusan ponsel",
@@ -451,9 +438,6 @@
"admin_password": "Kata Sandi Admin",
"administration": "Administrasi",
"advanced": "Tingkat lanjut",
"advanced_settings_clear_image_cache": "Bersihkan Cache Gambar",
"advanced_settings_clear_image_cache_error": "Gagal untuk membersihkan cache gambar",
"advanced_settings_clear_image_cache_success": "Sukses menghapus {size}",
"advanced_settings_enable_alternate_media_filter_subtitle": "Gunakan opsi ini untuk menyaring media saat sinkronisasi berdasarkan kriteria alternatif. Hanya coba ini dengan aplikasi mendeteksi semua album.",
"advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTAL] Gunakan saringan sinkronisasi album perangkat alternatif",
"advanced_settings_log_level_title": "Tingkat log: {level}",
@@ -517,7 +501,6 @@
"all": "Semua",
"all_albums": "Semua album",
"all_people": "Semua orang",
"all_photos": "Semua foto",
"all_videos": "Semua video",
"allow_dark_mode": "Perbolehkan mode gelap",
"allow_edits": "Perbolehkan penyuntingan",
@@ -525,9 +508,6 @@
"allow_public_user_to_upload": "Perbolehkan pengguna publik untuk mengunggah",
"allowed": "Diijinkan",
"alt_text_qr_code": "Gambar kode QR",
"always_keep": "Selalu simpan",
"always_keep_photos_hint": "Fitur Bebaskan Ruang ruang akan menyimpan semua foto di perangkat ini.",
"always_keep_videos_hint": "Fitur Bebaskan Ruang ruang akan menyimpan semua video di perangkat ini.",
"anti_clockwise": "Berlawanan arah jarum jam",
"api_key": "Kunci API",
"api_key_description": "Nilai ini hanya akan ditampilkan sekali. Pastikan untuk menyalin sebelum menutup jendela ini.",
@@ -572,9 +552,6 @@
"asset_list_layout_sub_title": "Penataan",
"asset_list_settings_subtitle": "Setelan grid foto",
"asset_list_settings_title": "Grid Foto",
"asset_not_found_on_device_android": "Aset tidak ditemukan di perangkat",
"asset_not_found_on_device_ios": "Aset tidak ditemukan di perangkat. Jika kamu menggunakan iCloud, aset mungkin tidak dapat diakses karena berkas rusak di iCloud",
"asset_not_found_on_icloud": "Aset tidak ditemukan di iCloud. Aset mungkin tidak dapat diakses karena berkas rusak di iCloud",
"asset_offline": "Aset Luring",
"asset_offline_description": "Aset eksternal ini tidak ada lagi di diska. Silakan hubungi administrator Immich Anda untuk bantuan.",
"asset_restored_successfully": "Aset telah berhasil dipulihkan",
@@ -626,7 +603,7 @@
"backup_album_selection_page_select_albums": "Pilih album",
"backup_album_selection_page_selection_info": "Info Pilihan",
"backup_album_selection_page_total_assets": "Total aset unik",
"backup_albums_sync": "Sinkronisasi Cadangan Album",
"backup_albums_sync": "Sinkronisasi cadangan album",
"backup_all": "Semua",
"backup_background_service_backup_failed_message": "Gagal mencadangkan aset. Mencoba lagi…",
"backup_background_service_complete_notification": "Pencadangan aset selesai",
@@ -761,16 +738,6 @@
"city": "Kota",
"cleanup_confirm_description": "Immich menemukan {count} aset (dibuat sebelum {date}) telah aman dicadangkan di server. Hapus salinan lokal dari perangkat ini?",
"cleanup_confirm_prompt_title": "Hapus dari perangkat ini?",
"cleanup_deleted_assets": "Pindahkan {count} aset ke tempat sampah di perangkat",
"cleanup_deleting": "Memindahkan ke tempat sampah...",
"cleanup_found_assets": "Menemukan {count} aset cadangan",
"cleanup_found_assets_with_size": "Menemukan {count} aset cadangan ({size})",
"cleanup_icloud_shared_albums_excluded": "iCloud Shared Albums dikecualikan dari pemindaian",
"cleanup_no_assets_found": "Tidak ada aset yang ditemukan dengan kriteria diatas. Fitur membebaskan ruang hanya dapat menghapus aset yang dicadangkan ke server",
"cleanup_preview_title": "Aset yang akan dihapus ({count})",
"cleanup_step3_description": "Pindah untuk cadangan aset yang sesuai dengan tanggal mu dan simpan pengaturan.",
"cleanup_step4_summary": "{count} aset (dibuat sebelum {date}) untuk dihapus dari perangkat lokal. Foto akan tetap dapat diakses dari aplikasi Immich.",
"cleanup_trash_hint": "Untuk dapat mengambil semua ruang penyimpanan, buka aplikasi galeri pada sistem dan kosongkan tempat sampah",
"clear": "Hapus",
"clear_all": "Hapus semua",
"clear_all_recent_searches": "Hapus semua pencarian terakhir",
@@ -856,18 +823,13 @@
"created_at": "Dibuat",
"creating_linked_albums": "Membuat album tertaut...",
"crop": "Pangkas",
"crop_aspect_ratio_fixed": "Diperbaiki",
"crop_aspect_ratio_free": "Bebas",
"crop_aspect_ratio_original": "Asli",
"curated_object_page_title": "Benda",
"current_device": "Perangkat saat ini",
"current_pin_code": "Kode PIN saat ini",
"current_server_address": "Alamat server saat ini",
"custom_date": "Tanggal kustom",
"custom_locale": "Lokal Khusus",
"custom_locale_description": "Format tanggal dan angka berdasarkan bahasa dan wilayah",
"custom_url": "URL Kustom",
"cutoff_date_description": "Simpan foto dari…",
"daily_title_text_date": "E, dd MMM",
"daily_title_text_date_year": "E, dd MMM yyyy",
"dark": "Gelap",
@@ -949,7 +911,6 @@
"download_include_embedded_motion_videos": "Video tertanam",
"download_include_embedded_motion_videos_description": "Sertakan video yang di sematkan dalam foto bergerak sebagai file terpisah",
"download_notfound": "Unduhan tidak ditemukan",
"download_original": "Unduh berkas asli",
"download_paused": "Unduhan dijeda",
"download_settings": "Unduhan",
"download_settings_description": "Kelola pengaturan berkaitan dengan pengunduhan aset",
@@ -959,7 +920,6 @@
"download_waiting_to_retry": "Menunggu untuk mencoba lagi",
"downloading": "Mengunduh",
"downloading_asset_filename": "Mengunduh aset {filename}",
"downloading_from_icloud": "Mengunduh dari iCloud",
"downloading_media": "Mengunduh media",
"drop_files_to_upload": "Lepaskan berkas di mana saja untuk mengunggah",
"duplicates": "Duplikat",
@@ -992,13 +952,6 @@
"editor": "Penyunting",
"editor_close_without_save_prompt": "Perubahan tidak akan di simpan",
"editor_close_without_save_title": "Tutup editor?",
"editor_confirm_reset_all_changes": "Apakah anda yakin mau mengatur ulang semua perubahan?",
"editor_flip_horizontal": "Balik horizontal",
"editor_flip_vertical": "Balik vertikal",
"editor_orientation": "Orientasi",
"editor_reset_all_changes": "Mengatur ulang perubahan",
"editor_rotate_left": "Putar 90° berlawanan arah jarum jam",
"editor_rotate_right": "Putar 90° searah jarum jam",
"email": "Surel",
"email_notifications": "Notifikasi surel",
"empty_folder": "Folder ini kosong",
@@ -1017,14 +970,11 @@
"error_change_sort_album": "Gagal mengubah urutan album",
"error_delete_face": "Terjadi kesalahan menghapus wajah dari aset",
"error_getting_places": "Kesalahan saat mengambil lokasi",
"error_loading_albums": "Gagal memuat album",
"error_loading_image": "Terjadi eror memuat gambar",
"error_loading_partners": "Kesalahan saat memuat partner: {error}",
"error_retrieving_asset_information": "Gagal mendapatkan informasi aset",
"error_saving_image": "Kesalahan: {error}",
"error_tag_face_bounding_box": "Galat saat memberi tag wajah tidak dapat memperoleh koordinat kotak pembatas",
"error_title": "Eror - Ada yang salah",
"error_while_navigating": "Gagal saat berpindah ke aset",
"errors": {
"cannot_navigate_next_asset": "Tidak dapat menuju ke aset berikutnya",
"cannot_navigate_previous_asset": "Tidak dapat menuju ke aset sebelumnya",
@@ -1148,7 +1098,6 @@
"unable_to_update_workflow": "Tidak dapat memperbarui alur kerja",
"unable_to_upload_file": "Tidak dapat mengunggah berkas"
},
"errors_text": "Gagal",
"exclusion_pattern": "Pola pengecualian",
"exif": "EXIF",
"exif_bottom_sheet_description": "Tambahkan Deskripsi...",
@@ -1193,6 +1142,7 @@
"features": "Fitur",
"features_in_development": "Fitur dalam Pengembangan",
"features_setting_description": "Kelola fitur aplikasi",
"file_name": "Nama berkas: {file_name}",
"file_name_or_extension": "Nama berkas atau ekstensi",
"file_size": "Ukuran berkas",
"filename": "Nama berkas",
@@ -1211,7 +1161,6 @@
"folders_feature_description": "Menjelajahi tampilan folder untuk foto dan video pada sistem file",
"forgot_pin_code_question": "Lupa PIN?",
"forward": "Maju",
"free_up_space": "Bebaskan ruang",
"full_path": "Jalur lengkap: {path}",
"gcast_enabled": "Google Cast",
"gcast_enabled_description": "Fitur ini memuat sumber daya eksternal dari Google agar dapat berfungsi.",

View File

@@ -1022,6 +1022,7 @@
"features": "Eiginleikar",
"features_in_development": "Eiginleikar í þróun",
"features_setting_description": "Sýsla með eiginleika smáforrits",
"file_name": "Skráarheiti",
"file_name_or_extension": "Skráarheiti eða nafnauki",
"file_size": "Skráarstærð",
"filename": "Skráarheiti",

View File

@@ -104,8 +104,6 @@
"image_preview_description": "Immagine a media dimensione senza metadati, utilizzata durante la visualizzazione di una singola risorsa e per il machine learning",
"image_preview_quality_description": "Qualità dell'anteprima da 1 a 100. Più alto è meglio ma produce file più pesanti e può ridurre la reattività dell'app. Impostare un valore basso può influenzare negativamente la qualità del machine learning.",
"image_preview_title": "Impostazioni dell'anteprima",
"image_progressive": "Progressiva",
"image_progressive_description": "Codifica progressivamente le immagini JPEG per mostrarle con un caricamento graduale. Questo non ha effetto sulle immagini WebP.",
"image_quality": "Qualità",
"image_resolution": "Risoluzione",
"image_resolution_description": "Risoluzioni più elevate possono preservare più dettagli ma richiedere più tempo per la codifica, avere dimensioni di file più grandi e ridurre la reattività dell'app.",
@@ -272,7 +270,7 @@
"oauth_auto_register": "Registrazione automatica",
"oauth_auto_register_description": "Automaticamente registra nuovi utenti dopo il login OAuth",
"oauth_button_text": "Testo pulsante",
"oauth_client_secret_description": "Richiesto per client confidenziali o se PKCE (Proof Key for Code Exchange) non è supportato dal client pubblico.",
"oauth_client_secret_description": "Richiesto se PKCE (Proof Key for Code Exchange) non è supportato dal provider OAuth",
"oauth_enable_description": "Login con OAuth",
"oauth_mobile_redirect_uri": "URI di reindirizzamento per app mobile",
"oauth_mobile_redirect_uri_override": "Sovrascrivi URI di reindirizzamento per app mobile",
@@ -517,7 +515,6 @@
"all": "Tutti",
"all_albums": "Tutti gli album",
"all_people": "Tutte le persone",
"all_photos": "Tutte le foto",
"all_videos": "Tutti i video",
"allow_dark_mode": "Permetti Tema Scuro",
"allow_edits": "Permetti modifiche",
@@ -525,9 +522,6 @@
"allow_public_user_to_upload": "Permetti agli utenti pubblici di caricare",
"allowed": "Consentito",
"alt_text_qr_code": "Immagine QR",
"always_keep": "Mantieni sempre",
"always_keep_photos_hint": "Libera Spazio mantiene tutte le foto su questo dispositivo.",
"always_keep_videos_hint": "Libera Spazio mantiene tutti i video su questo dispositivo.",
"anti_clockwise": "Senso anti-orario",
"api_key": "Chiave API",
"api_key_description": "Questo valore verrà mostrato una sola volta. Assicurati di copiarlo prima di chiudere la finestra.",
@@ -572,9 +566,6 @@
"asset_list_layout_sub_title": "Layout",
"asset_list_settings_subtitle": "Impostazioni del layout della griglia delle foto",
"asset_list_settings_title": "Griglia foto",
"asset_not_found_on_device_android": "Risorsa non trovata sul dispositivo",
"asset_not_found_on_device_ios": "Risorsa non trovata sul dispositivo. Se stai usando iCloud, la risorsa potrebbe essere inaccessibile a causa di un file errato salvato su iCloud",
"asset_not_found_on_icloud": "Risorsa non trovata su iCloud. La risorsa potrebbe essere inaccessibile a causa di un file errato salvato su iCloud",
"asset_offline": "Elemento Offline",
"asset_offline_description": "Questa risorsa esterna non esiste più sul disco. Contatta il tuo amministratore di Immich per assistenza.",
"asset_restored_successfully": "Risorsa ripristinata con successo",
@@ -764,12 +755,11 @@
"cleanup_deleted_assets": "Spostate {count} risorse nel cestino",
"cleanup_deleting": "Spostamento nel cestino...",
"cleanup_found_assets": "Trovate {count} risorse già salvate",
"cleanup_found_assets_with_size": "Trovate {count} risorse salvate ({size})",
"cleanup_icloud_shared_albums_excluded": "Gli Album Condivisi di iCloud sono esclusi dalla ricerca",
"cleanup_no_assets_found": "Nessuna risorsa trovata con i criteri specificati. Libera Spazio può solo rimuovere le risorse che sono state salvate sul server",
"cleanup_no_assets_found": "Nessuna risorsa già salvata corrisponde ai criteri richiesti",
"cleanup_preview_title": "Risorse da rimuovere ({count})",
"cleanup_step3_description": "Ricerca risorse già salvate sul server corrispondenti alle opzioni di ricerca.",
"cleanup_step4_summary": "{count} risorse (create prima del {date}) da rimuovere sul tuo dispositivo. Rimarrano comunque accessibili dall'app Immich.",
"cleanup_step3_description": "Ricerca foto e video che sono stati già salvati sul server e che corrispondono alle opzioni di ricerca",
"cleanup_step4_summary": "{count} risorse create prima del {date} sono in coda per la rimozione dal dispositivo",
"cleanup_trash_hint": "Per recuperare completamente lo spazio devi aprire l'app della galleria e svuotarne il cestino",
"clear": "Pulisci",
"clear_all": "Pulisci tutto",
@@ -867,7 +857,7 @@
"custom_locale": "Localizzazione personalizzata",
"custom_locale_description": "Formatta data e numeri in base alla lingua e al paese",
"custom_url": "URL personalizzato",
"cutoff_date_description": "Mantieni le foto fino al…",
"cutoff_date_description": "Rimuovi foto e video più vecchi del",
"cutoff_day": "{count, plural, one {giorno} other {giorni}}",
"cutoff_year": "{count, plural, one {anno} other {anni}}",
"daily_title_text_date": "E, dd MMM",
@@ -1019,7 +1009,6 @@
"error_change_sort_album": "Errore nel cambiare l'ordine di degli album",
"error_delete_face": "Errore nella rimozione del volto dalla risorsa",
"error_getting_places": "Errore durante il recupero dei luoghi",
"error_loading_albums": "Errore nel caricamento degli album",
"error_loading_image": "Errore nel caricamento dell'immagine",
"error_loading_partners": "Errore durante il caricamento dei partner: {error}",
"error_retrieving_asset_information": "Errore nel recuperare informazioni sull'elemento",
@@ -1195,6 +1184,7 @@
"features": "Funzionalità",
"features_in_development": "Funzionalità in fase di sviluppo",
"features_setting_description": "Gestisci le funzionalità dell'app",
"file_name": "Nome file: {file_name}",
"file_name_or_extension": "Nome file o estensione",
"file_size": "Dimensione del file",
"filename": "Nome file",
@@ -1331,15 +1321,9 @@
"json_editor": "Modificatore JSON",
"json_error": "JSON errore",
"keep": "Mantieni",
"keep_albums": "Mantieni gli album",
"keep_albums_count": "{count} {count, plural, one {Album} other {Album}} mantenuti",
"keep_all": "Tieni tutto",
"keep_description": "Scegli cosa rimane sul tuo dispositivo quando liberi spazio.",
"keep_favorites": "Mantieni i favoriti",
"keep_on_device": "Mantieni sul dispositivo",
"keep_on_device_hint": "Seleziona le risorse da mantenere sul dispositivo",
"keep_this_delete_others": "Tieni questo, elimina gli altri",
"keeping": "Mantieni: {items}",
"kept_this_deleted_others": "Mantenuto questa risorsa ed {count, plural, one {eliminata # risorsa} other {eliminate # risorse}}",
"keyboard_shortcuts": "Scorciatoie da tastiera",
"language": "Lingua",
@@ -1599,7 +1583,6 @@
"no_results_description": "Prova ad usare un sinonimo oppure una parola chiave più generica",
"no_shared_albums_message": "Crea un album per condividere foto e video con le persone nella tua rete",
"no_uploads_in_progress": "Nessun upload in corso",
"none": "Nulla",
"not_allowed": "Non permesso",
"not_available": "N/A",
"not_in_any_album": "In nessun album",
@@ -2134,8 +2117,6 @@
"skip_to_folders": "Salta alle cartelle",
"skip_to_tags": "Salta alle etichette",
"slideshow": "Presentazione",
"slideshow_repeat": "Ripeti presentazione",
"slideshow_repeat_description": "Ricomincia da capo quando la presentazione termina",
"slideshow_settings": "Impostazioni presentazione",
"sort_albums_by": "Ordina album per...",
"sort_created": "Data creazione",
@@ -2297,7 +2278,6 @@
"upload_details": "Dettagli di caricamento",
"upload_dialog_info": "Vuoi fare il backup sul server delle risorse selezionate?",
"upload_dialog_title": "Carica Risorsa",
"upload_error_with_count": "Invio in errore per {count, plural, one {# risorsa} other {# risorse}}",
"upload_errors": "Caricamento completato con {count, plural, one {# errore} other {# errori}}, ricarica la pagina per vedere le risorse caricate.",
"upload_finished": "Upload terminato",
"upload_progress": "Rimanenti {remaining, number} - Processati {processed, number}/{total, number}",

View File

@@ -572,9 +572,6 @@
"asset_list_layout_sub_title": "レイアウト",
"asset_list_settings_subtitle": "グリッドに関する設定",
"asset_list_settings_title": "グリッド",
"asset_not_found_on_device_android": "デバイス上に写真/動画が見つかりません",
"asset_not_found_on_device_ios": "デバイス上に写真/動画が見つかりませんでした。iCloudを併せてご利用の場合は、iCloudのファイル保管方法に問題があり、アクセスできない可能性があります。",
"asset_not_found_on_icloud": "iCloud上の写真/動画が見つかりませんでした。",
"asset_offline": "項目がオフラインです",
"asset_offline_description": "この外部項目はディスク上にもうありません。Immichサーバーの管理者に連絡をしてください。",
"asset_restored_successfully": "復元できました",
@@ -868,8 +865,6 @@
"custom_locale_description": "言語と地域に基づいて日付と数値をフォーマットします",
"custom_url": "カスタムURL",
"cutoff_date_description": "写真を保持する期間:",
"cutoff_day": "{count, plural, one {(日)} other {(日)}}",
"cutoff_year": "{count, plural, one {年} other {年}}",
"daily_title_text_date": "MM DD, EE",
"daily_title_text_date_year": "yyyy MM DD, EE",
"dark": "ダークモード",
@@ -1195,6 +1190,7 @@
"features": "機能",
"features_in_development": "開発中の機能",
"features_setting_description": "アプリの機能を管理する",
"file_name": "ファイル名: {file_name}",
"file_name_or_extension": "ファイル名または拡張子",
"file_size": "ファイルサイズ",
"filename": "ファイル名",
@@ -1332,7 +1328,6 @@
"json_error": "JSONエラー",
"keep": "保持",
"keep_albums": "アルバムを保持",
"keep_albums_count": "{count}個のアルバムを残す",
"keep_all": "全て保持",
"keep_description": "ストレージを解放する際に、デバイスに残すものを選択できます。",
"keep_favorites": "お気に入りを保持",
@@ -1795,7 +1790,6 @@
"rating_clear": "評価を取り消す",
"rating_count": "星{count, plural, one {#つ} other {#つ}}",
"rating_description": "情報欄にEXIFの評価を表示",
"rating_set": "お気に入り度 {rating, plural, one {# ツ星} other {# ツ星}}",
"reaction_options": "リアクションの選択",
"read_changelog": "変更履歴を読む",
"readonly_mode_disabled": "読み取り専用モード無効",
@@ -2212,7 +2206,6 @@
"theme_setting_theme_subtitle": "テーマ設定",
"theme_setting_three_stage_loading_subtitle": "三段階読み込みを有効にすると、パフォーマンスが改善する可能性がありますが、ネットワーク負荷が著しく増加します。",
"theme_setting_three_stage_loading_title": "三段階読み込みをオンにする",
"then": "そのとき",
"they_will_be_merged_together": "これらは一緒に統合されます",
"third_party_resources": "サードパーティーリソース",
"time": "時刻",
@@ -2297,7 +2290,6 @@
"upload_details": "アップロードの詳細",
"upload_dialog_info": "選択した項目のバックアップをしますか?",
"upload_dialog_title": "アップロード",
"upload_error_with_count": "{count, plural, one {#個の写真/動画} other {#個の写真/動画}}についてアップロードエラーが発生しました",
"upload_errors": "アップロードは{count, plural, one {#個} other {#個}}のエラーで完了しました、新しくアップロードされたアセットを見るにはページを更新してください。",
"upload_finished": "アップロード完了",
"upload_progress": "残り {remaining, number} - {processed, number}/{total, number} 処理済み",

View File

@@ -16,7 +16,6 @@
"add_a_name": "დაამატე სახელი",
"add_a_title": "დაასათაურე",
"add_action": "დაამატე მოქმედება",
"add_assets": "რესურსის ატვირთვა",
"add_birthday": "დაბადების დღის დამატება",
"add_endpoint": "ბოლოწერტილის დამატება",
"add_exclusion_pattern": "დაამატე გამონაკლისი ნიმუში",
@@ -33,7 +32,6 @@
"add_to_album_bottom_sheet_already_exists": "{album}-ში უკვე არსებობს",
"add_to_albums": "დაამატე ალბომებში",
"add_to_albums_count": "დაამატე ალბომში ({count})",
"add_to_bottom_bar": "დამატება სად",
"add_to_shared_album": "დაამატე საზიარო ალბომში",
"add_url": "დაამატე URL",
"added_to_archive": "დაარქივდა",
@@ -88,8 +86,6 @@
"oauth_settings": "OAuth",
"template_email_preview": "მინიატურა",
"transcoding_acceleration_vaapi": "VAAPI",
"transcoding_hardware_acceleration": "ჰარდვეარული ამაჩქარებელი",
"transcoding_policy": "ტრანსკოდირების პოლიტიკა",
"transcoding_threads": "ნაკადები",
"transcoding_tone_mapping": "ტონების ასახვა"
},

View File

@@ -104,8 +104,6 @@
"image_preview_description": "메타데이터가 제거된 중간 크기 이미지. 기계 학습 또는 개별 항목을 표시할 때 사용됩니다.",
"image_preview_quality_description": "미리보기의 품질을 1에서 100 사이로 설정합니다. 값을 높이면 품질이 좋아지지만 파일 크기가 커지고 앱 반응 속도가 느려질 수 있습니다. 너무 낮은 값은 기계 학습에 영향을 줄 수 있습니다.",
"image_preview_title": "미리보기 설정",
"image_progressive": "점진적 로딩",
"image_progressive_description": "JPEG 이미지를 점진적으로 표시할 수 있게 단계적으로 인코딩합니다. WebP 이미지에는 영향이 없습니다.",
"image_quality": "품질",
"image_resolution": "해상도",
"image_resolution_description": "해상도가 높으면 세부 정보가 보존되지만, 인코딩에 더 많은 시간이 소요되고 파일 크기가 커져 앱 반응 속도가 느려질 수 있습니다.",
@@ -194,7 +192,6 @@
"maintenance_delete_backup_description": "이 파일은 영구적으로 삭제됩니다.",
"maintenance_delete_error": "백업 삭제 실패.",
"maintenance_restore_backup": "백업 복원",
"maintenance_restore_backup_description": "Immich가 삭제되고 선택한 백업에서 복원됩니다. 계속하기 전에 백업이 생성됩니다.",
"maintenance_restore_backup_different_version": "이 백업은 다른 버전의 Immich에서 생성되었습니다!",
"maintenance_restore_backup_unknown_version": "백업 버전을 확인할 수 없습니다.",
"maintenance_restore_database_backup": "데이터베이스 백업 복원",
@@ -272,7 +269,7 @@
"oauth_auto_register": "자동 등록",
"oauth_auto_register_description": "OAuth 로그인 후 새 사용자를 자동으로 등록합니다.",
"oauth_button_text": "버튼 텍스트",
"oauth_client_secret_description": "비공개 클라이언트 또는 공개 클라이언트가 PKCE(Proof Key for Code Exchange, 코드 교환용 검증 키)를 지원하지 않는 경우 필요합니다.",
"oauth_client_secret_description": "OAuth 제공자가 PKCE(Proof Key for Code Exchange, 코드 교환용 검증 키)를 지원하지 않는 경우 필요합니다.",
"oauth_enable_description": "OAuth 로그인",
"oauth_mobile_redirect_uri": "모바일 리다이렉트 URI",
"oauth_mobile_redirect_uri_override": "모바일 리다이렉트 URI 오버라이드",
@@ -355,7 +352,7 @@
"theme_settings": "테마 설정",
"theme_settings_description": "Immich 웹 인터페이스를 사용자 정의합니다.",
"thumbnail_generation_job": "섬네일 생성",
"thumbnail_generation_job_description": "각 항목 및 인물에 대해 크고 작은 썸네일, 흐릿한 네일 생성",
"thumbnail_generation_job_description": "각 항목 및 인물에 대해 크고 작은 썸네일, 흐릿한 네일 생성",
"transcoding_acceleration_api": "가속 API",
"transcoding_acceleration_api_description": "트랜스코딩 가속에 사용할 API를 지정합니다. 이 설정은 'best effort' 방식으로 동작하며, 실패 시 소프트웨어 트랜스코딩으로 전환됩니다. 하드웨어에 따라 VP9은 지원되지 않을 수 있습니다.",
"transcoding_acceleration_nvenc": "NVENC (NVIDIA GPU 필요)",
@@ -451,9 +448,6 @@
"admin_password": "관리자 비밀번호",
"administration": "관리",
"advanced": "고급",
"advanced_settings_clear_image_cache": "이미지 캐시 지우기",
"advanced_settings_clear_image_cache_error": "이미지 캐시 삭제 실패",
"advanced_settings_clear_image_cache_success": "{size}가 성공적으로 정리됨",
"advanced_settings_enable_alternate_media_filter_subtitle": "이 옵션을 사용하면 동기화 중 미디어를 대체 기준으로 필터링할 수 있습니다. 앱이 모든 앨범을 제대로 감지하지 못할 때만 사용하세요.",
"advanced_settings_enable_alternate_media_filter_title": "대체 기기 앨범 동기화 필터 사용 (실험적)",
"advanced_settings_log_level_title": "로그 레벨: {level}",
@@ -517,7 +511,6 @@
"all": "모두",
"all_albums": "모든 앨범",
"all_people": "모든 인물",
"all_photos": "모든 사진",
"all_videos": "모든 동영상",
"allow_dark_mode": "다크 모드 사용",
"allow_edits": "편집자로 설정",
@@ -525,9 +518,6 @@
"allow_public_user_to_upload": "모든 사용자의 업로드 허용",
"allowed": "허용됨",
"alt_text_qr_code": "QR 코드 이미지",
"always_keep": "항상 유지",
"always_keep_photos_hint": "이 기기에 모든 사진이 보관됩니다.",
"always_keep_videos_hint": "이 기기에 모든 동영상이 보관됩니다.",
"anti_clockwise": "반시계 방향",
"api_key": "API 키",
"api_key_description": "이 값은 한 번만 표시됩니다. 창을 닫기 전 반드시 복사해주세요.",
@@ -572,9 +562,6 @@
"asset_list_layout_sub_title": "레이아웃",
"asset_list_settings_subtitle": "사진 배열 레이아웃 설정",
"asset_list_settings_title": "사진 배열",
"asset_not_found_on_device_android": "기기에서 항목을 찾을 수 없음",
"asset_not_found_on_device_ios": "기기에서 항목을 찾을 수 없습니다. iCloud를 사용하는 경우 저장된 파일이 손상되었을 수 있습니다.",
"asset_not_found_on_icloud": "iCloud에서 항목을 찾을 수 없습니다. iCloud에 저장된 파일이 손상되었을 수 있습니다.",
"asset_offline": "누락된 항목",
"asset_offline_description": "디스크에서 항목을 더이상 찾을 수 없습니다. 서버 관리자에게 연락하세요.",
"asset_restored_successfully": "항목이 복원되었습니다.",
@@ -763,14 +750,7 @@
"cleanup_confirm_prompt_title": "이 기기에서 삭제하시겠습니까?",
"cleanup_deleted_assets": "{count}개 항목 휴지통으로 이동됨",
"cleanup_deleting": "휴지통으로 이동 중...",
"cleanup_found_assets": "백업된 {count}개의 항목 찾",
"cleanup_found_assets_with_size": "백업된 {count}개의 항목 찾음 ({size})",
"cleanup_icloud_shared_albums_excluded": "iCloud의 공유 앨범은 스캔 대상에서 제외됩니다",
"cleanup_no_assets_found": "위 조건에 일치하는 항목이 없습니다. 저장 공간 확보 기능은 서버에 백업된 항목만 삭제할 수 있습니다.",
"cleanup_preview_title": "삭제 대상 항목 ({count})",
"cleanup_step3_description": "백업된 항목에서 날짜 및 보존 설정과 일치하는 항목을 스캔합니다.",
"cleanup_step4_summary": "({date} 이전에 생성된) {count} 항목이 로컬 기기에서 삭제됩니다. 사진은 Immich 앱에서 계속 액세스할 수 있습니다.",
"cleanup_trash_hint": "저장 공간을 완전히 확보하려면 시스템의 갤러리 앱을 열고 휴지통을 비우세요",
"cleanup_found_assets": "백업된 {count}개의 항목았습니다",
"clear": "지우기",
"clear_all": "모두 지우기",
"clear_all_recent_searches": "검색 기록 전체 삭제",
@@ -856,20 +836,15 @@
"created_at": "생성됨",
"creating_linked_albums": "연결된 앨범 생성 중...",
"crop": "자르기",
"crop_aspect_ratio_fixed": "고정",
"crop_aspect_ratio_free": "직접 조절",
"crop_aspect_ratio_original": "원본",
"curated_object_page_title": "사물",
"current_device": "현재 기기",
"current_pin_code": "현재 PIN 코드",
"current_server_address": "현재 서버 주소",
"custom_date": "날짜 선택",
"custom_locale": "사용자 지정 로케일",
"custom_locale_description": "언어 및 지역에 따른 날짜 및 숫자 형식 지정",
"custom_url": "사용자 지정 URL",
"cutoff_date_description": "선택한 기간의 사진을 유지합니다…",
"cutoff_day": "{count, plural, one {일} other {일}}",
"cutoff_year": "{count, plural, one {년} other {년}}",
"daily_title_text_date": "M월 d일 EEEE",
"daily_title_text_date_year": "yyyy년 M월 d일 EEEE",
"dark": "다크",
@@ -951,7 +926,6 @@
"download_include_embedded_motion_videos": "모션 포토 영상",
"download_include_embedded_motion_videos_description": "모션 포토에 포함된 동영상을 별도의 파일로 분리해 저장합니다.",
"download_notfound": "다운로드할 수 없음",
"download_original": "원본 다운로드",
"download_paused": "다운로드 일시 중지됨",
"download_settings": "다운로드",
"download_settings_description": "파일 다운로드 설정을 관리합니다.",
@@ -961,7 +935,6 @@
"download_waiting_to_retry": "재시도 대기 중",
"downloading": "다운로드",
"downloading_asset_filename": "{filename} 다운로드 중...",
"downloading_from_icloud": "iCloud에서 다운로드 중",
"downloading_media": "미디어 다운로드 중",
"drop_files_to_upload": "아무 곳에나 파일을 드롭하여 업로드",
"duplicates": "비슷한 항목",
@@ -991,14 +964,12 @@
"edit_title": "제목 변경",
"edit_user": "사용자 수정",
"edit_workflow": "워크플로 편집",
"editor": "편집",
"editor": "편집",
"editor_close_without_save_prompt": "변경 사항이 저장되지 않습니다.",
"editor_close_without_save_title": "편집을 종료하시겠습니까?",
"editor_confirm_reset_all_changes": "모든 수정사항을 초기화하시겠습니까?",
"editor_flip_horizontal": "좌우반전",
"editor_flip_vertical": "상하반전",
"editor_orientation": "방향",
"editor_reset_all_changes": "편집내용 초기화",
"editor_rotate_left": "반시계 방향으로 90° 회전",
"editor_rotate_right": "시계 방향으로 90° 회전",
"email": "이메일",
@@ -1018,10 +989,9 @@
"error": "오류",
"error_change_sort_album": "앨범 표시 순서 변경 실패",
"error_delete_face": "항목에서 얼굴 삭제 중 오류 발생",
"error_getting_places": "장소 로 오류",
"error_loading_albums": "앨범 로딩 오류",
"error_loading_image": "이미지 로딩 오류",
"error_loading_partners": "파트너 로딩 오류: {error}",
"error_getting_places": "장소 로 오류",
"error_loading_image": "이미지를 불러오는 중 오류 발생",
"error_loading_partners": "파트너 불러오기 실패: {error}",
"error_saving_image": "오류: {error}",
"error_tag_face_bounding_box": "얼굴 태그 실패 - 얼굴의 위치를 가져올 수 없습니다.",
"error_title": "오류 - 문제가 발생했습니다",
@@ -1191,6 +1161,7 @@
"features": "기능",
"features_in_development": "개발 중인 기능",
"features_setting_description": "사진 및 동영상 관리 기능을 설정합니다.",
"file_name": "파일 이름: {file_name}",
"file_name_or_extension": "파일명 또는 확장자",
"file_size": "파일 크기",
"filename": "파일명",
@@ -1209,9 +1180,7 @@
"folders_feature_description": "파일 시스템의 사진과 동영상을 폴더 보기로 탐색합니다.",
"forgot_pin_code_question": "PIN 번호를 잊어버렸나요?",
"forward": "앞으로",
"free_up_space": "저장 공간 확보",
"free_up_space_description": "백업된 사진과 동영상을 기기의 휴지통으로 이동하여 저장 공간을 확보하세요. 원본 파일은 서버에 안전하게 보관됩니다.",
"free_up_space_settings_subtitle": "기기의 저장 공간을 확보합니다.",
"free_up_space_description": "백업된 사진과 동영상을 기기의 휴지통으로 이동하여 저장 공간 확보하세요. 원본 파일은 서버에 안전하게 보관됩니다",
"full_path": "전체 경로: {path}",
"gcast_enabled": "구글 캐스트",
"gcast_enabled_description": "이 기능은 Google의 외부 리소스를 사용합니다.",
@@ -1327,15 +1296,9 @@
"json_editor": "JSON 편집기",
"json_error": "JSON 오류",
"keep": "유지",
"keep_albums": "앨범 유지",
"keep_albums_count": "{count}개의 {count, plural, one {앨범} other {앨범}} 유지",
"keep_all": "모두 유지",
"keep_description": "저장 공간 확보시에 유지할 항목을 선택하세요.",
"keep_favorites": "즐겨찾기 유지",
"keep_on_device": "기기에 유지",
"keep_on_device_hint": "이 기기에 유지할 항목을 선택합니다",
"keep_this_delete_others": "이 항목은 유지하고 나머지는 삭제",
"keeping": "유지: {items}",
"kept_this_deleted_others": "이 항목을 유지하고 {count, plural, one {#개의 항목} other {#개의 항목}}을 삭제함",
"keyboard_shortcuts": "키보드 단축키",
"language": "언어",
@@ -1494,8 +1457,6 @@
"minimize": "최소화",
"minute": "분",
"minutes": "분",
"mirror_horizontal": "수평",
"mirror_vertical": "수직",
"missing": "누락",
"mobile_app": "모바일 앱",
"mobile_app_download_onboarding_note": "다음 옵션 중 하나를 사용해 모바일 앱을 다운로드하세요.",
@@ -1507,7 +1468,6 @@
"move_down": "아래로 이동",
"move_off_locked_folder": "잠금 폴더에서 해제",
"move_to": "다음으로 이동",
"move_to_device_trash": "기기의 휴지통으로 이동",
"move_to_lock_folder_action_prompt": "잠금 폴더로 항목 {count}개 이동됨",
"move_to_locked_folder": "잠금 폴더로 이동",
"move_to_locked_folder_confirmation": "선택한 사진 또는 동영상이 모든 앨범에서 제거되며, 잠금 폴더에서만 볼 수 있습니다.",
@@ -1547,7 +1507,6 @@
"next_memory": "다음 추억",
"no": "아니요",
"no_actions_added": "아직 추가된 작업이 없습니다",
"no_albums_found": "앨범이 없습니다.",
"no_albums_message": "앨범을 생성하여 사진과 동영상을 정리하기",
"no_albums_with_name_yet": "아직 해당하는 이름의 앨범이 없는 것 같습니다.",
"no_albums_yet": "아직 앨범이 없는 것 같습니다.",
@@ -1844,7 +1803,6 @@
"reset_sqlite_confirmation": "SQLite 데이터베이스를 초기화하시겠습니까? 데이터를 재동기화하려면 로그아웃 후 다시 로그인해야 합니다.",
"reset_sqlite_success": "SQLite 데이터베이스를 초기화했습니다.",
"reset_to_default": "기본값으로 복원",
"resolution": "해상도",
"resolve_duplicates": "비슷한 항목 확인",
"resolved_all_duplicates": "비슷한 항목을 모두 처리했습니다.",
"restore": "복원",
@@ -1869,7 +1827,6 @@
"saved_settings": "설정이 저장되었습니다.",
"say_something": "댓글을 입력하세요",
"scaffold_body_error_occurred": "오류가 발생했습니다.",
"scan": "스캔",
"scan_all_libraries": "모든 라이브러리 스캔",
"scan_library": "스캔",
"scan_settings": "스캔 설정",
@@ -2103,8 +2060,6 @@
"skip_to_folders": "폴더로 건너뛰기",
"skip_to_tags": "태그로 건너뛰기",
"slideshow": "슬라이드 쇼",
"slideshow_repeat": "슬라이드 쇼 반복",
"slideshow_repeat_description": "슬라이드 쇼가 끝나면 처음으로 되돌아갑니다",
"slideshow_settings": "슬라이드 쇼 설정",
"sort_albums_by": "다음으로 앨범 정렬...",
"sort_created": "생성된 날짜",
@@ -2236,7 +2191,6 @@
"unhide_person": "인물 숨김 해제",
"unknown": "알 수 없음",
"unknown_country": "알 수 없는 지역",
"unknown_date": "알 수 없는 날짜",
"unknown_year": "알 수 없는 연도",
"unlimited": "무제한",
"unlink_motion_video": "모션 비디오 링크 해제",
@@ -2265,7 +2219,6 @@
"upload_details": "업로드 상세",
"upload_dialog_info": "선택한 항목을 서버에 백업하시겠습니까?",
"upload_dialog_title": "항목 업로드",
"upload_error_with_count": "{count, plural, one {#개} other {#개}} 항목 업로드 실패",
"upload_errors": "업로드가 완료되었습니다. 항목 {count, plural, one {#개} other {#개}}를 업로드하지 못했습니다. 업로드된 항목을 보려면 페이지를 새로고침하세요.",
"upload_finished": "업로드 완료",
"upload_progress": "전체 {total, number}개 중 {processed, number}개 완료, {remaining, number}개 대기 중",

View File

@@ -1154,6 +1154,7 @@
"features": "Funkcijos",
"features_in_development": "Kūrimo funkcijos",
"features_setting_description": "Valdyti aplikacijos funkcijas",
"file_name": "Failo pavadinimas: {file_name}",
"file_name_or_extension": "Failo pavadinimas arba plėtinys",
"file_size": "Failo dydis",
"filename": "Failopavadinimas",

View File

@@ -900,6 +900,7 @@
"favorites_page_no_favorites": "Nav atrasti iecienītākie faili",
"features_in_development": "Izstrādes stadijā esošas funkcijas",
"features_setting_description": "Lietotnes funkciju pārvaldība",
"file_name": "Faila nosaukums",
"file_name_or_extension": "Faila nosaukums vai paplašinājums",
"filename": "Faila nosaukums",
"filetype": "Faila tips",
@@ -1138,8 +1139,6 @@
"map_settings_only_show_favorites": "Rādīt tikai izlasi",
"map_settings_theme_settings": "Kartes Dizains",
"map_zoom_to_see_photos": "Attāliniet, lai redzētu fotoattēlus",
"mark_all_as_read": "Atzīmēt visus kā lasītus",
"marked_all_as_read": "Visi atzīmēti kā lasīti",
"matches": "Atbilstības",
"media_type": "Faila veids",
"memories": "Atmiņas",
@@ -1197,7 +1196,6 @@
"new_person": "Jauna persona",
"new_pin_code": "Jaunais PIN kods",
"new_timeline": "Jaunā laikjosla",
"new_update": "Pieejams atjauninājums",
"new_user_created": "Izveidots jauns lietotājs",
"new_version_available": "PIEEJAMA JAUNA VERSIJA",
"next": "Nākamais",
@@ -1489,7 +1487,7 @@
"select_album": "Izvēlies albumu",
"select_album_cover": "Izvēlieties albuma vāciņu",
"select_albums": "Izvēlies albumus",
"select_all_duplicates": "Atlasīt visus paturēšanai",
"select_all_duplicates": "Atlasīt visus dublikātus",
"select_avatar_color": "Izvēlies avatāra krāsu",
"select_face": "Izvēlies seju",
"select_from_computer": "Izvēlēties no datora",
@@ -1638,7 +1636,6 @@
"sort_title": "Nosaukums",
"source": "Pirmkods",
"stack": "Apvienot kaudzē",
"stack_duplicates": "Apvienot dublikātus kaudzē",
"start": "Sākt",
"start_date": "Sākuma datums",
"start_date_before_end_date": "Sākuma datumam jābūt pirms beigu datuma",
@@ -1721,7 +1718,6 @@
"unnamed_album": "Albums bez nosaukuma",
"unsaved_change": "Nesaglabāta izmaiņa",
"unselect_all": "Atcelt visu atlasi",
"unselect_all_duplicates": "Atlasīt visus dzēšanai",
"unstack": "At-Stekot",
"unsupported_field_type": "Nesatbalstīts lauka tips",
"untitled_workflow": "Nenosaukta darba plūsma",

View File

@@ -216,6 +216,7 @@
"favorite": "Омилено",
"favorites": "Омилени",
"features": "Функии",
"file_name": "Име на датотека",
"filename": "Име на датотека",
"filetype": "Тип на датотека",
"filter_people": "Филтрирај луѓе",

View File

@@ -1106,6 +1106,7 @@
"features": "ഫീച്ചറുകൾ",
"features_in_development": "വികസിപ്പിച്ചുകൊണ്ടിരിക്കുന്ന ഫീച്ചറുകൾ",
"features_setting_description": "ആപ്പ് ഫീച്ചറുകൾ കൈകാര്യം ചെയ്യുക",
"file_name": "ഫയലിന്റെ പേര്",
"file_name_or_extension": "ഫയലിന്റെ പേര് അല്ലെങ്കിൽ എക്സ്റ്റൻഷൻ",
"file_size": "ഫയൽ വലിപ്പം",
"filename": "ഫയൽനാമം",

View File

@@ -1101,6 +1101,7 @@
"features": "वैशिष्ट्ये",
"features_in_development": "विकासाधीन वैशिष्ट्ये",
"features_setting_description": "अॅपची वैशिष्ट्ये व्यवस्थापित करा",
"file_name": "फाईल नाव",
"file_name_or_extension": "फाईल नाव किंवा एक्स्टेंशन",
"file_size": "फाइल साइज़",
"filename": "फाइलनाव",

View File

@@ -50,7 +50,7 @@
"add_exclusion_pattern_description": "Legg til ekskluderingsmønstre. Globbing med *, ** og ? støttes. For å ignorere alle filer i en hvilken som helst mappe som heter \"Raw\", bruk \"**/Raw/**\". For å ignorere alle filer som slutter på \".tif\", bruk \"**/*.tif\". For å ignorere en absolutt filplassering, bruk \"/filsti/til/ignorer/**\".",
"admin_user": "Administrasjonsbruker",
"asset_offline_description": "Dette eksterne bibliotekselementet finnes ikke lenger på disk og har blitt flyttet til papirkurven. Hvis filen ble flyttet innad i biblioteket, se etter det tilsvarende elementet i tidslinjen din. For å gjenopprette elementet, vennligst sørg for at filstien under er tilgjengelig for Immich og skann biblioteket.",
"authentication_settings": "Godkjenninger",
"authentication_settings": "Godkjenningsinnstillinger",
"authentication_settings_description": "Administrer passord, OAuth, og andre innstillinger for autentisering",
"authentication_settings_disable_all": "Er du sikker på at du ønsker å deaktivere alle innloggingsmetoder? Innlogging vil bli fullstendig deaktivert.",
"authentication_settings_reenable": "For å aktivere på nytt, bruk en <link>Server Command</link>.",
@@ -65,7 +65,7 @@
"backup_onboarding_footer": "For mer informasjon om sikkerhetskopiering av Immich, se <link>dokumentasjonen</link>.",
"backup_onboarding_parts_title": "En 3-2-1 sikkerhetskopi inkluderer:",
"backup_onboarding_title": "Sikkerhetskopier",
"backup_settings": "Database-dump",
"backup_settings": "Database-dump instillinger",
"backup_settings_description": "Håndter innstillinger for database-dump.",
"cleared_jobs": "Ryddet opp jobber for: {job}",
"config_set_by_file": "Konfigurasjonen er for øyeblikket satt av en konfigurasjonsfil",
@@ -86,8 +86,8 @@
"export_config_as_json_description": "Last ned nåværende systemkonfigurasjon som en JSON fil",
"external_libraries_page_description": "Administrering for eksterne bibliotek",
"face_detection": "Ansiktsgjenkjennelse",
"face_detection_description": "Finn ansikter i bilder ved hjelp av maskinlæring. For videoer brukes bare miniatyrbildet. \"Alle\" går gjennom alle bilder (igjen). \"Tilbakestill\" fjerner all gjeldende ansiktsdata. \"Mangler\" legger til filer som ikke har blitt behandlet enda i køen. Oppdagede ansikter vil blir sendt til ansiktsgjenkjenning, og koblet til eksisterende eller nye personer.",
"facial_recognition_job_description": "Kobler oppdagede ansikt til personer. Dette utføres etter at ansiktssøk er fullført. \"Tilbakestill\" (om-)grupperer alle ansikt på nytt. \"Mangler\" stiller opp ansikt som ikke har blitt tilordnet en person ennå.",
"face_detection_description": "Finn ansikter i bilder ved hjelp av maskinlæring. For videoer brukes bare miniatyrbildet. \"Alle\" går gjennom alle bilder (igjen). \"Tilbakestill\" fjerner all gjeldende ansiktsdata. \"Manglende\" legger til filer som ikke har blitt behandlet enda i køen. Oppdagede ansikter vil blir sendt til ansiktsgjenkjenning, og koblet til eksisterende eller nye personer.",
"facial_recognition_job_description": "Kobler oppdagede ansikt til personer. Dette utføres etter at ansiktssøk er fullført. \"Tilbakestill\" (om-)grupperer alle ansikt på nytt. \"Missing\" stiller opp ansikt som ikke har blitt tilordnet en person ennå.",
"failed_job_command": "Kommandoen {command} feilet for jobb: {job}",
"force_delete_user_warning": "ADVARSEL: Dette vil umiddelbart fjerne brukeren og alle data. Dette kan ikke angres, og filene kan ikke gjenopprettes.",
"image_format": "Format",
@@ -265,7 +265,7 @@
"notification_email_test_email_sent": "En test-e-post er sendt til {email}. Vennligst sjekk innboksen din.",
"notification_email_username_description": "Brukernavn som skal brukes ved autentisering med e-posts serveren",
"notification_enable_email_notifications": "Aktiver e-postvarsler",
"notification_settings": "Varselinnstillinger",
"notification_settings": "Innstillinger for varsler",
"notification_settings_description": "Administrer varselinnstillinger, inkludert e-post",
"oauth_auto_launch": "Automatisk oppstart",
"oauth_auto_launch_description": "Start OAuth-innloggingsflyten automatisk når du navigerer til innloggingssiden",
@@ -378,7 +378,7 @@
"transcoding_constant_rate_factor": "Konstant ratefaktor (-crf)",
"transcoding_constant_rate_factor_description": "Nivået på videokvaliteten. Typiske verdier er 23 for H.264, 28 for HEVC, 31 for VP9 og 35 for AV1. Lavere verdier gir bedre kvalitet, men større filstørrelser.",
"transcoding_disabled_description": "Ikke transkoder noen videoer; dette kan føre til avspillingsproblemer på visse klienter",
"transcoding_encoding_options": "Kodek-alternativer",
"transcoding_encoding_options": "Kodek Alternativer",
"transcoding_encoding_options_description": "Sett kodeks, oppløsning, kvalitet og andre valg for koding av videoer",
"transcoding_hardware_acceleration": "Maskinvareakselerasjon",
"transcoding_hardware_acceleration_description": "Eksperimentell: raskere transkoding, men kan ha lavere kvalitet ved samme bithastighet",
@@ -572,9 +572,6 @@
"asset_list_layout_sub_title": "Fordeling",
"asset_list_settings_subtitle": "Innstillinger for layout av fotorutenett",
"asset_list_settings_title": "Fotorutenett",
"asset_not_found_on_device_android": "Elementet ble ikke funnet på enheten",
"asset_not_found_on_device_ios": "Elementet ble ikke funnet på enheten. Hvis du bruker iCloud, kan elementet være utilgjengelig på grunn av en feilaktig fil er lagret i iCloud",
"asset_not_found_on_icloud": "Elementet ble ikke funnet på iCloud. Elementet kan være utilgjengelig fordi det ligger en feilaktig fil i iCloud",
"asset_offline": "Fil utilgjengelig",
"asset_offline_description": "Dette elementet er offline. Immich kan ikke aksessere dets lokasjon. Vennligst påse at elementet er tilgjengelig og skann så biblioteket på nytt.",
"asset_restored_successfully": "Objekt(er) gjenopprettet",
@@ -1008,13 +1005,13 @@
"empty_trash_confirmation": "Vil du virkelig Tømme søppelbøtta? Dette vil slette alle filene i søppelbøtta permanent fra Immich.\nDu kan ikke angre denne handlingen!",
"enable": "Aktivere",
"enable_backup": "Aktiver backup",
"enable_biometric_auth_description": "Skriv inn PIN-koden for å aktivere biometrisk autentisering",
"enable_biometric_auth_description": "Skriv inn PINkoden for å aktivere biometrisk autentisering",
"enabled": "Aktivert",
"end_date": "Sluttdato",
"end_date": "Slutt dato",
"enqueued": "I kø",
"enter_wifi_name": "Skriv inn Wi-Fi navn",
"enter_your_pin_code": "Skriv inn din PIN-kode",
"enter_your_pin_code_subtitle": "Skriv inn din PIN-kode for å få tilgang til låst mappe",
"enter_your_pin_code": "Skriv inn din PIN kode",
"enter_your_pin_code_subtitle": "Skriv inn din PIN kode for å få tilgang til låst mappe",
"error": "Feil",
"error_change_sort_album": "Mislyktes ved endring av sorteringsrekkefølge på album",
"error_delete_face": "Feil ved sletting av ansikt fra aktivia",
@@ -1194,7 +1191,8 @@
"feature_photo_updated": "Fremhevet bilde oppdatert",
"features": "Funksjoner",
"features_in_development": "Funksjoner under utvikling",
"features_setting_description": "Administrer funksjoner for appen",
"features_setting_description": "Administrerer funksjoner for appen",
"file_name": "Filnavn: {file_name}",
"file_name_or_extension": "Filnavn eller filtype",
"file_size": "Filstørrelse",
"filename": "Filnavn",
@@ -1429,7 +1427,7 @@
"logs": "Logger",
"longitude": "Lengdegrad",
"look": "Se",
"loop_videos": "Gjenta videoer",
"loop_videos": "Gjenta Videoer",
"loop_videos_description": "Aktiver for å automatisk loope en video i detaljeviseren.",
"main_branch_warning": "Du bruker en utviklingsversjon; vi anbefaler på det sterkeste og bruke en utgitt versjon!",
"main_menu": "Hovedmeny",
@@ -1780,7 +1778,7 @@
"purchase_panel_title": "Hjelp prosjektet",
"purchase_per_server": "For hver server",
"purchase_per_user": "For hver bruker",
"purchase_remove_product_key": "Fjern produktnøkkel",
"purchase_remove_product_key": "Ta bor Produktnøkkel",
"purchase_remove_product_key_prompt": "Vil du virkelig ta bort produktnøkkelen?",
"purchase_remove_server_product_key": "Ta bort Server Produktnøkkel",
"purchase_remove_server_product_key_prompt": "Vil du virkelig ta bort Server Produktnøkkelen?",
@@ -1794,7 +1792,7 @@
"rating": "Stjernevurdering",
"rating_clear": "Slett vurdering",
"rating_count": "{count, plural, one {# sjerne} other {# stjerner}}",
"rating_description": "Vis EXIF vurdering i informasjonspanel",
"rating_description": "Hvis EXIF vurdering i informasjons panelet",
"rating_set": "Vurdering satt til {rating, plural, one {# stjerne} other {# stjerner}}",
"reaction_options": "Reaksjonsalternativer",
"read_changelog": "Les endringslogg",
@@ -1821,7 +1819,7 @@
"refreshes_every_file": "Oppdaterer alle filer",
"refreshing_encoded_video": "Oppdaterer kodete video",
"refreshing_faces": "Oppdaterer ansikter",
"refreshing_metadata": "Oppdaterer metadata",
"refreshing_metadata": "Oppdaterer matadata",
"regenerating_thumbnails": "Regenererer miniatyrbilder",
"remote": "Eksternt",
"remote_assets": "Eksterne elementer",
@@ -1912,7 +1910,7 @@
"search_by_ocr_example": "Latte",
"search_camera_lens_model": "Søk etter objektivmodell...",
"search_camera_make": "Søk etter kameramerke...",
"search_camera_model": "Søk etter kameramodell...",
"search_camera_model": "Søk etter kamera modell...",
"search_city": "Søk etter by...",
"search_country": "Søk etter land...",
"search_filter_apply": "Aktiver filter",
@@ -1953,7 +1951,7 @@
"search_rating": "Søk etter vurdering...",
"search_result_page_new_search_hint": "Nytt søk",
"search_settings": "Søke instillinger",
"search_state": "Søk etter fylke...",
"search_state": "Søk etter stat...",
"search_suggestion_list_smart_search_hint_1": "Smartsøk er aktivert som standard, for å søke etter metadata bruk syntaksen ",
"search_suggestion_list_smart_search_hint_2": "m:ditt-søkeord",
"search_tags": "Søk tags...",
@@ -2173,7 +2171,7 @@
"suggestions": "Forslag",
"sunrise_on_the_beach": "Soloppgang på stranden",
"support": "Støtte",
"support_and_feedback": "Støtte og tilbakemelding",
"support_and_feedback": "Støtte og Tilbakemelding",
"support_third_party_description": "Immich-installasjonen din ble pakket av en tredjepart. Problemer du opplever kan være forårsaket av den pakken, så vennligst ta opp problemer med dem i første omgang ved å bruke koblingene nedenfor.",
"swap_merge_direction": "Bytt retning på sammenslåingen",
"sync": "Synkroniser",
@@ -2297,7 +2295,6 @@
"upload_details": "Opplastingsdetaljer",
"upload_dialog_info": "Vil du utføre backup av valgte element(er) til serveren?",
"upload_dialog_title": "Last opp element",
"upload_error_with_count": "Opplastningsfeil for {count, plural, one {# element} other {# elementer}}",
"upload_errors": "Opplasting fullført med {count, plural, one {# error} other {# errors}}, oppdater siden for å se nye opplastingsressurser.",
"upload_finished": "Opplasting fullført",
"upload_progress": "Gjenstående {remaining, number} behandlet {processed, number}/{total, number}",
@@ -2324,7 +2321,7 @@
"user_purchase_settings": "Kjøpe",
"user_purchase_settings_description": "Administrer dine kjøp",
"user_role_set": "Sett {user} som {role}",
"user_usage_detail": "Detaljer av brukernes forbruk",
"user_usage_detail": "Detaljer av brukers forbruk",
"user_usage_stats": "Kontobruksstatistikk",
"user_usage_stats_description": "Vis kontobruksstatistikk",
"username": "Brukernavn",

View File

@@ -572,9 +572,6 @@
"asset_list_layout_sub_title": "Layout",
"asset_list_settings_subtitle": "Fotoraster layout instellingen",
"asset_list_settings_title": "Fotoraster",
"asset_not_found_on_device_android": "Item niet gevonden op apparaat",
"asset_not_found_on_device_ios": "Item niet gevonden op apparaat. Wanneer je iCloud gebruikt, kan het item niet toegankelijk zijn door een slecht bestand in iCloud",
"asset_not_found_on_icloud": "Item niet gevonden in iCloud. Het item kan ontoegankelijk zijn door een slecht bestand op iCloud",
"asset_offline": "Item offline",
"asset_offline_description": "Dit externe item is niet meer op de schijf te vinden. Neem contact op met de Immich beheerder voor hulp.",
"asset_restored_successfully": "Item succesvol hersteld",
@@ -869,7 +866,7 @@
"custom_url": "Aangepaste URL",
"cutoff_date_description": "Bewaar foto's van de laatste…",
"cutoff_day": "{count, plural, one {dag} other {dagen}}",
"cutoff_year": "{count, plural, one {jaar} other {jaar}}",
"cutoff_year": "{count, plural, one {jaar} other {jaren}}",
"daily_title_text_date": "E dd MMM",
"daily_title_text_date_year": "E dd MMM yyyy",
"dark": "Donker",
@@ -1195,6 +1192,7 @@
"features": "Functies",
"features_in_development": "Functies in ontwikkeling",
"features_setting_description": "Beheer de app functies",
"file_name": "Bestandsnaam: {file_name}",
"file_name_or_extension": "Bestandsnaam of extensie",
"file_size": "Bestandsgrootte",
"filename": "Bestandsnaam",
@@ -2297,7 +2295,6 @@
"upload_details": "Uploaddetails",
"upload_dialog_info": "Wil je een backup maken van de geselecteerde item(s) op de server?",
"upload_dialog_title": "Item uploaden",
"upload_error_with_count": "Upload fout voor {count, plural, one {# item} other {# items}}",
"upload_errors": "Upload voltooid met {count, plural, one {# fout} other {# fouten}}, vernieuw de pagina om de nieuwe items te zien.",
"upload_finished": "Uploaden is voltooid",
"upload_progress": "Resterend {remaining, number} - Verwerkt {processed, number}/{total, number}",

View File

@@ -1,6 +1,6 @@
{
"name": "immich-i18n",
"version": "2.5.4",
"version": "2.5.2",
"private": true,
"scripts": {
"format": "prettier --check .",

View File

@@ -272,7 +272,7 @@
"oauth_auto_register": "Automatyczna rejestracja",
"oauth_auto_register_description": "Automatycznie rejestruj nowych użytkowników po zalogowaniu się za pomocą protokołu OAuth",
"oauth_button_text": "Tekst na przycisku",
"oauth_client_secret_description": "Wymagane dla poufnego klienta lub jeśli PKCE (Proof Key for Code Exchange) nie jest obsługiwane dla klienta publicznego.",
"oauth_client_secret_description": "Wymagane jeżeli PKCE (Proof Key for Code Exchange) nie jest wspierane przez dostawcę OAuth",
"oauth_enable_description": "Loguj się za pomocą OAuth",
"oauth_mobile_redirect_uri": "Mobilny adres zwrotny",
"oauth_mobile_redirect_uri_override": "Zapasowy URI przekierowania mobilnego",
@@ -572,9 +572,6 @@
"asset_list_layout_sub_title": "Układ",
"asset_list_settings_subtitle": "Ustawienia układu siatki zdjęć",
"asset_list_settings_title": "Siatka Zdjęć",
"asset_not_found_on_device_android": "Nie znaleziono zasobu na urządzeniu",
"asset_not_found_on_device_ios": "Nie znaleziono zasobu na urządzeniu. Jeśli korzystasz z usługi iCloud, zasób może być niedostępny z powodu uszkodzonego pliku przechowywanego w usłudze iCloud",
"asset_not_found_on_icloud": "Nie znaleziono zasobu w usłudze iCloud. Zasób może być niedostępny z powodu uszkodzonego pliku przechowywanego w usłudze iCloud",
"asset_offline": "Zasób niedostępny",
"asset_offline_description": "Ten zewnętrzny zasób nie jest już dostępny na dysku. Aby uzyskać pomoc, skontaktuj się z administratorem Immich.",
"asset_restored_successfully": "Zasób został pomyślnie przywrócony",
@@ -867,7 +864,7 @@
"custom_locale": "Niestandardowy Region",
"custom_locale_description": "Formatuj daty i liczby na podstawie języka i regionu",
"custom_url": "Niestandardowy URL",
"cutoff_date_description": "Zachowaj zdjęcia z ostatnich…",
"cutoff_date_description": "Przechowuj zdjęcia z ostatnich…",
"cutoff_day": "{count, plural, one {dzień} other {dni}}",
"cutoff_year": "{count, plural, one {rok} few {lata} other {lat}}",
"daily_title_text_date": "E, dd MMM",
@@ -1195,6 +1192,7 @@
"features": "Funkcje",
"features_in_development": "Funkcje w fazie rozwoju",
"features_setting_description": "Zarządzaj funkcjami aplikacji",
"file_name": "Nazwa pliku: {file_name}",
"file_name_or_extension": "Nazwie lub rozszerzeniu pliku",
"file_size": "Rozmiar pliku",
"filename": "Nazwa pliku",
@@ -1332,14 +1330,14 @@
"json_error": "Błąd JSON",
"keep": "Zachowaj",
"keep_albums": "Zachowaj albumy",
"keep_albums_count": "Zachowuję {count} {count, plural, one {album} few {albumy} other {albumów}}",
"keep_albums_count": "Przechowano {count} {count, plural, one {album} few {albumy} other {albumów}}",
"keep_all": "Zachowaj wszystko",
"keep_description": "Wybierz, co zachować na Twoim urządzeniu przy zwalnianiu miejsca.",
"keep_favorites": "Zachowaj ulubione",
"keep_on_device": "Zachowaj na urządzeniu",
"keep_on_device_hint": "Wybierz elementy, które chcesz zachować na tym urządzeniu",
"keep_on_device_hint": "Wybierz , co zachować na tym urządzeniu",
"keep_this_delete_others": "Zachowaj to, usuń pozostałe",
"keeping": "Zachowuję:{items}",
"keeping": "Przechowano:{items}",
"kept_this_deleted_others": "Zachowano ten zasób i usunięto {count, plural, one {#zasób} other {#zasoby}}",
"keyboard_shortcuts": "Skróty klawiaturowe",
"language": "Język",
@@ -1599,7 +1597,7 @@
"no_results_description": "Spróbuj użyć synonimu lub bardziej ogólnego słowa kluczowego",
"no_shared_albums_message": "Stwórz album aby udostępnić zdjęcia i filmy osobom w Twojej sieci",
"no_uploads_in_progress": "Brak przesyłań w toku",
"none": "Żadne",
"none": "Pusto",
"not_allowed": "Niedozwolone",
"not_available": "Nie dotyczy",
"not_in_any_album": "Bez albumu",
@@ -2095,7 +2093,7 @@
"sharing": "Udostępnianie",
"sharing_enter_password": "Wprowadź hasło, aby wyświetlić tę stronę.",
"sharing_page_album": "Udostępnione albumy",
"sharing_page_description": "Twórz współdzielone albumy, aby udostępniać zdjęcia i filmy osobom w twojej sieci.",
"sharing_page_description": "Twórz wspóldzielone albumy, aby udostępniać zdjęcia i filmy osobom w sieci.",
"sharing_page_empty_list": "PUSTA LISTA",
"sharing_sidebar_description": "Wyświetl link do udostępniania na pasku bocznym",
"sharing_silver_appbar_create_shared_album": "Utwórz współdzielony album",
@@ -2297,7 +2295,6 @@
"upload_details": "Szczegóły przesyłania",
"upload_dialog_info": "Czy chcesz wykonać kopię zapasową wybranych zasobów na serwerze?",
"upload_dialog_title": "Prześlij Zasób",
"upload_error_with_count": "Błąd przesyłania dla {count, plural, one {# zasobu} other {# zasobów}}",
"upload_errors": "Przesyłanie zakończone z {count, plural, one {# błędem} other {# błędami}}. Odśwież stronę, aby zobaczyć nowo przesłane zasoby.",
"upload_finished": "Przesyłanie zakończone",
"upload_progress": "Pozostałe {remaining, number} - Przetworzone {processed, number}/{total, number}",

View File

@@ -104,8 +104,6 @@
"image_preview_description": "Imagem de tamanho médio sem metadados, utilizada ao visualizar um único ficheiro e pela aprendizagem de máquina",
"image_preview_quality_description": "Qualidade de pré-visualização de 1 a 100. Maior é melhor, mas produz ficheiros maiores e pode reduzir a capacidade de resposta da aplicação. Definir um valor demasiado baixo pode afetar a qualidade da aprendizagem de máquina.",
"image_preview_title": "Definições de Pré-visualização",
"image_progressive": "Progressivo",
"image_progressive_description": "Codificar imagens JPEG de forma progressiva para exibição com carregamento gradual. Não tem efeito em imagens WebP.",
"image_quality": "Qualidade",
"image_resolution": "Resolução",
"image_resolution_description": "Resoluções mais altas podem ajudar a preservar mais detalhes mas demoram mais a codificar, têm tamanhos de ficheiro maiores e podem reduzir a capacidade de resposta da aplicação.",
@@ -190,21 +188,10 @@
"machine_learning_smart_search_enabled": "Ativar a Pesquisa Inteligente",
"machine_learning_smart_search_enabled_description": "Se desativado, as imagens não serão codificadas para Pesquisa Inteligente.",
"machine_learning_url_description": "A URL do servidor de aprendizagem de máquina. Se for fornecido mais do que um URL, cada servidor será testado, um a um, até um deles responder com sucesso, por ordem do primeiro ao último. Servidores que não responderem serão temporariamente ignorados até voltarem a estar online.",
"maintenance_delete_backup": "Eliminar Cópia de Segurança",
"maintenance_delete_backup_description": "Este ficheiro irá ser apagado para sempre.",
"maintenance_delete_error": "Ocorreu um erro ao eliminar a cópia de segurança.",
"maintenance_restore_backup": "Restaurar Cópia de Segurança",
"maintenance_restore_backup_description": "O Immich irá ser apagado e de seguida restaurado a partir da cópia de segurança selecionada. Irá ser criada uma cópia de segurança antes de continuar.",
"maintenance_restore_backup_different_version": "Esta cópia de segurança foi criada com uma versão diferente do Immich!",
"maintenance_restore_backup_unknown_version": "Não foi possível determinar a versão da cópia de segurança.",
"maintenance_restore_database_backup": "Restaurar cópia de seguraça da base de dados",
"maintenance_restore_database_backup_description": "Reverter para um estado anterior da base de dados utilizando um ficheiro de cópia de segurança",
"maintenance_settings": "Manutenção",
"maintenance_settings_description": "Colocar o Immich no modo de manutenção.",
"maintenance_start": "Aternar para o modo de manutenção",
"maintenance_start": "Iniciar modo de manutenção",
"maintenance_start_error": "Ocorreu um erro ao iniciar o modo de manutenção.",
"maintenance_upload_backup": "Carregar ficheiro de cópia de segurança da base de dados",
"maintenance_upload_backup_error": "Não foi possível carregar cópia de segurança. É um ficheiro .sql/.sql.gz?",
"manage_concurrency": "Gerir simultaneidade",
"manage_concurrency_description": "Navegar para a página das tarefas para gerir as tarefas em simultâneo",
"manage_log_settings": "Gerir definições de registo",
@@ -272,7 +259,7 @@
"oauth_auto_register": "Registo automático",
"oauth_auto_register_description": "Registar automaticamente novos utilizadores após iniciarem sessão com o OAuth",
"oauth_button_text": "Texto do botão",
"oauth_client_secret_description": "Obrigatório para o cliente confidencial, ou se a PKCE (Proof Key for Code Exchange) não for suportada para cliente público.",
"oauth_client_secret_description": "Obrigatório se PKCE (Proof Key for Code Exchange) não for suportado pelo provedor OAuth",
"oauth_enable_description": "Iniciar sessão com o OAuth",
"oauth_mobile_redirect_uri": "URI de redirecionamento móvel",
"oauth_mobile_redirect_uri_override": "Substituição de URI de redirecionamento móvel",
@@ -451,9 +438,6 @@
"admin_password": "Palavra-passe do administrador",
"administration": "Administração",
"advanced": "Avançado",
"advanced_settings_clear_image_cache": "Limpar a Cache de Imagens",
"advanced_settings_clear_image_cache_error": "Ocorreu um erro ao limpar a cache de imagens",
"advanced_settings_clear_image_cache_success": "Limpeza concluída com sucesso {size}",
"advanced_settings_enable_alternate_media_filter_subtitle": "Utilize esta definição para filtrar ficheiros durante a sincronização baseada em critérios alternativos. Utilize apenas se a aplicação estiver com problemas a detetar todos os álbuns.",
"advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Utilizar um filtro alternativo de sincronização de álbuns em dispositivos",
"advanced_settings_log_level_title": "Nível de registo: {level}",
@@ -517,7 +501,6 @@
"all": "Todos",
"all_albums": "Todos os álbuns",
"all_people": "Todas as pessoas",
"all_photos": "Todas as fotos",
"all_videos": "Todos os vídeos",
"allow_dark_mode": "Permitir modo escuro",
"allow_edits": "Permitir edições",
@@ -525,9 +508,6 @@
"allow_public_user_to_upload": "Permitir que utilizadores públicos façam carregamentos",
"allowed": "Permitido",
"alt_text_qr_code": "Imagem do código QR",
"always_keep": "Manter sempre",
"always_keep_photos_hint": "Libertar Espaço irá manter todas as fotos neste dispositivo.",
"always_keep_videos_hint": "Libertar Espaço irá manter todos os vídeos neste dispositivo.",
"anti_clockwise": "Sentido anti-horário",
"api_key": "Chave de API",
"api_key_description": "Este valor será apresentado apenas uma única vez. Por favor, certifique-se que o copiou antes de fechar a janela.",
@@ -572,9 +552,6 @@
"asset_list_layout_sub_title": "Disposição",
"asset_list_settings_subtitle": "Configurações de disposição da grade de fotos",
"asset_list_settings_title": "Grade de fotos",
"asset_not_found_on_device_android": "Ficheiro não encontrado no dispositivo",
"asset_not_found_on_device_ios": "Ficheiro não encontrado no dispositivo. Se estiver a utilizar o iCloud, o ficheiro pode estar inacessível devido a um ficheiro corrompido armazenado no iCloud",
"asset_not_found_on_icloud": "Ficheiro não encontrado no iCloud. Este pode estar inacessível devido a um ficheiro corrompido armazenado no iCloud",
"asset_offline": "Ficheiro Indisponível",
"asset_offline_description": "Este ficheiro externo deixou de estar disponível no disco. Contacte o seu administrador do Immich para obter ajuda.",
"asset_restored_successfully": "FIcheiro restaurado com sucesso",
@@ -626,7 +603,7 @@
"backup_album_selection_page_select_albums": "Selecione Álbuns",
"backup_album_selection_page_selection_info": "Informações da Seleção",
"backup_album_selection_page_total_assets": "Total de ficheiros únicos",
"backup_albums_sync": "Cópia de Segurança de Sincronização de Álbuns",
"backup_albums_sync": "Cópia de segurança de sincronização de álbuns",
"backup_all": "Tudo",
"backup_background_service_backup_failed_message": "Ocorreu um erro ao efetuar cópia de segurança dos ficheiros. A tentar de novo…",
"backup_background_service_complete_notification": "Cópia de conteúdos concluída",
@@ -764,12 +741,11 @@
"cleanup_deleted_assets": "{count} ficheiro(s) foram movidos para a reciclagem do dispositivo",
"cleanup_deleting": "A mover para a reciclagem...",
"cleanup_found_assets": "Foram encontrados {count} ficheiro(s) com cópias de segurança",
"cleanup_found_assets_with_size": "Foram encontrados {count} ficheiros com cópia de segurança ({size})",
"cleanup_icloud_shared_albums_excluded": "Álbuns Partilhados do iCloud serão excluídos da pesquisa",
"cleanup_no_assets_found": "Nenhum ficheiro encontrado que siga os critérios acima. Libertar Espaço apenas pode remover ficheiros que tenham sido copiados para o servidor",
"cleanup_no_assets_found": "Nenhum ficheiro de cópia de segurança encontrado que siga os seus critérios",
"cleanup_preview_title": "Ficheiros a serem removidos ({count})",
"cleanup_step3_description": "Procurar por ficheiros no servidor que sigam os seus critérios de data e se serão mantidos.",
"cleanup_step4_summary": "{count} ficheiros (criados antes de {date}) para remover do seu dispositivo local. As fotos irão manter-se acessíveis através da aplicação do Immich.",
"cleanup_step3_description": "Procurar por fotos e vídeos que tenham sido copiados para o servidor com a data limite e as opções de filtro selecionadas",
"cleanup_step4_summary": "{count} ficheiros criados antes de {date} estão em espera para serem removidos do seu dispositivo",
"cleanup_trash_hint": "Para recuperar por completo o espaço de armazenamento, abra a aplicação da galeria do sistema e esvazie a reciclagem",
"clear": "Limpar",
"clear_all": "Limpar tudo",
@@ -867,7 +843,7 @@
"custom_locale": "Localização Personalizada",
"custom_locale_description": "Formatar datas e números baseados na língua e na região",
"custom_url": "URL personalizado",
"cutoff_date_description": "Manter fotos dos últimos…",
"cutoff_date_description": "Remover fotos e vídeos anteriores a",
"cutoff_day": "{count, plural, one {dia} other {dias}}",
"cutoff_year": "{count, plural, one {ano} other {anos}}",
"daily_title_text_date": "E, dd MMM",
@@ -1019,14 +995,11 @@
"error_change_sort_album": "Ocorreu um erro ao mudar a ordem de exibição",
"error_delete_face": "Falha ao remover rosto do ficheiro",
"error_getting_places": "Erro ao obter locais",
"error_loading_albums": "Ocorreu um erro ao carregar os álbuns",
"error_loading_image": "Erro ao carregar a imagem",
"error_loading_partners": "Erro ao carregar parceiros: {error}",
"error_retrieving_asset_information": "Ocorreu um erro ao carregar as informações do ficheiro",
"error_saving_image": "Erro: {error}",
"error_tag_face_bounding_box": "Erro ao marcar o rosto - não foi possível localizar o rosto",
"error_title": "Erro - Algo correu mal",
"error_while_navigating": "Ocorreu um erro ao navegar para o ficheiro",
"errors": {
"cannot_navigate_next_asset": "Não foi possível navegar para o próximo ficheiro",
"cannot_navigate_previous_asset": "Não foi possível navegar para o ficheiro anterior",
@@ -1195,6 +1168,7 @@
"features": "Funcionalidades",
"features_in_development": "Funcionalidades em Desenvolvimento",
"features_setting_description": "Configurar as funcionalidades da aplicação",
"file_name": "Nome do ficheiro: {file_name}",
"file_name_or_extension": "Nome do ficheiro ou extensão",
"file_size": "Tamanho do ficheiro",
"filename": "Nome do ficheiro",
@@ -1214,7 +1188,7 @@
"forgot_pin_code_question": "Esqueceu-se do seu PIN?",
"forward": "Para a frente",
"free_up_space": "Libertar Espaço",
"free_up_space_description": "Mover fotos e vídeos que tenham sido copiados para o servidor para a reciclagem do seu dispositivo para libertar espaço. As cópias no servidor mantêm-se seguras.",
"free_up_space_description": "Mover fotos e vídeos que tenham sido copiados para o servidor para a reciclagem do seu dispositivo para libertar espaço. As cópias no servidor mantêm-se seguras",
"free_up_space_settings_subtitle": "Libertar espaço no dispositivo",
"full_path": "Caminho completo: {path}",
"gcast_enabled": "Google Cast",
@@ -1331,15 +1305,9 @@
"json_editor": "Editor JSON",
"json_error": "Erro JSON",
"keep": "Manter",
"keep_albums": "Manter álbuns",
"keep_albums_count": "A manter {count} {count, plural, one {álbum} other {álbuns}}",
"keep_all": "Manter Todos",
"keep_description": "Escolha o que fica no seu dispositivo quando liberta espaço.",
"keep_favorites": "Manter favoritos",
"keep_on_device": "Manter no dispositivo",
"keep_on_device_hint": "Selecionar itens para manter neste dispositivo",
"keep_this_delete_others": "Manter este ficheiro, eliminar os outros",
"keeping": "A manter: {items}",
"kept_this_deleted_others": "Foi mantido ficheiro e {count, plural, one {eliminado # outro} other {eliminados # outros}}",
"keyboard_shortcuts": "Atalhos do teclado",
"language": "Idioma",
@@ -1433,28 +1401,10 @@
"loop_videos_description": "Ativar para repetir os vídeos automaticamente durante a exibição.",
"main_branch_warning": "Está a usar uma versão de desenvolvimento; recomendamos vivamente que use uma versão de lançamento!",
"main_menu": "Menu Principal",
"maintenance_action_restore": "A Restaurar Base de Dados",
"maintenance_description": "O Immich foi colocado em <link>modo de manutenção</link>.",
"maintenance_end": "Desativar modo de manutenção",
"maintenance_end_error": "Ocorreu um erro ao desativar o modo de manutenção.",
"maintenance_logged_in_as": "Sessão iniciada como {user}",
"maintenance_restore_from_backup": "Restaurar a partir de uma cópia de segurança",
"maintenance_restore_library": "Restaurar a Sua Biblioteca",
"maintenance_restore_library_confirm": "Se isto parecer correto, continue para restaurar uma cópia de segurança!",
"maintenance_restore_library_description": "A Restaurar Base de Dados",
"maintenance_restore_library_folder_has_files": "{folder} tem {count} pasta(s)",
"maintenance_restore_library_folder_no_files": "{folder} tem ficheiros em falta!",
"maintenance_restore_library_folder_pass": "leitura e escrita possível",
"maintenance_restore_library_folder_read_fail": "leitura impossível",
"maintenance_restore_library_folder_write_fail": "escrita impossível",
"maintenance_restore_library_hint_missing_files": "Pode ter ficheiros importantes em falta",
"maintenance_restore_library_hint_regenerate_later": "Pode regenerá-las mais tarde nas definições",
"maintenance_restore_library_hint_storage_template_missing_files": "Está a utilizar um modelo de armazenamento? Pode ter ficheiros em falta",
"maintenance_restore_library_loading": "A carregar verificações de integradade e heurísticas…",
"maintenance_task_backup": "A criar uma cópia de segurança da base de dados existente…",
"maintenance_task_migrations": "A migrar base de dados…",
"maintenance_task_restore": "A restaurar a cópia de segurança selecionada…",
"maintenance_task_rollback": "Não foi possível restaurar, a reverter para o ponto de restauro…",
"maintenance_title": "Temporariamente Indisponível",
"make": "Marca",
"manage_geolocation": "Gerir localização",
@@ -1569,12 +1519,11 @@
"next_memory": "Próxima memória",
"no": "Não",
"no_actions_added": "Ainda não foram adicionadas ações",
"no_albums_found": "Nenhum álbum encontrado",
"no_albums_message": "Crie um álbum para organizar as suas fotos e vídeos",
"no_albums_with_name_yet": "Parece que ainda não tem nenhum álbum com este nome.",
"no_albums_yet": "Parece que ainda não tem nenhum álbum.",
"no_archived_assets_message": "Arquive fotos e vídeos para os ocultar da sua visualização de fotos",
"no_assets_message": "Clique para carregar a sua primeira foto",
"no_assets_message": "FAÇA CLIQUE PARA CARREGAR A SUA PRIMEIRA FOTO",
"no_assets_to_show": "Não há ficheiros para exibir",
"no_cast_devices_found": "Nenhum dispositivo de transmissão encontrado",
"no_checksum_local": "Sem cálculo de verificação disponível - não pode capturar conteúdos locais",
@@ -1599,7 +1548,6 @@
"no_results_description": "Tente um sinónimo ou uma palavra-chave mais comum",
"no_shared_albums_message": "Crie um álbum para partilhar fotos e vídeos com pessoas na sua rede",
"no_uploads_in_progress": "Nenhum carregamento em curso",
"none": "Nenhum",
"not_allowed": "Não permitido",
"not_available": "N/A",
"not_in_any_album": "Não está em nenhum álbum",
@@ -1929,7 +1877,6 @@
"search_filter_media_type_title": "Selecione o tipo do ficheiro",
"search_filter_ocr": "Pesquisar por OCR",
"search_filter_people_title": "Selecionar pessoas",
"search_filter_star_rating": "Classificação",
"search_for": "Pesquisar por",
"search_for_existing_person": "Pesquisar por pessoas existentes",
"search_no_more_result": "Sem mais resultados",
@@ -2134,8 +2081,6 @@
"skip_to_folders": "Saltar para pastas",
"skip_to_tags": "Saltar para as etiquetas",
"slideshow": "Apresentação",
"slideshow_repeat": "Repetir apresentação de diapositivos",
"slideshow_repeat_description": "Repetir do inicio quando a apresentação acabar",
"slideshow_settings": "Definições de apresentação",
"sort_albums_by": "Ordenar álbuns por...",
"sort_created": "Data de criação",
@@ -2212,7 +2157,6 @@
"theme_setting_theme_subtitle": "Escolha a configuração do tema da aplicação",
"theme_setting_three_stage_loading_subtitle": "O carregamento em três estágios pode aumentar o desempenho do carregamento, mas causa uma carga de rede significativamente maior",
"theme_setting_three_stage_loading_title": "Habilitar carregamento em três estágios",
"then": "Depois",
"they_will_be_merged_together": "Eles serão unidos",
"third_party_resources": "Recursos de terceiros",
"time": "Hora",
@@ -2268,7 +2212,6 @@
"unhide_person": "Exibir pessoa",
"unknown": "Desconhecido",
"unknown_country": "País desconhecido",
"unknown_date": "Data desconhecida",
"unknown_year": "Ano desconhecido",
"unlimited": "Ilimitado",
"unlink_motion_video": "Remover relação com video animado",
@@ -2297,7 +2240,6 @@
"upload_details": "Detalhes do Carregamento",
"upload_dialog_info": "Deseja realizar uma cópia de segurança dos ficheiros selecionados para o servidor?",
"upload_dialog_title": "Enviar ficheiro",
"upload_error_with_count": "Erro ao carregar {count, plural, one {# ficheiro} other {# ficheiros}}",
"upload_errors": "Envio completo com {count, plural, one {# erro} other {# erros}}, atualize a página para ver os novos ficheiros enviados.",
"upload_finished": "Carregamento acabado",
"upload_progress": "Restante(s) {remaining, number} - Processado(s) {processed, number}/{total, number}",
@@ -2312,7 +2254,7 @@
"url": "URL",
"usage": "Utilização",
"use_biometric": "Utilizar dados biométricos",
"use_current_connection": "Utilizar a ligação atual",
"use_current_connection": "usar conexão atual",
"use_custom_date_range": "Utilizar um intervalo de datas personalizado",
"user": "Utilizador",
"user_has_been_deleted": "Este utilizador for eliminado.",

View File

@@ -77,7 +77,7 @@
"confirm_user_pin_code_reset": "Tem certeza de que deseja redefinir o código PIN do usuário {user}?",
"copy_config_to_clipboard_description": "Copiar as configurações do sistema como um objeto JSON para a área de transferência",
"create_job": "Criar tarefa",
"cron_expression": "Expressão cron",
"cron_expression": "Expressão CRON",
"cron_expression_description": "Defina o intervalo de análise no formato Cron. Para mais informações, por favor veja o <link>Crontab Guru</link>",
"cron_expression_presets": "Sugestões de expressão Cron",
"disable_login": "Desabilitar login",
@@ -572,9 +572,6 @@
"asset_list_layout_sub_title": "Layout",
"asset_list_settings_subtitle": "Configurações de layout da grade de fotos",
"asset_list_settings_title": "Grade de Fotos",
"asset_not_found_on_device_android": "Arquivo não encontrado no dispositivo",
"asset_not_found_on_device_ios": "Arquivo não encontrado no dispositivo. Se estiver usando o iCloud, o arquivo pode estar inacessível devido a um arquivo corrompido armazenado no iCloud",
"asset_not_found_on_icloud": "Arquivo não encontrado no iCloud. o arquivo pode estar inacessível devido a um arquivo corrompido armazenado no iCloud",
"asset_offline": "Arquivo indisponível",
"asset_offline_description": "Este arquivo externo não está mais disponível. Contate seu administrador do Immich para obter ajuda.",
"asset_restored_successfully": "Arquivo restaurado",
@@ -764,12 +761,11 @@
"cleanup_deleted_assets": "{count} mídias movidas para a lixeira do dispositivo",
"cleanup_deleting": "Movendo para a lixeira...",
"cleanup_found_assets": "Encontrados {count} arquivos com backup",
"cleanup_found_assets_with_size": "Foram encontrados {count} arquivos com backup ({size})",
"cleanup_icloud_shared_albums_excluded": "Álbuns compartilhados do iCloud não serão incluídos",
"cleanup_no_assets_found": "Não foram encontrados arquivos que correspondam aos seus critérios. Liberar Espaço só pode remover arquivos que foram copiados para o servidor",
"cleanup_no_assets_found": "Não foram encontrados arquivos que correspondam aos seus critérios",
"cleanup_preview_title": "Remover {count} arquivos",
"cleanup_step3_description": "Procure por arquivos de backup que correspondam à sua data e manter configurações.",
"cleanup_step4_summary": "{count} arquivos criados antes de {date} foram selecionados para liberar espaço do seu dispositivo. Fotos permanecerão acessíveis através do app do Immich.",
"cleanup_step3_description": "Procurar por fotos e vídeos que já tem o backup feito no servidor até a data de corte e mais outros filtros selecionados",
"cleanup_step4_summary": "{count} arquivos criados antes de {date} foram selecionados para liberar espaço do seu dispositivo",
"cleanup_trash_hint": "Para liberar espaço imediatamente, abra a galeria de fotos original do dispositivo e esvazie a lixeira",
"clear": "Limpar",
"clear_all": "Limpar tudo",
@@ -867,7 +863,7 @@
"custom_locale": "Localização Customizada",
"custom_locale_description": "Formatar datas e números baseado no idioma e na região",
"custom_url": "URL personalizada",
"cutoff_date_description": "Manter fotos dos últimos…",
"cutoff_date_description": "Remover fotos mais antigas que",
"cutoff_day": "{count, plural, one {dia} other {dias}}",
"cutoff_year": "{count, plural, one {ano} other {anos}}",
"daily_title_text_date": "E, dd MMM",
@@ -1019,14 +1015,11 @@
"error_change_sort_album": "Falha ao alterar a ordem de exibição",
"error_delete_face": "Erro ao remover face do arquivo",
"error_getting_places": "Erro ao buscar os locais",
"error_loading_albums": "Erro ao carregar álbuns",
"error_loading_image": "Erro ao carregar a página",
"error_loading_partners": "Erro ao carregar parceiros: {error}",
"error_retrieving_asset_information": "Erro ao recuperar informações do arquivo",
"error_saving_image": "Erro: {error}",
"error_tag_face_bounding_box": "Erro ao marcar o rosto - não foi possível localizar o rosto",
"error_title": "Erro - Algo deu errado",
"error_while_navigating": "Erro ao navegar para o arquivo",
"errors": {
"cannot_navigate_next_asset": "Não foi possível navegar para o próximo arquivo",
"cannot_navigate_previous_asset": "Não foi possível navegar para o arquivo anterior",
@@ -1195,6 +1188,7 @@
"features": "Funcionalidades",
"features_in_development": "Funções em desenvolvimento",
"features_setting_description": "Gerenciar as funcionalidades da aplicação",
"file_name": "Arquivo: {file_name}",
"file_name_or_extension": "Nome do arquivo ou extensão",
"file_size": "Tamanho do arquivo",
"filename": "Nome do arquivo",
@@ -1214,7 +1208,7 @@
"forgot_pin_code_question": "Esqueceu seu PIN?",
"forward": "Para frente",
"free_up_space": "Liberar espaço",
"free_up_space_description": "Mova as fotos e vídeos de backup para a lixeira do seu dispositivo para liberar espaço. Suas cópias no servidor permanecem seguras.",
"free_up_space_description": "Libere espaço ao mover as fotos e vídeos já com backup no servidor para a lixeira do seu dispositivo. As cópias no servidor ainda existirão e estão a salvo",
"free_up_space_settings_subtitle": "Liberar espaço no dispositivo",
"full_path": "Caminho completo: {path}",
"gcast_enabled": "Google Cast",
@@ -1331,15 +1325,9 @@
"json_editor": "Editor JSON",
"json_error": "Erro no JSON",
"keep": "Manter",
"keep_albums": "Manter álbuns",
"keep_albums_count": "Mantendo {count} {count, plural, one {álbum} other {álbuns}}",
"keep_all": "Manter Todos",
"keep_description": "Escolha o que fica no seu dispositivo ao liberar espaço.",
"keep_favorites": "Manter favoritos",
"keep_on_device": "Manter no dispositivo",
"keep_on_device_hint": "Selecione os itens que deseja manter neste dispositivo",
"keep_this_delete_others": "Manter este, excluir o resto",
"keeping": "Mantendo: {items}",
"kept_this_deleted_others": "Este foi mantido e {count, plural, one {# arquivo foi excluído} other {# arquivos foram excluídos}}",
"keyboard_shortcuts": "Atalhos do teclado",
"language": "Idioma",
@@ -1387,7 +1375,7 @@
"local_network_sheet_info": "O aplicativo irá se conectar ao servidor através deste endereço quando estiver na rede Wi-Fi especificada",
"location": "Localização",
"location_permission": "Permissão de localização",
"location_permission_content": "Para usar o recurso de alternância automática, o Immich requer permissão de localização precisa para poder ler o nome da rede Wi-Fi atual",
"location_permission_content": "Para utilizar a função de troca automática de URL é necessário a permissão de localização precisa, para que seja possível ler o nome da rede Wi-Fi",
"location_picker_choose_on_map": "Escolha no mapa",
"location_picker_latitude_error": "Digite uma latitude válida",
"location_picker_latitude_hint": "Digite a latitude",
@@ -1433,28 +1421,10 @@
"loop_videos_description": "Ative para repetir os vídeos automaticamente durante a exibição.",
"main_branch_warning": "Você está utilizando uma versão de desenvolvimento. É fortemente recomendado que utilize uma versão estável!",
"main_menu": "Menu Principal",
"maintenance_action_restore": "Restaurando Banco de Dados",
"maintenance_description": "O Immich foi colocado em <link>modo de manutenção</link>.",
"maintenance_end": "Desativar modo de manutenção",
"maintenance_end_error": "Ocorreu um erro ao desativar o modo de manutenção.",
"maintenance_logged_in_as": "Usuário atual: {user}",
"maintenance_restore_from_backup": "Restaurar a partir de Backup",
"maintenance_restore_library": "Restaurar Sua Biblioteca",
"maintenance_restore_library_confirm": "Se tudo parecer correto, prossiga com a restauração do backup!",
"maintenance_restore_library_description": "Restaurando o Banco de Dados",
"maintenance_restore_library_folder_has_files": "{folder} possui {count} pasta(s)",
"maintenance_restore_library_folder_no_files": "{folder} está faltando arquivos!",
"maintenance_restore_library_folder_pass": "legível e escrevível",
"maintenance_restore_library_folder_read_fail": "ilegível",
"maintenance_restore_library_folder_write_fail": "não gravável",
"maintenance_restore_library_hint_missing_files": "Talvez estejam faltando arquivos importantes",
"maintenance_restore_library_hint_regenerate_later": "Você pode regenerá-los depois nas configurações",
"maintenance_restore_library_hint_storage_template_missing_files": "Está usando um modelo de armazenamento? Podem estar faltando arquivos",
"maintenance_restore_library_loading": "Carregando verificações de integridade e heurísticas…",
"maintenance_task_backup": "Criando um backup do banco de dados existente…",
"maintenance_task_migrations": "Executando migrações do banco de dados…",
"maintenance_task_restore": "Restaurando o backup escolhido…",
"maintenance_task_rollback": "Falha na restauração, voltando para o ponto de restauração…",
"maintenance_title": "Temporariamente Indisponível",
"make": "Marca",
"manage_geolocation": "Gerenciar localização",
@@ -1569,12 +1539,11 @@
"next_memory": "Próxima memória",
"no": "Não",
"no_actions_added": "Nenhuma ação foi adicionada ainda",
"no_albums_found": "Nenhum álbum encontrado",
"no_albums_message": "Crie um álbum para organizar suas fotos e vídeos",
"no_albums_with_name_yet": "Parece que você ainda não tem nenhum álbum com esse nome.",
"no_albums_yet": "Parece que você ainda não tem nenhum álbum.",
"no_archived_assets_message": "Arquive fotos e vídeos para os ocultar da sua visualização de fotos",
"no_assets_message": "Clique aqui para enviar sua primeira foto",
"no_assets_message": "CLIQUE PARA ENVIAR SUA PRIMEIRA FOTO",
"no_assets_to_show": "Não há arquivos para exibir",
"no_cast_devices_found": "Nenhum dispositivo encontrado",
"no_checksum_local": "Nenhum checksum disponível - não foi possível carregar os arquivos locais",
@@ -1599,7 +1568,6 @@
"no_results_description": "Tente um sinônimo ou uma palavra-chave mais geral",
"no_shared_albums_message": "Crie um álbum para compartilhar fotos e vídeos com pessoas em sua rede",
"no_uploads_in_progress": "Nenhum envio em progresso",
"none": "Nenhum",
"not_allowed": "Não permitido",
"not_available": "N/A",
"not_in_any_album": "Fora de álbum",
@@ -1929,7 +1897,6 @@
"search_filter_media_type_title": "Selecione o tipo de mídia",
"search_filter_ocr": "Buscar por OCR",
"search_filter_people_title": "Selecione pessoas",
"search_filter_star_rating": "Avaliação",
"search_for": "Pesquisar por",
"search_for_existing_person": "Pesquisar por pessoas",
"search_no_more_result": "Não há mais resultados",
@@ -1990,7 +1957,7 @@
"selected_gps_coordinates": "Coordenadas de GPS Selecionada",
"send_message": "Enviar mensagem",
"send_welcome_email": "Enviar E-mail de boas vindas",
"server_endpoint": "URL do Servidor",
"server_endpoint": "URL do servidor",
"server_info_box_app_version": "Versão do aplicativo",
"server_info_box_server_url": "Endereço",
"server_offline": "Servidor Indisponível",
@@ -2134,8 +2101,6 @@
"skip_to_folders": "Ir para pastas",
"skip_to_tags": "Ir para os marcadores",
"slideshow": "Apresentação",
"slideshow_repeat": "Repetir apresentação de slides",
"slideshow_repeat_description": "Voltar para o início quando a apresentação terminar",
"slideshow_settings": "Opções de apresentação",
"sort_albums_by": "Ordenar álbuns por...",
"sort_created": "Data de criação",
@@ -2212,7 +2177,6 @@
"theme_setting_theme_subtitle": "Escolha a configuração de tema do app",
"theme_setting_three_stage_loading_subtitle": "O carregamento em três estágios oferece a imagem de melhor qualidade em troca de uma velocidade de carregamento mais lenta",
"theme_setting_three_stage_loading_title": "Ative o carregamento em três estágios",
"then": "Antes",
"they_will_be_merged_together": "Eles serão mesclados",
"third_party_resources": "Recursos de terceiros",
"time": "Hora",
@@ -2268,7 +2232,6 @@
"unhide_person": "Exibir pessoa",
"unknown": "Desconhecido",
"unknown_country": "País desconhecido",
"unknown_date": "Data desconhecida",
"unknown_year": "Ano desconhecido",
"unlimited": "Ilimitado",
"unlink_motion_video": "Remover relação com video animado",
@@ -2297,7 +2260,6 @@
"upload_details": "Detalhes do envio",
"upload_dialog_info": "Deseja fazer o backup dos arquivos selecionados no servidor?",
"upload_dialog_title": "Enviar arquivo",
"upload_error_with_count": "Erro de envio para {count, plural, one {# arquivo} other {# arquivos}}",
"upload_errors": "Envio concluído com {count, plural, one {# erro} other {# erros}}, atualize a página para ver os novos arquivos.",
"upload_finished": "Envio finalizado",
"upload_progress": "{remaining, number} restantes - {processed, number}/{total, number} já processados",

View File

@@ -104,8 +104,6 @@
"image_preview_description": "Imagine de dimensiune medie cu metadate eliminate, utilizată la vizualizarea unui singur element și pentru învățarea automată",
"image_preview_quality_description": "Calitatea previzualizării de la 1 la 100. O valoare mai mare oferă o calitate mai bună, dar produce fișiere mai mari și poate reduce receptivitatea aplicației. Setarea unei valori scăzute poate afecta calitatea învățării automate.",
"image_preview_title": "Previzualizați setările",
"image_progressive": "Progresiv",
"image_progressive_description": "Encodează imaginile JPEG progresiv, pentru încărcare graduală.Fără efect pentru imaginile WebP",
"image_quality": "Calitate",
"image_resolution": "Rezolutie",
"image_resolution_description": "Rezoluțiile mai mari pot păstra mai multe detalii, dar necesită mai mult timp pentru a fi codificate, au dimensiuni mai mari ale fișierelor și pot reduce răspunsul aplicației.",
@@ -194,17 +192,12 @@
"maintenance_delete_backup_description": "Acest fisier va fi sters permanent.",
"maintenance_delete_error": "Stergerea backup-ului nu a reusit.",
"maintenance_restore_backup": "Restaureaza Backup",
"maintenance_restore_backup_description": "Immich va fi șters si restaurat din backup-ul ales. Va fi creat un nou backup înainte de a continua.",
"maintenance_restore_backup_different_version": "Acest backup a fost creat folosind o versiune diferita de Immich!",
"maintenance_restore_backup_unknown_version": "Versiunea de backup nu a putut fi determinată.",
"maintenance_restore_database_backup": "Restaurează baza de date din backup",
"maintenance_restore_database_backup_description": "Restaureaza la o bază de date precedentă folosind un fisier backup",
"maintenance_settings": "Întreținere",
"maintenance_settings_description": "Puneți Immich în modul de întreținere.",
"maintenance_start": "Schimbă la modul de întreținere",
"maintenance_start": "Pornește modul de întreținere",
"maintenance_start_error": "Nu s-a putut porni modul de întreținere.",
"maintenance_upload_backup": "Încarcă fișier backup pentru baza de date",
"maintenance_upload_backup_error": "Nu s-a putut încărca backupul, e un fișier .sql/.sql.gz?",
"manage_concurrency": "Gestionează sarcinile paralele",
"manage_concurrency_description": "Accesează pagina de joburi pentru a gestiona concurența lor",
"manage_log_settings": "Administrați setările jurnalului",
@@ -451,9 +444,6 @@
"admin_password": "Parolă administrator",
"administration": "Administrare",
"advanced": "Avansat",
"advanced_settings_clear_image_cache": "Șterge cache-ul",
"advanced_settings_clear_image_cache_error": "Ștergerea cache-ului de imagini a eșuat",
"advanced_settings_clear_image_cache_success": "{size} șterși cu succes",
"advanced_settings_enable_alternate_media_filter_subtitle": "Utilizați această opțiune pentru a filtra conținutul media în timpul sincronizării pe baza unor criterii alternative. Încercați numai dacă întâmpinați probleme cu aplicația la detectarea tuturor albumelor.",
"advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Utilizați filtrul alternativ de sincronizare a albumelor de pe dispozitiv",
"advanced_settings_log_level_title": "Nivel log: {level}",
@@ -517,7 +507,6 @@
"all": "Toate",
"all_albums": "Toate albumele",
"all_people": "Toți oamenii",
"all_photos": "Toate fotografiile",
"all_videos": "Toate videoclipurile",
"allow_dark_mode": "Permite mod întunecat",
"allow_edits": "Permite editări",
@@ -525,9 +514,6 @@
"allow_public_user_to_upload": "Permite utilizatorului public să încarce",
"allowed": "Permis",
"alt_text_qr_code": "Cod QR",
"always_keep": "Păstrează întotdeauna",
"always_keep_photos_hint": "Eliberează Spațiu va păstra toate fotografiile de pe acest dispozitiv.",
"always_keep_videos_hint": "Eliberează Spațiu va păstra toate video-urile de pe acest dispozitiv.",
"anti_clockwise": "În sens invers acelor de ceasornic",
"api_key": "Cheie API",
"api_key_description": "Această valoare va fi afișată o singură dată. Vă rugăm să vă asigurați că o copiați înainte de a închide fereastra.",
@@ -572,9 +558,6 @@
"asset_list_layout_sub_title": "Aspect",
"asset_list_settings_subtitle": "Setări format grilă fotografii",
"asset_list_settings_title": "Grilă fotografii",
"asset_not_found_on_device_android": "Obiect negăsit pe dispozitiv",
"asset_not_found_on_device_ios": "Obiect negăsit pe dispozitiv.Dacă folosești iCloud, obiectul poate fi inaccesibil din cauza stocării incorecte pe iCloud",
"asset_not_found_on_icloud": "Obiect negăsit pe iCloud. Obiectul poate fi inaccesibil din cauza stocării incorecte pe iCloud",
"asset_offline": "Resursă Offline",
"asset_offline_description": "Această resursă externă nu mai este găsită pe disc. Contactează te rog administratorul tău Immich pentru ajutor.",
"asset_restored_successfully": "Date restaurate cu succes",
@@ -764,7 +747,6 @@
"cleanup_deleted_assets": "Muta {count} materiale in coșul de gunoi",
"cleanup_deleting": "Se șterge...",
"cleanup_found_assets": "Am găsit {count} materiale in copia de rezerva",
"cleanup_found_assets_with_size": "{count} obiecte găsite ({size})",
"cleanup_icloud_shared_albums_excluded": "Albumele partajate iCLoud sunt excluse de la cautare",
"cleanup_no_assets_found": "Nici un material in copia de rezerva găsit după criteriu",
"cleanup_preview_title": "Materiale sa fie șterse ({count})",
@@ -869,7 +851,6 @@
"custom_url": "URL personalizat",
"cutoff_date_description": "Eliminați fotografiile și videoclipurile mai vechi de",
"cutoff_day": "{count, plural, o {day} mai multe {days}}",
"cutoff_year": "{count, plural, =0 {0 ani} one {# an} few {# ani} other {# de ani}}",
"daily_title_text_date": "E, LLL zz",
"daily_title_text_date_year": "E, LLL zz, aaaa",
"dark": "Întunecat",
@@ -1019,14 +1000,11 @@
"error_change_sort_album": "Nu s-a putut modifica ordinea de sortare a albumului",
"error_delete_face": "Eroare la ștergerea feței din activ",
"error_getting_places": "Eroare la obținerea locațiilor",
"error_loading_albums": "Eroare la încărcarea albumelor",
"error_loading_image": "Eroare la încărcarea imaginii",
"error_loading_partners": "Eroare la încărcarea partenerilor: {error}",
"error_retrieving_asset_information": "Eroare la colectarea informațiilor obiectului",
"error_saving_image": "Eroare: {error}",
"error_tag_face_bounding_box": "Eroare la etichetarea feței - nu se pot obține coordonatele casetei de delimitare",
"error_title": "Eroare - ceva nu a mers",
"error_while_navigating": "Eroare la navigarea spre obiect",
"errors": {
"cannot_navigate_next_asset": "Nu se poate naviga către următoarea resursă",
"cannot_navigate_previous_asset": "Nu se poate naviga la resursa anterioară",
@@ -1195,6 +1173,7 @@
"features": "Caracteristici",
"features_in_development": "Funcții în dezvoltare",
"features_setting_description": "Gestionați funcțiile aplicației",
"file_name": "Nume de fișier: {file_name}",
"file_name_or_extension": "Numele sau extensia fișierului",
"file_size": "Mărime fișier",
"filename": "Numele fișierului",
@@ -1331,15 +1310,9 @@
"json_editor": "Editor JSON",
"json_error": "Eroare JSON",
"keep": "Păstrați",
"keep_albums": "Păstreaza albume",
"keep_albums_count": "Păstrez {count} {count, plural, one {album} few {albume} other {de albume}}",
"keep_all": "Păstrați Tot",
"keep_description": "Alege ce să rămână pe dispozitiv când eliberezi spațiu.",
"keep_favorites": "Păstrați favoritele",
"keep_on_device": "Păstrează pe dispozitiv",
"keep_on_device_hint": "Selectează ce să rămână pe dispozitiv",
"keep_this_delete_others": "Păstrați asta, ștergeți celelalte",
"keeping": "Păstrez: {items}",
"kept_this_deleted_others": "S-a păstrat acest material și s-au șters {count, plural, one {# material} other {# materiale}}",
"keyboard_shortcuts": "Comenzi rapide de tastatură",
"language": "Limbă",
@@ -1433,28 +1406,10 @@
"loop_videos_description": "Activați pentru a rula in buclă automat un videoclip în vizualizatorul de detalii.",
"main_branch_warning": "Utilizați o versiune de dezvoltare; vă recomandăm insistent să utilizați o versiune de lansare!",
"main_menu": "Meniu principal",
"maintenance_action_restore": "Restaurare bază de date",
"maintenance_description": "Immich a fost pus în <link>modul de întreținere</link>.",
"maintenance_end": "Ieșire din modul de întreținere",
"maintenance_end_error": "Nu s-a reușit ieșirea din modul de întreținere.",
"maintenance_logged_in_as": "Conectat în prezent ca {user}",
"maintenance_restore_from_backup": "Restaurează din backup",
"maintenance_restore_library": "Restaurează-ți biblioteca",
"maintenance_restore_library_confirm": "Dacă pare corect, continuă spre a restaura un backup!",
"maintenance_restore_library_description": "Restaurare bază de date",
"maintenance_restore_library_folder_has_files": "{folder} are {count} {count, plural, one {fișier} few {fișiere} other {de fișiere}}",
"maintenance_restore_library_folder_no_files": "Lipsesc fișiere din {folder}!",
"maintenance_restore_library_folder_pass": "permite scrierea și citirea",
"maintenance_restore_library_folder_read_fail": "nu permite citirea",
"maintenance_restore_library_folder_write_fail": "nu permite scrierea",
"maintenance_restore_library_hint_missing_files": "Posibil să lipsească fișiere importante",
"maintenance_restore_library_hint_regenerate_later": "Poți regenera mai tarziu în setări",
"maintenance_restore_library_hint_storage_template_missing_files": "Folosesti șablonul de stocare? Posibil să-ți lipsească fișiere",
"maintenance_restore_library_loading": "Încarc verificările de integritate si euristice…",
"maintenance_task_backup": "Creez backupul bazei de date existente…",
"maintenance_task_migrations": "Rulez migrările bazei de date…",
"maintenance_task_restore": "Restaurez backupul ales…",
"maintenance_task_rollback": "Restaurarea a eșuat, întorc la punctul de restaurare…",
"maintenance_title": "Temporar indisponibil",
"make": "Marcă",
"manage_geolocation": "Gestionați locația",
@@ -1569,7 +1524,6 @@
"next_memory": "Următoarea amintire",
"no": "Nu",
"no_actions_added": "Nu s-au adăugat încă acțiuni",
"no_albums_found": "Niciun album găsit",
"no_albums_message": "Creați un album pentru a vă organiza fotografiile și videoclipurile",
"no_albums_with_name_yet": "Se pare că nu aveți încă niciun album cu acest nume.",
"no_albums_yet": "Se pare că nu aveți încă niciun album.",
@@ -1599,7 +1553,6 @@
"no_results_description": "Încercați un sinonim sau un cuvânt cheie mai general",
"no_shared_albums_message": "Creați un album pentru a partaja fotografii și videoclipuri cu persoanele din rețeaua dvs",
"no_uploads_in_progress": "Nicio încărcare în curs",
"none": "Niciunul",
"not_allowed": "Nu este permis",
"not_available": "N/A",
"not_in_any_album": "Nu există în niciun album",
@@ -1682,7 +1635,6 @@
"people": "Persoane",
"people_edits_count": "Editat {count, plural, one {# persoană} other {# persoane}}",
"people_feature_description": "Răsfoiți fotografii și videoclipuri grupate după persoane",
"people_selected": "{count, plural,one {# persoană selectată} few {# persoane selectate}other {# de persoane selectate}}",
"people_sidebar_description": "Afișează un link către persoane în bara laterală",
"permanent_deletion_warning": "Avertisment de ștergere permanentă",
"permanent_deletion_warning_setting_description": "Afișează un avertisment la ștergerea definitivă a resurselor",
@@ -1790,7 +1742,6 @@
"purchase_settings_server_activated": "Cheia de produs a serverului este gestionată de administrator",
"query_asset_id": "Interoghează ID-ul resursei",
"queue_status": "Se pun în coadă {count}/{total}",
"rate_asset": "Dă o notă",
"rating": "Evaluare cu stele",
"rating_clear": "Anuleaza evaluarea",
"rating_count": "{count, plural, one {# stea} other {# stele}}",
@@ -1929,7 +1880,6 @@
"search_filter_media_type_title": "Selectați tipul media",
"search_filter_ocr": "Caută dupa OCR",
"search_filter_people_title": "Selectați persoane",
"search_filter_star_rating": "După rating în stele",
"search_for": "Căutare după",
"search_for_existing_person": "Caută o persoană existentă",
"search_no_more_result": "Nu mai există rezultate",
@@ -1964,23 +1914,17 @@
"second": "Secundǎ",
"see_all_people": "Vizualizează toate persoanele",
"select": "Selectează",
"select_album": "Selectează album",
"select_album_cover": "Selectați coperta albumului",
"select_albums": "Selectează albume",
"select_all": "Selectați tot",
"select_all_duplicates": "Selectați toate duplicatele",
"select_all_in": "Selectați tot în {group}",
"select_avatar_color": "Selectați culoarea avatarului",
"select_count": "{count, plural, one {Selectează #} few {Selectează #} other {Selectează #}}",
"select_cutoff_date": "Selectează data limită",
"select_face": "Selectați fața",
"select_featured_photo": "Selectați fotografia recomandată",
"select_from_computer": "Selectați din calculator",
"select_keep_all": "Selectați tot pentru păstrare",
"select_library_owner": "Selectați proprietarul bibliotecii",
"select_new_face": "Selectați o nouǎ fațǎ",
"select_people": "Selectează oameni",
"select_person": "Selectează persoana",
"select_person_to_tag": "Selectați o persoană pentru a o eticheta",
"select_photos": "Selectați fotografii",
"select_trash_all": "Selectați tot pentru ștergere",
@@ -2116,7 +2060,6 @@
"show_password": "Afișați parola",
"show_person_options": "Afișați opțiunile persoanelor",
"show_progress_bar": "Afișați Bara de Progres",
"show_schema": "Arată schema",
"show_search_options": "Afișați opțiunile de căutare",
"show_shared_links": "Afișare linkuri partajate",
"show_slideshow_transition": "Afișați tranziția de prezentare",
@@ -2134,8 +2077,6 @@
"skip_to_folders": "Treceți la foldere",
"skip_to_tags": "Treceți la etichete",
"slideshow": "Prezentare de diapozitive",
"slideshow_repeat": "Repetă prezentarea",
"slideshow_repeat_description": "Reîntoarce-te la început cand prezentarea se încheie",
"slideshow_settings": "Setări pentru prezentarea de diapozitive",
"sort_albums_by": "Sortați albumele după...",
"sort_created": "Data creării",
@@ -2212,7 +2153,6 @@
"theme_setting_theme_subtitle": "Alege tema aplicației",
"theme_setting_three_stage_loading_subtitle": "Încărcarea în trei etape are putea crește performanța încărcării dar generează un volum semnificativ mai mare de trafic pe rețea",
"theme_setting_three_stage_loading_title": "Pornește încărcarea în 3 etape",
"then": "Atunci",
"they_will_be_merged_together": "Vor fi îmbinate împreună",
"third_party_resources": "Resurse Terță Parte",
"time": "Timp",
@@ -2247,13 +2187,6 @@
"trash_page_select_assets_btn": "Selectează resurse",
"trash_page_title": "Coș ({count})",
"trashed_items_will_be_permanently_deleted_after": "Elementele din coșul de gunoi vor fi șterse definitiv după {days, plural, one {# zi} other {# zile}}.",
"trigger": "Declanșator",
"trigger_asset_uploaded": "Fișier încărcat",
"trigger_asset_uploaded_description": "Declanșează cand un fișier este încarcat",
"trigger_description": "Un eveniment care declanșează fluxul de lucru",
"trigger_person_recognized": "Persoana Recunoscută",
"trigger_person_recognized_description": "Declanșat atunci când este detectată o persoană",
"trigger_type": "Tip de declanșare",
"troubleshoot": "Depanați",
"type": "Tip",
"unable_to_change_pin_code": "Nu se poate schimba codul PIN",
@@ -2268,7 +2201,6 @@
"unhide_person": "Dezvăluie persoana",
"unknown": "Necunoscut",
"unknown_country": "Țară necunoscută",
"unknown_date": "Dată necunoscută",
"unknown_year": "An Necunoscut",
"unlimited": "Nelimitat",
"unlink_motion_video": "Deconectați videoclipul în mișcare",
@@ -2285,9 +2217,7 @@
"unstack": "Dezasamblați",
"unstack_action_prompt": "{count} neîmpachetate",
"unstacked_assets_count": "Nestivuit {count, plural, one {# resursă} other {# resurse}}",
"unsupported_field_type": "Tip de câmp neacceptat",
"untagged": "Neetichetat",
"untitled_workflow": "Flux fara titlu",
"up_next": "Mai departe",
"update_location_action_prompt": "Actualizează locația pentru {count} resurse selectate cu:",
"updated_at": "Actualizat",
@@ -2297,7 +2227,6 @@
"upload_details": "Detalii încărcare",
"upload_dialog_info": "Vrei să backup resursele selectate pe server?",
"upload_dialog_title": "Încarcă resursă",
"upload_error_with_count": "Eroare la încărcare pentru {count, plural, one {# fișier} other {# fișiere}}",
"upload_errors": "Încărcare finalizată cu {count, plural, one {# eroare} other {# erori}}, reîmprospătați pagina pentru a reîncărca noile resurse.",
"upload_finished": "Încărcarea s-a finalizat",
"upload_progress": "Rămas {remaining, number} - Procesat {processed, number}/{total, number}",
@@ -2333,7 +2262,6 @@
"utilities": "Utilitǎți",
"validate": "Validați",
"validate_endpoint_error": "Vă rugăm să introduceți o adresă URL validă",
"validation_error": "Eroare de validare",
"variables": "Variabile",
"version": "Versiune",
"version_announcement_closing": "Prietenul tǎu, Alex",
@@ -2345,12 +2273,10 @@
"video_hover_setting_description": "Redați miniatura video când mouse-ul trece peste element. Chiar și atunci când este dezactivată, redarea poate fi pornită trecând cu mouse-ul peste pictograma de redare.",
"videos": "Videoclipuri",
"videos_count": "{count, plural, one {# Videoclip} other {# Videoclipuri}}",
"videos_only": "Doar videoclipuri",
"view": "Secțiune",
"view_album": "Vizualizează Album",
"view_all": "Vizualizează Tot",
"view_all_users": "Vizulizați toți utilizatorii",
"view_asset_owners": "Vezi proprietarii resursei",
"view_details": "Vedeți detaliile",
"view_in_timeline": "Vizualizează în cronologie",
"view_link": "Vezi link",
@@ -2366,36 +2292,18 @@
"viewer_stack_use_as_main_asset": "Folosește ca resursă principală",
"viewer_unstack": "Anulează grup",
"visibility_changed": "Vizibilitatea schimbată pentru {count, plural, one {# persoană} other {# persoane}}",
"visual": "Vizual",
"visual_builder": "Constructor vizual",
"waiting": "În așteptare",
"waiting_count": "În așteptare: {count}",
"warning": "Avertisment",
"week": "Sǎptǎmânǎ",
"welcome": "Bun venit",
"welcome_to_immich": "Bun venit la Immich",
"width": "Lățime",
"wifi_name": "Nume Wi-Fi",
"workflow_delete_prompt": "Ești sigur că vrei să ștergi acest flux de lucru?",
"workflow_deleted": "Flux de lucru șters",
"workflow_description": "Descrierea fluxului de lucru",
"workflow_info": "Informații despre fluxul de lucru",
"workflow_json": "Flux de lucru JSON",
"workflow_json_help": "Editează configurația fluxului de lucru în format JSON. Modificările vor fi sincronizate cu constructorul vizual.",
"workflow_name": "Numele fluxului de lucru",
"workflow_navigation_prompt": "Ești sigur că vrei să părăsești fără să salvezi modificările?",
"workflow_summary": "Rezumatul fluxului de lucru",
"workflow_update_success": "Fluxul de lucru a fost actualizat cu succes",
"workflow_updated": "Fluxul de lucru a fost actualizat",
"workflows": "Fluxuri de lucru",
"workflows_help_text": "Fluxurile de lucru automatizează acțiuni pe resurse, folosind declanșatori și filtre",
"wrong_pin_code": "Cod PIN greșit",
"year": "An",
"years_ago": "acum {years, plural, one {# an} other {# ani}} în urmă",
"yes": "Da",
"you_dont_have_any_shared_links": "Nu aveți linkuri partajate",
"your_wifi_name": "Numele rețelei tale WiFi",
"zero_to_clear_rating": "apasă 0 pentru a reseta evaluarea resursei",
"zoom_image": "Măriți Imaginea",
"zoom_to_bounds": "Mărește la margini"
}

View File

@@ -1195,6 +1195,7 @@
"features": "Дополнительные возможности",
"features_in_development": "Функции в разработке",
"features_setting_description": "Управление дополнительными возможностями приложения",
"file_name": "Имя файла: {file_name}",
"file_name_or_extension": "Имя файла или расширение",
"file_size": "Размер файла",
"filename": "Имя файла",

View File

@@ -572,9 +572,6 @@
"asset_list_layout_sub_title": "Rozvrhnutie",
"asset_list_settings_subtitle": "Nastavenia rozloženia mriežky fotografií",
"asset_list_settings_title": "Mriežka fotografií",
"asset_not_found_on_device_android": "Položka nebola nájdená v zariadení",
"asset_not_found_on_device_ios": "Položka nebola nájdená v zariadení. Ak používate iCloud, položka môže byť nedostupná kvôli poškodenému súboru uloženému v iCloude",
"asset_not_found_on_icloud": "Položka nebola nájdená v iCloude. Položka môže byť nedostupná kvôli poškodenému súboru uloženému v iCloude",
"asset_offline": "Médium je offline",
"asset_offline_description": "Tento externá položka sa už nenachádza na disku. Pre pomoc sa prosím obráťte na správcu systému Immich.",
"asset_restored_successfully": "Položky boli úspešne obnovené",
@@ -782,8 +779,6 @@
"client_cert_import": "Importovať",
"client_cert_import_success_msg": "Certifikát klienta je naimportovaný",
"client_cert_invalid_msg": "Neplatný súbor certifikátu alebo nesprávne heslo",
"client_cert_password_message": "Zadajte heslo pre tento certifikát",
"client_cert_password_title": "Heslo certifikátu",
"client_cert_remove_msg": "Certifikát klienta je odstránený",
"client_cert_subtitle": "Podporuje iba formát PKCS12 (.p12, .pfx). Importovanie/odstránenie certifikátu je k dispozícii len pred prihlásením",
"client_cert_title": "SSL certifikát klienta [EXPERIMENTÁLNE]",
@@ -869,7 +864,7 @@
"custom_locale": "Vlastné nastavenie jazyka",
"custom_locale_description": "Formátovanie dátumov a čísel podľa jazyka a regiónu",
"custom_url": "Vlastná URL adresa",
"cutoff_date_description": "Ponechať fotografie z posledného obdobia…",
"cutoff_date_description": "Ponechať fotografie z posledného…",
"cutoff_day": "{count, plural, one {deň} few {dni} other {dní}}",
"cutoff_year": "{count, plural, one {rok} few {roky} other {rokov}}",
"daily_title_text_date": "EEEE, d. MMMM",
@@ -1197,9 +1192,8 @@
"features": "Funkcie",
"features_in_development": "Funkcie vo vývoji",
"features_setting_description": "Spravovať funkcie aplikácie",
"file_name": "Názov súboru: {file_name}",
"file_name_or_extension": "Názov alebo prípona súboru",
"file_name_text": "Názov súboru",
"file_name_with_value": "Názov súboru: {file_name}",
"file_size": "Veľkosť súboru",
"filename": "Názov súboru",
"filetype": "Typ súboru",
@@ -1976,7 +1970,7 @@
"select_all_in": "Označiť všetky v {group}",
"select_avatar_color": "Vyberte farbu avatara",
"select_count": "{count, plural, one {Vybrať #} other {Vybrať #}}",
"select_cutoff_date": "Vybrať cieľový dátum",
"select_cutoff_date": "Vybrať dátum konca",
"select_face": "Vyberte tvár",
"select_featured_photo": "Vyberte náhľadovú fotku",
"select_from_computer": "Vybrať z počítača",
@@ -2301,7 +2295,6 @@
"upload_details": "Podrobnosti o nahrávaní",
"upload_dialog_info": "Chcete zálohovať zvolené médiá na server?",
"upload_dialog_title": "Nahrať médiá",
"upload_error_with_count": "Chyba pri nahrávaní {count, plural, one {# položky} few {# položiek} other {# položiek}}",
"upload_errors": "Nahrávanie ukončené s {count, plural, one {# chybou} other {# chybami}}, obnovte stránku, aby sa zobrazili nové položky.",
"upload_finished": "Nahrávanie dokončené",
"upload_progress": "Ostáva {remaining, number} - Spracovaných {processed, number}/{total, number}",

View File

@@ -572,9 +572,6 @@
"asset_list_layout_sub_title": "Postavitev",
"asset_list_settings_subtitle": "Nastavitve postavitve mreže fotografij",
"asset_list_settings_title": "Mreža fotografij",
"asset_not_found_on_device_android": "Sredstva ni bilo mogoče najti v napravi",
"asset_not_found_on_device_ios": "Sredstva ni bilo mogoče najti v napravi. Če uporabljate iCloud, sredstvo morda ni dostopno zaradi napačne datoteke, shranjene v iCloudu",
"asset_not_found_on_icloud": "Sredstva ni bilo mogoče najti v iCloudu. Sredstvo morda ni dostopno zaradi napačne datoteke, shranjene v iCloudu",
"asset_offline": "Sredstvo brez povezave",
"asset_offline_description": "Tega zunanjega sredstva ni več mogoče najti na disku. Za pomoč kontaktirajte Immich skrbnika.",
"asset_restored_successfully": "Sredstvo uspešno obnovljeno",
@@ -1195,6 +1192,7 @@
"features": "Funkcije",
"features_in_development": "Funkcije v razvoju",
"features_setting_description": "Upravljaj funkcije aplikacije",
"file_name": "Ime datoteke: {file_name}",
"file_name_or_extension": "Ime ali končnica datoteke",
"file_size": "Velikost datoteke",
"filename": "Ime datoteke",
@@ -1230,7 +1228,7 @@
"go_to_search": "Pojdi na iskanje",
"gps": "GPS",
"gps_missing": "Brez GPS-a",
"grant_permission": "Dodaj dovoljenje",
"grant_permission": "Podeli dovoljenje",
"group_albums_by": "Združi albume po ...",
"group_country": "Združi po državah",
"group_no": "Brez združevanja",
@@ -1608,7 +1606,7 @@
"notes": "Opombe",
"nothing_here_yet": "Tukaj še ni ničesar",
"notification_permission_dialog_content": "Če želite omogočiti obvestila, pojdite v Nastavitve in izberite Dovoli.",
"notification_permission_list_tile_content": "Dodaj dovoljenje za pošiljanje obvestil.",
"notification_permission_list_tile_content": "Izdaj dovoljenje za omogočanje obvestil.",
"notification_permission_list_tile_enable_button": "Omogoči obvestila",
"notification_permission_list_tile_title": "Dovoljenje za obvestila",
"notification_toggle_setting_description": "Omogoči e-poštna obvestila",
@@ -1697,8 +1695,8 @@
"permission_onboarding_continue_anyway": "Vseeno nadaljuj",
"permission_onboarding_get_started": "Začnimo",
"permission_onboarding_go_to_settings": "Pojdite na nastavitve",
"permission_onboarding_permission_denied": "Dovoljenje zavrnjeno. Če želite uporabljati Immich, v nastavitvah dodajte dovoljenja za fotografije in videoposnetke.",
"permission_onboarding_permission_granted": "Dovoljenje ste dodali! Vse je pripravljeno.",
"permission_onboarding_permission_denied": "Dovoljenje zavrnjeno. Če želite uporabljati Immich, v nastavitvah podelite dovoljenja za fotografije in videoposnetke.",
"permission_onboarding_permission_granted": "Dovoljenje je izdano! Vse je pripravljeno.",
"permission_onboarding_permission_limited": "Dovoljenje je omejeno. Če želite Immichu dovoliti varnostno kopiranje in upravljanje vaše celotne zbirke galerij, v nastavitvah podelite dovoljenja za fotografije in videoposnetke.",
"permission_onboarding_request": "Immich potrebuje dovoljenje za ogled vaših fotografij in videoposnetkov.",
"person": "Oseba",
@@ -2297,7 +2295,6 @@
"upload_details": "Podrobnosti o nalaganju",
"upload_dialog_info": "Ali želite varnostno kopirati izbrana sredstva na strežnik?",
"upload_dialog_title": "Naloži sredstvo",
"upload_error_with_count": "Napaka pri prilaganju za {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}",
"upload_errors": "Nalaganje je končano s/z {count, plural, one {# napako} two {# napakama} other {# napakami}}, osvežite stran, da vidite nova sredstva za nalaganje.",
"upload_finished": "Nalaganje končano",
"upload_progress": "Preostalo {remaining, number} - Obdelano {processed, number}/{total, number}",

View File

@@ -987,6 +987,7 @@
"feature_photo_updated": "Главна фотографија је ажурирана",
"features": "Функције (феатурес)",
"features_setting_description": "Управљајте функцијама апликације",
"file_name": "Назив документа",
"file_name_or_extension": "Име датотеке или екстензија",
"filename": "Име датотеке",
"filetype": "Врста документа",

View File

@@ -971,6 +971,7 @@
"feature_photo_updated": "Glavna fotografija je ažurirana",
"features": "Funkcije (features)",
"features_setting_description": "Upravljajte funkcijama aplikacije",
"file_name": "Naziv dokumenta",
"file_name_or_extension": "Ime datoteke ili ekstenzija",
"filename": "Ime datoteke",
"filetype": "Vrsta dokumenta",

View File

@@ -572,9 +572,6 @@
"asset_list_layout_sub_title": "Layout",
"asset_list_settings_subtitle": "Layoutinställningar för bildrutnät",
"asset_list_settings_title": "Bildrutnät",
"asset_not_found_on_device_android": "Tillgångar hittades inte på enheten",
"asset_not_found_on_device_ios": "Tillgångar hittades inte på enheten. Om du använder iCloud kan tillgången vara oåtkomlig på grund av en felaktig fil som lagrats på iCloud",
"asset_not_found_on_icloud": "Tillgångar hittades inte på iCloud. Tillgången kan vara oåtkomlig på grund av en felaktig fil som lagras på iCloud",
"asset_offline": "Tillgång offline",
"asset_offline_description": "Denna externa tillgång finns inte längre på disken. Kontakta din Immich-administratör för hjälp.",
"asset_restored_successfully": "Objekt återställt",
@@ -766,7 +763,7 @@
"cleanup_found_assets": "Hittade {count} säkerhetskopierade material",
"cleanup_found_assets_with_size": "Hittade {count} säkerhetskopierade tillgångar ({size})",
"cleanup_icloud_shared_albums_excluded": "iCloud delade album exkluderas från skanningen",
"cleanup_no_assets_found": "Inga tillgångar hittades som matchar kriterierna ovan. Frigör utrymme kan bara ta bort tillgångar som har säkerhetskopierats till servern",
"cleanup_no_assets_found": "Inga tillgångar hittades som matchar kriterierna ovan. Frigör utrymme kan bara ta bort tillgångar som har säkerhetskopierats till servern.",
"cleanup_preview_title": "Material att ta bort {count}",
"cleanup_step3_description": "Skanna efter säkerhetskopierade tillgångar som matchar ditt datum och behåll inställningarna.",
"cleanup_step4_summary": "{count} tillgångar (skapade före {date}) att tas bort från din lokala enhet. Foton kommer att förbli tillgängliga från Immich-appen.",
@@ -1195,6 +1192,7 @@
"features": "Funktioner",
"features_in_development": "Funktioner i utveckling",
"features_setting_description": "Hantera appens funktioner",
"file_name": "Filnamn: {file_name}",
"file_name_or_extension": "Filnamn eller -tillägg",
"file_size": "Filstorlek",
"filename": "Filnamn",
@@ -2297,7 +2295,6 @@
"upload_details": "Uppladdningsdetaljer",
"upload_dialog_info": "Vill du säkerhetskopiera de valda objekten till servern?",
"upload_dialog_title": "Ladda Upp Objekt",
"upload_error_with_count": "Uppladdningsfel för {count, plural, one {# asset} other {# assets}}",
"upload_errors": "Uppladdning klar med {count, plural, one {# fel} other {# fel}}, ladda om sidan för att se nya objekt.",
"upload_finished": "Uppladdningen är klar",
"upload_progress": "Återstående {remaining, number} - Bearbetade {processed, number}/{total, number}",

View File

@@ -77,7 +77,7 @@
"copy_config_to_clipboard_description": "தற்போதைய கணினி உள்ளமைவை JSON பொருளாக கிளிப்போர்டுக்கு நகலெடுக்கவும்",
"create_job": "வேலையை உருவாக்கு",
"cron_expression": "க்ரோன் வெளிப்பாடு",
"cron_expression_description": "க்ரோன் வடிவமைப்பைப் பயன்படுத்தி ச்கேனிங் இடைவெளியை அமைக்கவும். மேலும் தகவலுக்கு எ.கா.<link> க்ரோன்டாப் குரு </link>",
"cron_expression_description": "CRON வடிவமைப்பைப் பயன்படுத்தி ச்கேனிங் இடைவெளியை அமைக்கவும். மேலும் தகவலுக்கு எ.கா.<link> க்ரோன்டாப் குரு </link>",
"cron_expression_presets": "க்ரோன் வெளிப்பாடு முன்னமைவுகள்",
"disable_login": "உள்நுழைவை முடக்கு",
"duplicate_detection_job_description": "ஒத்த படங்களைக் கண்டறிய, சொத்துக்களில் இயந்திரக் கற்றலை இயக்கவும். ஸ்மார்ட் தேடலை நம்பியுள்ளது",
@@ -1118,6 +1118,7 @@
"features": "நற்பொருத்தங்கள்",
"features_in_development": "வளர்ச்சியில் நற்பொருத்தங்கள்",
"features_setting_description": "பயன்பாட்டு அம்சங்களை நிர்வகிக்கவும்",
"file_name": "கோப்பு பெயர்",
"file_name_or_extension": "கோப்பு பெயர் அல்லது நீட்டிப்பு",
"file_size": "கோப்பு அளவு",
"filename": "கோப்புப்பெயர்",

View File

@@ -731,6 +731,7 @@
"feature_photo_updated": "ఫీచర్ ఫోటో నవీకరించబడింది",
"features": "లక్షణాలు",
"features_setting_description": "యాప్ ఫీచర్‌లను నిర్వహించండి",
"file_name": "ఫైల్ పేరు",
"file_name_or_extension": "ఫైల్ పేరు లేదా పొడిగింపు",
"filename": "ఫైలుపేరు",
"filetype": "ఫైల్ రకం",

View File

@@ -445,8 +445,6 @@
"allow_public_user_to_download": "อนุญาตให้ผู้ใช้สาธารณะดาวน์โหลดได้",
"allow_public_user_to_upload": "อนุญาตให้ผู้ใช้สาธารณะอัปโหลดได้",
"alt_text_qr_code": "รูปภาพ QR code",
"always_keep_photos_hint": "\"เพิ่มพื้นที่ว่าง\" จะเก็บรูปภาพทั้งหมดบนอุปกรณ์นี้",
"always_keep_videos_hint": "\"เพิ่มพื้นที่ว่าง\" จะเก็บวิดีโอทั้งหมดบนอุปกรณ์นี้",
"anti_clockwise": "ทวนเข็มนาฬิกา",
"api_key": "API key",
"api_key_description": "ค่านี้จะแสดงเพียงครั้งเดียว โปรดคัดลอกก่อนปิดหน้าต่าง",
@@ -1006,6 +1004,7 @@
"feature_photo_updated": "อัพเดทภาพเด่นแล้ว",
"features": "ฟีเจอร์",
"features_setting_description": "จัดการฟีเจอร์แอป",
"file_name": "ชื่อไฟล์",
"file_name_or_extension": "นามสกุลหรือชื่อไฟล์",
"filename": "ชื่อไฟล์",
"filetype": "ชนิดไฟล์",
@@ -1019,9 +1018,6 @@
"folders": "โฟล์เดอร์",
"folders_feature_description": "การเรียกดูมุมมองโฟลเดอร์สำหรับภาพถ่ายและวิดีโอในระบบไฟล์",
"forward": "ไปข้างหน้า",
"free_up_space": "เพิ่มพื้นที่ว่าง",
"free_up_space_description": "เพิ่มพื้นที่ว่างโดยการย้ายรูปภาพและวิดีโอที่สำรองข้อมูลแล้วไปยังถังขยะของอุปกรณ์ของคุณ สำเนาที่อยู่บนเซิร์ฟเวอร์ยังคงอยู่อย่างปลอดภัย",
"free_up_space_settings_subtitle": "เพิ่มพื้นที่จัดเก็บอุปกรณ์",
"gcast_enabled": "Google Cast",
"gcast_enabled_description": "ฟีเจอร์นี้ต้องโหลดทรัพยากรจาก Google เพื่อทำงาน",
"general": "ทั่วไป",
@@ -1121,7 +1117,6 @@
"jobs": "งาน",
"keep": "เก็บ",
"keep_all": "เก็บทั้งหมด",
"keep_description": "เลือกสิ่งที่จะเก็บไว้บนอุปกรณ์ของคุณขณะเพิ่มพื้นที่ว่าง",
"keep_this_delete_others": "เก็บสิ่งนี้ไว้ ลบอันอื่นออก",
"kept_this_deleted_others": "เก็บเนื้อหานี้และลบ {count, plural, one {# Asset} other {# Asset}}",
"keyboard_shortcuts": "ปุ่มพิมพ์ลัด",

View File

@@ -572,9 +572,6 @@
"asset_list_layout_sub_title": "Düzen",
"asset_list_settings_subtitle": "Fotoğraf ızgara düzeni ayarları",
"asset_list_settings_title": "Fotoğraf Izgarası",
"asset_not_found_on_device_android": "Cihazda varlık bulunamadı",
"asset_not_found_on_device_ios": "Cihazınızda varlık bulunamadı. eğer icloud kullanıyorsanız, icloud'da depolanan dosyanın hatalı olması nedeniyle varlığa erişilemeyebilir.",
"asset_not_found_on_icloud": "Varlık icloud'da bulunamadı. İcloud'da depolanan dosyanın hatalı olması nedeniyle varlığa erişilemeyebilir.",
"asset_offline": "Öğe Çevrim Dışı",
"asset_offline_description": "Bu harici öğe artık diskte bulunmuyor. Yardım için lütfen Immich yöneticinizle iletişime geçin.",
"asset_restored_successfully": "Öğe başarıyla geri yüklendi",
@@ -1195,6 +1192,7 @@
"features": "Özellikler",
"features_in_development": "Geliştirme Aşamasındaki Özellikler",
"features_setting_description": "Uygulamanın özelliklerini yönet",
"file_name": "Dosya adı: {file_name}",
"file_name_or_extension": "Dosya adı veya uzantı",
"file_size": "Dosya boyutu",
"filename": "Dosya adı",

File diff suppressed because it is too large Load Diff

View File

@@ -1140,6 +1140,7 @@
"features": "Tính năng",
"features_in_development": "Tính năng đang được phát triển",
"features_setting_description": "Quản lý các tính năng app",
"file_name": "Tên tệp: {file_name}",
"file_name_or_extension": "Tên hoặc phần mở rộng tập tin",
"file_size": "Kích cỡ tệp tin",
"filename": "Tên tệp",

View File

@@ -2,79 +2,6 @@
"about": "關於",
"account": "帳號",
"account_settings": "帳號設定",
"acknowledge": "了解",
"action": "動作",
"action_common_update": "更新",
"action_description": "針對篩選後嘅資源執行嘅",
"actions": "動作",
"active": "正在處理",
"active_count": "正在處理:{count}",
"activity": "活動",
"activity_changed": "活動已{enabled, select, true {啟動} other {停止}}",
"add": "加",
"add_a_description": "加一個描述",
"add_a_location": "加一個位置",
"add_a_name": "加一個姓名",
"add_a_title": "加一個標題",
"add_action": "加動作",
"add_action_description": "點擊以加動作",
"add_assets": "加資源",
"add_birthday": "加一個生日",
"add_endpoint": "加端點",
"add_filter": "加過濾器",
"add_filter_description": "點擊以加一個過濾條件",
"add_location": "加位置",
"add_more_users": "加更多用戶",
"add_partner": "加伙伴",
"add_path": "加路徑",
"add_photos": "加多張相片",
"add_tag": "加標籤",
"add_to": "加至…",
"add_to_album": "加至相簿",
"add_to_album_bottom_sheet_added": "已加至{album}",
"add_to_album_bottom_sheet_already_exists": "已在 {album} 中",
"add_to_album_bottom_sheet_some_local_assets": "無法加部分本機資源至相簿",
"add_to_albums": "加至相簿",
"add_to_albums_count": "加 ({count}) 個項目至相簿",
"add_to_bottom_bar": "加至",
"add_to_shared_album": "加至共享相簿",
"add_url": "加網址",
"added_to_favorites": "已加至最愛",
"added_to_favorites_count": "已加{count, number} 個項目至最愛",
"admin": {
"admin_user": "管理員用戶",
"authentication_settings": "驗證設定",
"authentication_settings_description": "管理密碼、OAuth 同其他驗證設定",
"backup_onboarding_parts_title": "一個3-2-1備份包括",
"backup_onboarding_title": "備份"
},
"main_menu": "主選單",
"maintenance_action_restore": "還原緊數據庫",
"onboarding_user_welcome_description": "我哋而家開始喇!",
"onboarding_welcome_user": "歡迎,{user}",
"online": "已上線",
"only_favorites": "只顯示最愛",
"open": "開",
"open_in_map_view": "用地圖開",
"open_in_openstreetmap": "用 OpenStreetMap 開",
"open_the_search_filters": "開搜尋過濾器",
"options": "選項",
"or": "或者",
"organize_into_albums": "執成相簿",
"setting_notifications_notify_seconds": "{count} 秒",
"warning": "警告",
"week": "星期",
"welcome": "歡迎",
"welcome_to_immich": "歡迎使用 Immich",
"width": "寬",
"wifi_name": "Wi-Fi 名",
"wrong_pin_code": "PIN 碼唔啱",
"year": "年",
"years_ago": "{years, plural, one {#年} other {#年}}前",
"yes": "是",
"you_dont_have_any_shared_links": "你無共享連結",
"your_wifi_name": "你嘅 Wi-Fi 名稱",
"zero_to_clear_rating": "按0以清除資源評級",
"zoom_image": "縮放相片",
"zoom_to_bounds": "縮放至邊界"
"week": "星期"
}

View File

@@ -451,9 +451,6 @@
"admin_password": "管理員密碼",
"administration": "管理",
"advanced": "進階",
"advanced_settings_clear_image_cache": "清除圖片快取",
"advanced_settings_clear_image_cache_error": "清除圖片快取失敗",
"advanced_settings_clear_image_cache_success": "成功清除{size}",
"advanced_settings_enable_alternate_media_filter_subtitle": "使用此選項可在同步時依其他條件篩選媒體。僅在應用程式無法偵測到所有相簿時再嘗試使用。",
"advanced_settings_enable_alternate_media_filter_title": "[實驗性] 使用替代的裝置相簿同步篩選器",
"advanced_settings_log_level_title": "日誌等級:{level}",
@@ -517,7 +514,6 @@
"all": "全部",
"all_albums": "所有相簿",
"all_people": "所有人物",
"all_photos": "所有照片",
"all_videos": "所有影片",
"allow_dark_mode": "允許深色模式",
"allow_edits": "允許編輯",
@@ -525,9 +521,6 @@
"allow_public_user_to_upload": "允許公開使用者上傳",
"allowed": "允許",
"alt_text_qr_code": "QR code 圖片",
"always_keep": "一律保留",
"always_keep_photos_hint": "所有的照片將會被保留在此裝置上。",
"always_keep_videos_hint": "所有的影片將會被保留在此裝置上。",
"anti_clockwise": "逆時針",
"api_key": "API 金鑰",
"api_key_description": "此金鑰僅顯示一次。請在關閉前複製它。",
@@ -572,9 +565,6 @@
"asset_list_layout_sub_title": "版面",
"asset_list_settings_subtitle": "相片格狀版面設定",
"asset_list_settings_title": "相片格狀檢視",
"asset_not_found_on_device_android": "無法在裝置上找到項目",
"asset_not_found_on_device_ios": "無法在裝置上找到項目。iCloud 上的項目可能因檔案損失無法查閱",
"asset_not_found_on_icloud": "項目不存在於在iCloud。項目有機會因檔案損毀而無法檢閱",
"asset_offline": "媒體離線",
"asset_offline_description": "此外部媒體已無法在磁碟中找到。請聯絡您的 Immich 管理員以取得協助。",
"asset_restored_successfully": "媒體復原成功",
@@ -759,18 +749,8 @@
"checksum": "校驗和",
"choose_matching_people_to_merge": "選擇要合併的相符人物",
"city": "城市",
"cleanup_confirm_description": "Immich 發現 {count} 個項目(在 {date} 之前創建)安全備份到服務器。是否從此設備中刪除本地副本?",
"cleanup_confirm_description": "Immich發現{count}個資產(在{date}之前創建)安全備份到服務器。是否從此設備中刪除本地副本?",
"cleanup_confirm_prompt_title": "從此裝置刪除?",
"cleanup_deleted_assets": "已將{count}項目移到裝置的垃圾桶裡",
"cleanup_deleting": "正在移動到垃圾桶...",
"cleanup_found_assets": "找到{count}件已上傳的項目",
"cleanup_found_assets_with_size": "找到{count}件,總共({size})已上傳的項目",
"cleanup_icloud_shared_albums_excluded": "iCloud共享相簿被排除於搜尋之外",
"cleanup_no_assets_found": "未找到任何符合條件的項目。釋放內存功能只能移除已備份到伺服器的項目",
"cleanup_preview_title": "{count} 項需要移除的項目",
"cleanup_step3_description": "掃描符合日期和保存設定的已備份項目。",
"cleanup_step4_summary": "從這台裝置上移除{count}件創建於{date}前的項目。照片仍然可以在Immich上查看。",
"cleanup_trash_hint": "要完全恢復內存,請清空相簿中的垃圾桶",
"clear": "清空",
"clear_all": "全部清除",
"clear_all_recent_searches": "清除所有最近的搜尋",
@@ -856,20 +836,13 @@
"created_at": "建立於",
"creating_linked_albums": "建立連結相簿 ...",
"crop": "裁剪",
"crop_aspect_ratio_fixed": "已修復",
"crop_aspect_ratio_free": "無限制",
"crop_aspect_ratio_original": "原檔",
"curated_object_page_title": "事物",
"current_device": "目前裝置",
"current_pin_code": "目前 PIN 碼",
"current_server_address": "目前的伺服器位址",
"custom_date": "另選日期",
"custom_locale": "自訂地區設定",
"custom_locale_description": "根據語言與地區格式化日期與數字",
"custom_url": "自訂 URL",
"cutoff_date_description": "保留最近多少天的照片…",
"cutoff_day": "{count, plural, one {天} other {天}}",
"cutoff_year": "{count, plural, one {年} other {年}}",
"daily_title_text_date": "E, MMM dd",
"daily_title_text_date_year": "YYYY 年 M 月 D 日 (E)",
"dark": "深色",
@@ -951,7 +924,6 @@
"download_include_embedded_motion_videos": "嵌入影片",
"download_include_embedded_motion_videos_description": "將動態相片中內嵌的影片另存為獨立檔案",
"download_notfound": "無法找到下載",
"download_original": "下載原始文件",
"download_paused": "下載已暫停",
"download_settings": "下載",
"download_settings_description": "管理與媒體下載相關的設定",
@@ -961,7 +933,6 @@
"download_waiting_to_retry": "等待重試",
"downloading": "下載中",
"downloading_asset_filename": "正在下載媒體 {filename}",
"downloading_from_icloud": "正從iCloud下載",
"downloading_media": "正在下載媒體",
"drop_files_to_upload": "將檔案拖放到任何位置以上傳",
"duplicates": "重複項目",
@@ -994,13 +965,6 @@
"editor": "編輯器",
"editor_close_without_save_prompt": "此變更將不會被儲存",
"editor_close_without_save_title": "要關閉編輯器嗎?",
"editor_confirm_reset_all_changes": "你確定要重設所有變更嗎?",
"editor_flip_horizontal": "水平翻轉",
"editor_flip_vertical": "垂直翻轉",
"editor_orientation": "方向",
"editor_reset_all_changes": "重設變更",
"editor_rotate_left": "逆時針旋轉90度",
"editor_rotate_right": "順時針旋轉90度",
"email": "電子郵件",
"email_notifications": "Email 通知",
"empty_folder": "這個資料夾是空的",
@@ -1019,14 +983,11 @@
"error_change_sort_album": "變更相簿排序失敗",
"error_delete_face": "從媒體刪除臉孔時失敗",
"error_getting_places": "取得位置時出錯",
"error_loading_albums": "無法加載相簿",
"error_loading_image": "圖片載入錯誤",
"error_loading_partners": "載入合作夥伴時出錯:{error}",
"error_retrieving_asset_information": "無法獲取項目資訊",
"error_saving_image": "錯誤:{error}",
"error_tag_face_bounding_box": "標記臉部錯誤 - 無法取得邊界框坐標",
"error_title": "錯誤 - 發生錯誤",
"error_while_navigating": "無法引導至項目",
"errors": {
"cannot_navigate_next_asset": "無法導覽至下一個媒體",
"cannot_navigate_previous_asset": "無法導覽至上一個媒體",
@@ -1150,7 +1111,6 @@
"unable_to_update_workflow": "無法更新工作流",
"unable_to_upload_file": "無法上傳檔案"
},
"errors_text": "錯誤",
"exclusion_pattern": "排除模式",
"exif": "EXIF 可交換影像檔格式",
"exif_bottom_sheet_description": "新增描述...",
@@ -1195,6 +1155,7 @@
"features": "功能",
"features_in_development": "發展中的特點",
"features_setting_description": "管理應用程式功能",
"file_name": "檔案名稱:{file_name}",
"file_name_or_extension": "檔案名稱或副檔名",
"file_size": "文件大小",
"filename": "檔案名稱",
@@ -1213,9 +1174,6 @@
"folders_feature_description": "透過資料夾檢視瀏覽檔案系統中的相片與影片",
"forgot_pin_code_question": "忘記您的 PIN 碼?",
"forward": "由新至舊",
"free_up_space": "釋放內存",
"free_up_space_description": "已備份照片和影片已經移到裝置的垃圾桶以釋放內存。伺服器上的存檔依然安全。",
"free_up_space_settings_subtitle": "釋放裝置內存",
"full_path": "完整路徑:{path}",
"gcast_enabled": "Google Cast",
"gcast_enabled_description": "此功能需要從 Google 載入外部資源才能正常運作。",
@@ -1331,14 +1289,8 @@
"json_editor": "JSON編輯器",
"json_error": "JSON錯誤",
"keep": "保留",
"keep_albums": "保留相簿",
"keep_all": "全部保留",
"keep_description": "選擇釋放空間時,保留在裝置上的相片",
"keep_favorites": "保留最愛的相片",
"keep_on_device": "保留在裝置上",
"keep_on_device_hint": "選擇保留在裝置上的相片",
"keep_this_delete_others": "保留這個,刪除其他",
"keeping": "保留:{items}",
"kept_this_deleted_others": "保留這個項目並刪除{count, plural, one {# asset} other {# assets}}",
"keyboard_shortcuts": "鍵盤快捷鍵",
"language": "語言",
@@ -1432,25 +1384,10 @@
"loop_videos_description": "啟用後,影片結束會自動重播。",
"main_branch_warning": "您現在使用的是開發版本;我們強烈您建議使用正式發行版!",
"main_menu": "主選單",
"maintenance_action_restore": "復原資料庫",
"maintenance_description": "Immich已進入<link>維護模式</link>。",
"maintenance_end": "結束維護模式",
"maintenance_end_error": "未能結束維護模式。",
"maintenance_logged_in_as": "當前以{user}身份登入",
"maintenance_restore_from_backup": "從備份復原",
"maintenance_restore_library": "復原你的相簿",
"maintenance_restore_library_confirm": "確認是否正確,將繼續從備份復原!",
"maintenance_restore_library_description": "正在復原資料庫",
"maintenance_restore_library_folder_has_files": "{folder}有{count}個資料夾",
"maintenance_restore_library_folder_no_files": "{folder}有缺失的檔案!",
"maintenance_restore_library_folder_pass": "可以讀寫",
"maintenance_restore_library_folder_read_fail": "無法讀取",
"maintenance_restore_library_folder_write_fail": "無法寫入",
"maintenance_restore_library_hint_missing_files": "可能遺失重要檔案",
"maintenance_restore_library_hint_regenerate_later": "之後可以在設定重新產生",
"maintenance_task_backup": "正在建立現有資料庫的備份…",
"maintenance_task_restore": "正在從選擇的備份復原…",
"maintenance_task_rollback": "復原失敗,恢復到之前的儲存…",
"maintenance_title": "暫時不可用",
"make": "製造商",
"manage_geolocation": "管理位置",
@@ -1512,8 +1449,6 @@
"minimize": "最小化",
"minute": "分",
"minutes": "分鐘",
"mirror_horizontal": "水平",
"mirror_vertical": "垂直",
"missing": "排入未處理",
"mobile_app": "移動應用程序",
"mobile_app_download_onboarding_note": "使用以下選項下載配套移動應用程序",
@@ -1525,7 +1460,6 @@
"move_down": "向下移動",
"move_off_locked_folder": "移出鎖定的資料夾",
"move_to": "移動到",
"move_to_device_trash": "移動到裝置的垃圾桶",
"move_to_lock_folder_action_prompt": "{count} 已新增至鎖定的資料夾中",
"move_to_locked_folder": "移至鎖定的資料夾",
"move_to_locked_folder_confirmation": "這些照片和影片將從所有相簿中移除,並僅可從鎖定的資料夾檢視",
@@ -1565,7 +1499,6 @@
"next_memory": "下一張回憶",
"no": "否",
"no_actions_added": "尚未添加任何操作",
"no_albums_found": "無相簿",
"no_albums_message": "建立相簿來整理照片和影片",
"no_albums_with_name_yet": "看來還沒有這個名字的相簿。",
"no_albums_yet": "看來您還沒有任何相簿。",
@@ -1595,7 +1528,6 @@
"no_results_description": "試試同義詞或更通用的關鍵字吧",
"no_shared_albums_message": "建立相簿分享照片和影片",
"no_uploads_in_progress": "沒有正在上傳的項目",
"none": "無",
"not_allowed": "不允許",
"not_available": "不適用",
"not_in_any_album": "不在任何相簿中",
@@ -1698,7 +1630,7 @@
"permission_onboarding_permission_limited": "如要繼續,請允許 Immich 備份和管理您的相簿收藏,在設定中授予相片和影片權限。",
"permission_onboarding_request": "Immich 需要權限才能檢視您的相片和短片。",
"person": "人物",
"person_age_months": "{months, plural, one {# 個月} other {# 個月}}",
"person_age_months": "{months, plural, one {# 個月} other {# 個月}}",
"person_age_year_months": "1 年 {months, plural, one {# 個月} other {# 個月}}",
"person_age_years": "{years, plural, other {# 歲}}",
"person_birthdate": "生於 {date}",
@@ -1710,7 +1642,6 @@
"photos_and_videos": "照片及影片",
"photos_count": "{count, plural, other {{count, number} 張照片}}",
"photos_from_previous_years": "往年的照片",
"photos_only": "只允許照片",
"pick_a_location": "選擇位置",
"pick_custom_range": "自定義範圍",
"pick_date_range": "選擇日期範圍",
@@ -1891,11 +1822,9 @@
"saved_settings": "已儲存設定",
"say_something": "說說您的想法吧",
"scaffold_body_error_occurred": "發生錯誤",
"scan": "掃描",
"scan_all_libraries": "掃描所有相簿",
"scan_library": "掃描",
"scan_settings": "掃描設定",
"scanning": "正在掃描",
"scanning_for_album": "掃描相簿中……",
"search": "搜尋",
"search_albums": "搜尋相簿",
@@ -2259,7 +2188,6 @@
"unhide_person": "取消隱藏人物",
"unknown": "未知",
"unknown_country": "未知國家",
"unknown_date": "未知的日期",
"unknown_year": "未知年份",
"unlimited": "不限制",
"unlink_motion_video": "解除連結動態影片",
@@ -2335,7 +2263,6 @@
"video_hover_setting_description": "當滑鼠停在項目上時播放影片縮圖。即使停用,將滑鼠停在播放圖示上也可以播放。",
"videos": "影片",
"videos_count": "{count, plural, other {# 部影片}}",
"videos_only": "只允許影片",
"view": "檢視",
"view_album": "檢視相簿",
"view_all": "瀏覽全部",

View File

@@ -307,7 +307,7 @@
"require_password_change_on_login": "强制用户首次登录时修改密码",
"reset_settings_to_default": "将设置重置为默认值",
"reset_settings_to_recent_saved": "将设置重置为上次保存的值",
"scanning_library": "正在扫描资库",
"scanning_library": "正在扫描资库",
"search_jobs": "搜索任务…",
"send_welcome_email": "发送欢迎邮件",
"server_external_domain_settings": "外部域名",
@@ -540,139 +540,136 @@
"app_download_links": "APP下载链接",
"app_settings": "应用设置",
"app_stores": "应用商店",
"app_update_available": "应用更新已发布",
"appears_in": "收录于",
"apply_count": "应用 ({count, number})",
"app_update_available": "应用程序更新可用",
"appears_in": "所属相册",
"apply_count": "应用 ({count, number}个资产)",
"archive": "归档",
"archive_action_prompt": "已将 {count} 项添加到归档",
"archive_or_unarchive_photo": "归档或取消归档照片",
"archive_page_no_archived_assets": "未找到已归的资源",
"archive_page_no_archived_assets": "未找到归档资产",
"archive_page_title": "归档({count}",
"archive_size": "归档大小",
"archive_size_description": "配置下载归档大小GiB",
"archive_size_description": "配置下载归档大小GiB",
"archived": "已归档",
"archived_count": "{count, plural, other {已归档 # 项}}",
"are_these_the_same_person": "是同一个人吗?",
"are_you_sure_to_do_this": "确定执行此操作?",
"array_field_not_fully_supported": "数组字段需要手动进行 JSON 编辑",
"asset_action_delete_err_read_only": "无法删除只读资源,已跳过",
"asset_action_share_err_offline": "无法获取离线资源,已跳过",
"are_these_the_same_person": "他们是同一个人吗?",
"are_you_sure_to_do_this": "确定执行此操作?",
"array_field_not_fully_supported": "数组字段需要手动编辑 JSON",
"asset_action_delete_err_read_only": "无法删除只读资产,跳过",
"asset_action_share_err_offline": "无法获取离线资产,跳过",
"asset_added_to_album": "已添加至相册",
"asset_adding_to_album": "正在添加至相册…",
"asset_created": "资源已创建",
"asset_description_updated": "资描述已更新",
"asset_filename_is_offline": "资“{filename}”已离线",
"asset_has_unassigned_faces": "资源包含未分配的人脸",
"asset_hashing": "正在计算哈希值…",
"asset_list_group_by_sub_title": "分组依据",
"asset_created": "已创建资产",
"asset_description_updated": "资描述已更新",
"asset_filename_is_offline": "资“{filename}”已离线",
"asset_has_unassigned_faces": "资产中有未分配的人脸",
"asset_hashing": "哈希校验中…",
"asset_list_group_by_sub_title": "分组方式",
"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_day": "月份 + 日期",
"asset_list_layout_settings_group_by": "资分组方式",
"asset_list_layout_settings_group_by_month_day": "月和日",
"asset_list_layout_sub_title": "布局",
"asset_list_settings_subtitle": "照片网格布局设置",
"asset_list_settings_title": "照片网格",
"asset_not_found_on_device_android": "设备上未找到该资源",
"asset_not_found_on_device_ios": "设备上未找到该资源。如果您使用了 iCloud可能是由于 iCloud 中存储了错误的文件导致资源无法访问",
"asset_not_found_on_icloud": "iCloud 中未找到该资源。可能是由于 iCloud 中存储了错误的文件导致资源无法访问",
"asset_offline": "资源离线",
"asset_offline_description": "磁盘上未找到此外部资源。请联系您的 Immich 管理员寻求帮助。",
"asset_restored_successfully": "资源恢复成功",
"asset_offline": "资产脱机",
"asset_offline_description": "磁盘上已找不到该外部资产。请联系您的 Immich 管理员寻求帮助。",
"asset_restored_successfully": "已成功恢复所有资产",
"asset_skipped": "已跳过",
"asset_skipped_in_trash": "回收站中",
"asset_trashed": "资源已移至回收站",
"asset_troubleshoot": "资源诊断",
"asset_skipped_in_trash": "回收",
"asset_trashed": "资产已被删除",
"asset_troubleshoot": "资产故障排除",
"asset_uploaded": "已上传",
"asset_uploading": "上传中…",
"asset_viewer_settings_subtitle": "管理画廊查看器设置",
"asset_viewer_settings_subtitle": "管理图库浏览器设置",
"asset_viewer_settings_title": "资源查看器",
"assets": "资",
"assets_added_count": "已添加{count, plural, one {#个资} other {#个资}}",
"assets_added_to_album_count": "已向相册添加{count, plural, one {#个资} other {#个资}}",
"assets_added_to_albums_count": "已 {albumTotal, plural, one {# 个相册} other {# 个相册}}添加 {assetTotal, plural, one {# 个资源} other {# 个资源}}",
"assets_cannot_be_added_to_album_count": "无法向相册添加{count, plural, one {个资} other {个资}}",
"assets_cannot_be_added_to_albums": "无法向任何一个相册添加 {count, plural, one {个资} other {个资}}",
"assets_count": "{count, plural, one {#个资} other {#个资}}",
"assets_deleted_permanently": "已永久删除 {count} 个资",
"assets": "资",
"assets_added_count": "已添加{count, plural, one {#个资} other {#个资}}",
"assets_added_to_album_count": "已添加{count, plural, one {#个资} other {#个资}}到相册",
"assets_added_to_albums_count": "已添加 {assetTotal, plural, one {# 个资产} other {# 个资产}}到 {albumTotal, plural, one {# 个相册} other {# 个相册}}",
"assets_cannot_be_added_to_album_count": "{count, plural, one {个资} other {个资}} 无法添加到相册中",
"assets_cannot_be_added_to_albums": "{count, plural, one {个资} other {个资}} 无法添加到相册",
"assets_count": "{count, plural, one {#个资} other {#个资}}",
"assets_deleted_permanently": "{count} 个资产已被永久删除",
"assets_deleted_permanently_from_server": "已永久移除 {count} 个资产",
"assets_downloaded_failed": "{count, plural, one {已下载#个文件 - {error} 文件下载失败} other {已下载#个文件 - {error} 个文件下载失败}}",
"assets_downloaded_successfully": "{count, plural, one {已成功下载 # 个文件} other {已成功下载 # 个文件}}",
"assets_moved_to_trash_count": "已将{count, plural, one {#个资} other {#个资}}移动到回收站",
"assets_permanently_deleted_count": "已永久删除{count, plural, one {#个资} other {#个资}}",
"assets_removed_count": "已移除{count, plural, one {#个资} other {#个资}}",
"assets_removed_permanently_from_device": "已从您的设备中永久除 {count} 个资",
"assets_restore_confirmation": "确定要恢复回收站中的所有资吗?操作无法撤!请注意,任何离线资源无法通过方式恢复。",
"assets_restored_count": "已恢复{count, plural, one {#个资} other {#个资}}",
"assets_restored_successfully": "已成功恢复{count}个资",
"assets_trashed": "{count} 个资源移至回收站",
"assets_trashed_count": "已将{count, plural, one {#个资} other {#个资}}移至回收站",
"assets_trashed_from_server": "Immich 服务器上已移除 {count} 个资",
"assets_were_part_of_album_count": "{count, plural, one {个资} other {个资}}已在相册中",
"assets_were_part_of_albums_count": "{count, plural, one {个资} other {个资}} 已存在于这些相册中",
"assets_downloaded_failed": "{count, plural, one {已下载#个文件 - {error} 文件失败} other {已下载#个文件 - {error} 个文件失败}}",
"assets_downloaded_successfully": "{count, plural, one {已成功下载 # 个文件} other {已成功下载 # 个文件}}",
"assets_moved_to_trash_count": "{count, plural, one {#个资} other {#个资}}移动到回收站",
"assets_permanently_deleted_count": "已永久删除{count, plural, one {#个资} other {#个资}}",
"assets_removed_count": "已移除{count, plural, one {#个资} other {#个资}}",
"assets_removed_permanently_from_device": "已从设备中永久除 {count} 个资",
"assets_restore_confirmation": "确定要恢复回收站中的所有资吗?操作无法撤!请注意,脱机项目无法通过这种方式恢复。",
"assets_restored_count": "已恢复{count, plural, one {#个资} other {#个资}}",
"assets_restored_successfully": "已成功恢复{count}个资",
"assets_trashed": "{count} 个资产放入回收站",
"assets_trashed_count": "{count, plural, one {#个资} other {#个资}}已放入回收站",
"assets_trashed_from_server": "{count} 个资产已放入回收站",
"assets_were_part_of_album_count": "{count, plural, one {个资} other {个资}}已在相册中",
"assets_were_part_of_albums_count": "{count, plural, one {个资} other {个资}} 已相册中",
"authorized_devices": "已授权设备",
"automatic_endpoint_switching_subtitle": "在可用时通过指定 Wi-Fi 进行本地连接,其他位置则使用替代网络连接",
"automatic_endpoint_switching_subtitle": "连接指定 Wi-Fi 时使用本地网络,否则使用外部网络",
"automatic_endpoint_switching_title": "自动切换 URL",
"autoplay_slideshow": "自动播放幻灯片",
"back": "返回",
"back_close_deselect": "返回、关闭或取消选择",
"background_backup_running_error": "后台备份正在运行,无法启动手动备份",
"background_backup_running_error": "后台备份正在运行,无法启动手动备份",
"background_location_permission": "后台定位权限",
"background_location_permission_content": "为了在后台运行时实现网络切换Immich 必须始终拥有精确位置访问权限,以便应用能够读取 Wi-Fi 网络名称",
"background_options": "后台选项",
"background_location_permission_content": "为确保后台运行时自动切换网络,需授予 Immich *始终允许精确定位* 权限,以识别 Wi-Fi 网络名称",
"background_options": "背景选项",
"backup": "备份",
"backup_album_selection_page_albums_device": "设备上的相册({count}",
"backup_album_selection_page_albums_tap": "单击包含,双击排除",
"backup_album_selection_page_assets_scatter": "资源文件可能分散在多个相册中。因此,在备份过程中,您可以选择包含或排除特定相册。",
"backup_album_selection_page_albums_tap": "单击选中,双击取消",
"backup_album_selection_page_assets_scatter": "资可能分散在多个相册中。因此,在备份过程中可以选择包含或排除特定相册。",
"backup_album_selection_page_select_albums": "选择相册",
"backup_album_selection_page_selection_info": "选择信息",
"backup_album_selection_page_total_assets": "唯一资源总计",
"backup_album_selection_page_total_assets": "总计",
"backup_albums_sync": "备份相册同步",
"backup_all": "全部",
"backup_background_service_backup_failed_message": "资源备份失败正在重试…",
"backup_background_service_complete_notification": "资备份完成",
"backup_background_service_connection_failed_message": "无法连接服务器正在重试…",
"backup_background_service_backup_failed_message": "备份失败正在重试…",
"backup_background_service_complete_notification": "资备份完成",
"backup_background_service_connection_failed_message": "连接服务器失败,正在重试…",
"backup_background_service_current_upload_notification": "正在上传 “{filename}”",
"backup_background_service_default_notification": "正在检查新资…",
"backup_background_service_error_title": "备份错误",
"backup_background_service_in_progress_notification": "正在备份您的资…",
"backup_background_service_default_notification": "正在检查新资…",
"backup_background_service_error_title": "备份失败",
"backup_background_service_in_progress_notification": "正在备份您的资…",
"backup_background_service_upload_failure_notification": "“{filename}”上传失败",
"backup_controller_page_albums": "备份相册",
"backup_controller_page_background_app_refresh_disabled_content": "在“设置”>“通用”>“后台 App 刷新”中启用此功能,以使用后台备份。",
"backup_controller_page_background_app_refresh_disabled_title": "后台 App 刷新已关闭",
"backup_controller_page_background_app_refresh_disabled_content": "要使用后台备份功能,请在“设置”>“常规”>“后台应用刷新”中启用后台应用程序刷新。",
"backup_controller_page_background_app_refresh_disabled_title": "后台应用刷新已禁用",
"backup_controller_page_background_app_refresh_enable_button_text": "前往设置",
"backup_controller_page_background_battery_info_link": "展示操作步骤",
"backup_controller_page_background_battery_info_message": "为获得最佳的后台备份体验,请在系统设置中禁用针对 Immich 的任何电池优化限制。\n\n由于该设置因设备而异,请查询您设备制造商的具体要求。",
"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_charging": "仅充电时",
"backup_controller_page_background_configure_error": "后台服务配置失败",
"backup_controller_page_background_delay": "延迟新文件备份:{duration}",
"backup_controller_page_background_description": "开后台服务,即可在无需打开 App 的情况下,自动备份所有新文件",
"backup_controller_page_background_is_off": "后台自动备份未开启",
"backup_controller_page_background_charging": "仅充电时",
"backup_controller_page_background_configure_error": "配置后台服务失败",
"backup_controller_page_background_delay": "延迟备份的新资产{duration}",
"backup_controller_page_background_description": "开后台服务以自动备份任何新资产,且无需打开应用",
"backup_controller_page_background_is_off": "后台自动备份已关闭",
"backup_controller_page_background_is_on": "后台自动备份已开启",
"backup_controller_page_background_turn_off": "关闭后台服务",
"backup_controller_page_background_turn_on": "开启后台服务",
"backup_controller_page_background_wifi": "仅 Wi-Fi",
"backup_controller_page_background_wifi": "仅 Wi-Fi",
"backup_controller_page_backup": "备份",
"backup_controller_page_backup_selected": "已选: ",
"backup_controller_page_backup_selected": "已选 ",
"backup_controller_page_backup_sub": "已备份的照片和视频",
"backup_controller_page_created": "创建时间:{date}",
"backup_controller_page_desc_backup": "开前台备份,打开 App 即自动上传新文件。",
"backup_controller_page_desc_backup": "开前台备份,以在程序运行时自动备份新资产。",
"backup_controller_page_excluded": "已排除: ",
"backup_controller_page_failed": "失败({count}",
"backup_controller_page_filename": "文件名:{filename} [{size}]",
"backup_controller_page_filename": "文件名{filename} [{size}]",
"backup_controller_page_id": "ID{id}",
"backup_controller_page_info": "备份信息",
"backup_controller_page_none_selected": "未选择",
"backup_controller_page_none_selected": "未选择",
"backup_controller_page_remainder": "剩余",
"backup_controller_page_remainder_sub": "已选项中尚未备份的照片和视频",
"backup_controller_page_remainder_sub": "所选数据中尚未备份的数据",
"backup_controller_page_server_storage": "服务器存储",
"backup_controller_page_start_backup": "开始备份",
"backup_controller_page_status_off": "未开启前台自动备份",
"backup_controller_page_status_on": "前台自动备份已开",
"backup_controller_page_storage_format": "已用 {used}(共 {total}",
"backup_controller_page_to_backup": "备份的相册",
"backup_controller_page_total_sub": "包含所选相册内全部唯一的照片和视频",
"backup_controller_page_status_off": "前台自动备份已关闭",
"backup_controller_page_status_on": "前台自动备份已开",
"backup_controller_page_storage_format": "{used}/{total} 已使用",
"backup_controller_page_to_backup": "备份的相册",
"backup_controller_page_total_sub": "选中相册中所有不重复的视频和图像",
"backup_controller_page_turn_off": "关闭前台备份",
"backup_controller_page_turn_on": "开启前台备份",
"backup_controller_page_uploading_file_info": "正在上传中的文件信息",
@@ -1195,6 +1192,7 @@
"features": "功能",
"features_in_development": "开发中的功能",
"features_setting_description": "管理 App 功能",
"file_name": "文件名:{file_name}",
"file_name_or_extension": "文件名或扩展名",
"file_size": "大小",
"filename": "文件名",
@@ -1242,7 +1240,7 @@
"has_quota": "配额大小",
"hash_asset": "哈希项目",
"hashed_assets": "已哈希的项目",
"hashing": "正在进行哈希检验",
"hashing": "正在哈希",
"header_settings_add_header_tip": "添加标头",
"header_settings_field_validator_msg": "设置不可为空",
"header_settings_header_name_input": "标头名称",
@@ -1519,7 +1517,7 @@
"mirror_horizontal": "水平",
"mirror_vertical": "垂直",
"missing": "缺失",
"mobile_app": "移动端APP",
"mobile_app": "手机APP",
"mobile_app_download_onboarding_note": "下载移动应用以访问这些选项",
"model": "型号",
"month": "月",
@@ -1820,9 +1818,9 @@
"refreshed": "已刷新",
"refreshes_every_file": "重新扫描所有现有文件和新文件",
"refreshing_encoded_video": "正在刷新已编码视频",
"refreshing_faces": "刷新面部识别",
"refreshing_metadata": "刷新元数据",
"regenerating_thumbnails": "重新生成缩略图",
"refreshing_faces": "正在面部重新识别",
"refreshing_metadata": "正在刷新元数据",
"regenerating_thumbnails": "正在重新生成缩略图",
"remote": "远程",
"remote_assets": "远程项目",
"remote_media_summary": "远程媒体摘要",
@@ -2297,7 +2295,6 @@
"upload_details": "上传详情",
"upload_dialog_info": "是否要将所选项目备份到服务器?",
"upload_dialog_title": "上传项目",
"upload_error_with_count": "{count, plural, one {# 个项目} other {# 个项目}}上传错误",
"upload_errors": "上传完成,出现{count, plural, one {#个错误} other {#个错误}},刷新页面以查看新上传的项目。",
"upload_finished": "上传完成",
"upload_progress": "剩余{remaining, number} - 已处理 {processed, number}/{total, number}",

View File

@@ -1,6 +1,6 @@
[project]
name = "immich-ml"
version = "2.5.4"
version = "2.5.2"
description = ""
authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }]
requires-python = ">=3.11,<4.0"
@@ -41,6 +41,7 @@ types = [
"types-ujson>=5.10.0.20240515",
]
lint = [
"black>=23.3.0",
"mypy>=1.3.0",
"ruff>=0.0.272",
{ include-group = "types" },
@@ -92,5 +93,9 @@ target-version = "py311"
select = ["E", "F", "I"]
per-file-ignores = { "test_main.py" = ["F403"] }
[tool.black]
line-length = 120
target-version = ['py311']
[tool.pytest.ini_options]
markers = ["providers", "ov_device_ids"]

View File

@@ -85,6 +85,43 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764, upload-time = "2024-02-18T19:09:04.156Z" },
]
[[package]]
name = "black"
version = "25.12.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "mypy-extensions" },
{ name = "packaging" },
{ name = "pathspec" },
{ name = "platformdirs" },
{ name = "pytokens" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c4/d9/07b458a3f1c525ac392b5edc6b191ff140b596f9d77092429417a54e249d/black-25.12.0.tar.gz", hash = "sha256:8d3dd9cea14bff7ddc0eb243c811cdb1a011ebb4800a5f0335a01a68654796a7", size = 659264, upload-time = "2025-12-08T01:40:52.501Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/60/ad/7ac0d0e1e0612788dbc48e62aef8a8e8feffac7eb3d787db4e43b8462fa8/black-25.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0cfa263e85caea2cff57d8f917f9f51adae8e20b610e2b23de35b5b11ce691a", size = 1877003, upload-time = "2025-12-08T01:43:29.967Z" },
{ url = "https://files.pythonhosted.org/packages/e8/dd/a237e9f565f3617a88b49284b59cbca2a4f56ebe68676c1aad0ce36a54a7/black-25.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a2f578ae20c19c50a382286ba78bfbeafdf788579b053d8e4980afb079ab9be", size = 1712639, upload-time = "2025-12-08T01:52:46.756Z" },
{ url = "https://files.pythonhosted.org/packages/12/80/e187079df1ea4c12a0c63282ddd8b81d5107db6d642f7d7b75a6bcd6fc21/black-25.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e1b65634b0e471d07ff86ec338819e2ef860689859ef4501ab7ac290431f9b", size = 1758143, upload-time = "2025-12-08T01:45:29.137Z" },
{ url = "https://files.pythonhosted.org/packages/93/b5/3096ccee4f29dc2c3aac57274326c4d2d929a77e629f695f544e159bfae4/black-25.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a3fa71e3b8dd9f7c6ac4d818345237dfb4175ed3bf37cd5a581dbc4c034f1ec5", size = 1420698, upload-time = "2025-12-08T01:45:53.379Z" },
{ url = "https://files.pythonhosted.org/packages/7e/39/f81c0ffbc25ffbe61c7d0385bf277e62ffc3e52f5ee668d7369d9854fadf/black-25.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:51e267458f7e650afed8445dc7edb3187143003d52a1b710c7321aef22aa9655", size = 1229317, upload-time = "2025-12-08T01:46:35.606Z" },
{ url = "https://files.pythonhosted.org/packages/d1/bd/26083f805115db17fda9877b3c7321d08c647df39d0df4c4ca8f8450593e/black-25.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f96b7c98c1ddaeb07dc0f56c652e25bdedaac76d5b68a059d998b57c55594a", size = 1924178, upload-time = "2025-12-08T01:49:51.048Z" },
{ url = "https://files.pythonhosted.org/packages/89/6b/ea00d6651561e2bdd9231c4177f4f2ae19cc13a0b0574f47602a7519b6ca/black-25.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:05dd459a19e218078a1f98178c13f861fe6a9a5f88fc969ca4d9b49eb1809783", size = 1742643, upload-time = "2025-12-08T01:49:59.09Z" },
{ url = "https://files.pythonhosted.org/packages/6d/f3/360fa4182e36e9875fabcf3a9717db9d27a8d11870f21cff97725c54f35b/black-25.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1f68c5eff61f226934be6b5b80296cf6939e5d2f0c2f7d543ea08b204bfaf59", size = 1800158, upload-time = "2025-12-08T01:44:27.301Z" },
{ url = "https://files.pythonhosted.org/packages/f8/08/2c64830cb6616278067e040acca21d4f79727b23077633953081c9445d61/black-25.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:274f940c147ddab4442d316b27f9e332ca586d39c85ecf59ebdea82cc9ee8892", size = 1426197, upload-time = "2025-12-08T01:45:51.198Z" },
{ url = "https://files.pythonhosted.org/packages/d4/60/a93f55fd9b9816b7432cf6842f0e3000fdd5b7869492a04b9011a133ee37/black-25.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:169506ba91ef21e2e0591563deda7f00030cb466e747c4b09cb0a9dae5db2f43", size = 1237266, upload-time = "2025-12-08T01:45:10.556Z" },
{ url = "https://files.pythonhosted.org/packages/c8/52/c551e36bc95495d2aa1a37d50566267aa47608c81a53f91daa809e03293f/black-25.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a05ddeb656534c3e27a05a29196c962877c83fa5503db89e68857d1161ad08a5", size = 1923809, upload-time = "2025-12-08T01:46:55.126Z" },
{ url = "https://files.pythonhosted.org/packages/a0/f7/aac9b014140ee56d247e707af8db0aae2e9efc28d4a8aba92d0abd7ae9d1/black-25.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ec77439ef3e34896995503865a85732c94396edcc739f302c5673a2315e1e7f", size = 1742384, upload-time = "2025-12-08T01:49:37.022Z" },
{ url = "https://files.pythonhosted.org/packages/74/98/38aaa018b2ab06a863974c12b14a6266badc192b20603a81b738c47e902e/black-25.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e509c858adf63aa61d908061b52e580c40eae0dfa72415fa47ac01b12e29baf", size = 1798761, upload-time = "2025-12-08T01:46:05.386Z" },
{ url = "https://files.pythonhosted.org/packages/16/3a/a8ac542125f61574a3f015b521ca83b47321ed19bb63fe6d7560f348bfe1/black-25.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:252678f07f5bac4ff0d0e9b261fbb029fa530cfa206d0a636a34ab445ef8ca9d", size = 1429180, upload-time = "2025-12-08T01:45:34.903Z" },
{ url = "https://files.pythonhosted.org/packages/e6/2d/bdc466a3db9145e946762d52cd55b1385509d9f9004fec1c97bdc8debbfb/black-25.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bc5b1c09fe3c931ddd20ee548511c64ebf964ada7e6f0763d443947fd1c603ce", size = 1239350, upload-time = "2025-12-08T01:46:09.458Z" },
{ url = "https://files.pythonhosted.org/packages/35/46/1d8f2542210c502e2ae1060b2e09e47af6a5e5963cb78e22ec1a11170b28/black-25.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0a0953b134f9335c2434864a643c842c44fba562155c738a2a37a4d61f00cad5", size = 1917015, upload-time = "2025-12-08T01:53:27.987Z" },
{ url = "https://files.pythonhosted.org/packages/41/37/68accadf977672beb8e2c64e080f568c74159c1aaa6414b4cd2aef2d7906/black-25.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2355bbb6c3b76062870942d8cc450d4f8ac71f9c93c40122762c8784df49543f", size = 1741830, upload-time = "2025-12-08T01:54:36.861Z" },
{ url = "https://files.pythonhosted.org/packages/ac/76/03608a9d8f0faad47a3af3a3c8c53af3367f6c0dd2d23a84710456c7ac56/black-25.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9678bd991cc793e81d19aeeae57966ee02909877cb65838ccffef24c3ebac08f", size = 1791450, upload-time = "2025-12-08T01:44:52.581Z" },
{ url = "https://files.pythonhosted.org/packages/06/99/b2a4bd7dfaea7964974f947e1c76d6886d65fe5d24f687df2d85406b2609/black-25.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:97596189949a8aad13ad12fcbb4ae89330039b96ad6742e6f6b45e75ad5cfd83", size = 1452042, upload-time = "2025-12-08T01:46:13.188Z" },
{ url = "https://files.pythonhosted.org/packages/b2/7c/d9825de75ae5dd7795d007681b752275ea85a1c5d83269b4b9c754c2aaab/black-25.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:778285d9ea197f34704e3791ea9404cd6d07595745907dd2ce3da7a13627b29b", size = 1267446, upload-time = "2025-12-08T01:46:14.497Z" },
{ url = "https://files.pythonhosted.org/packages/68/11/21331aed19145a952ad28fca2756a1433ee9308079bd03bd898e903a2e53/black-25.12.0-py3-none-any.whl", hash = "sha256:48ceb36c16dbc84062740049eef990bb2ce07598272e673c17d1a7720c71c828", size = 206191, upload-time = "2025-12-08T01:40:50.963Z" },
]
[[package]]
name = "blinker"
version = "1.7.0"
@@ -882,7 +919,7 @@ wheels = [
[[package]]
name = "immich-ml"
version = "2.5.4"
version = "2.5.2"
source = { editable = "." }
dependencies = [
{ name = "aiocache" },
@@ -924,6 +961,7 @@ rknn = [
[package.dev-dependencies]
dev = [
{ name = "black" },
{ name = "httpx" },
{ name = "locust" },
{ name = "mypy" },
@@ -939,6 +977,7 @@ dev = [
{ name = "types-ujson" },
]
lint = [
{ name = "black" },
{ name = "mypy" },
{ name = "ruff" },
{ name = "types-pyyaml" },
@@ -992,6 +1031,7 @@ provides-extras = ["cpu", "cuda", "openvino", "armnn", "rknn", "rocm"]
[package.metadata.requires-dev]
dev = [
{ name = "black", specifier = ">=23.3.0" },
{ name = "httpx", specifier = ">=0.24.1" },
{ name = "locust", specifier = ">=2.15.1" },
{ name = "mypy", specifier = ">=1.3.0" },
@@ -1007,6 +1047,7 @@ dev = [
{ name = "types-ujson", specifier = ">=5.10.0.20240515" },
]
lint = [
{ name = "black", specifier = ">=23.3.0" },
{ name = "mypy", specifier = ">=1.3.0" },
{ name = "ruff", specifier = ">=0.0.272" },
{ name = "types-pyyaml", specifier = ">=6.0.12.20241230" },
@@ -2191,6 +2232,15 @@ client = [
{ name = "websocket-client" },
]
[[package]]
name = "pytokens"
version = "0.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4e/8d/a762be14dae1c3bf280202ba3172020b2b0b4c537f94427435f19c413b72/pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a", size = 17644, upload-time = "2025-11-05T13:36:35.34Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/84/25/d9db8be44e205a124f6c98bc0324b2bb149b7431c53877fc6d1038dddaf5/pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3", size = 12195, upload-time = "2025-11-05T13:36:33.183Z" },
]
[[package]]
name = "pywin32"
version = "311"

View File

@@ -131,7 +131,6 @@ dependencies {
implementation "androidx.compose.ui:ui-tooling:$compose_version"
implementation "androidx.compose.material3:material3:1.2.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
implementation "com.google.android.material:material:1.12.0"
}
// This is uncommented in F-Droid build script

View File

@@ -27,8 +27,7 @@
<application android:label="Immich" android:name=".ImmichApp" android:usesCleartextTraffic="true"
android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true"
android:largeHeap="true" android:enableOnBackInvokedCallback="false" android:allowBackup="false"
android:networkSecurityConfig="@xml/network_security_config">
android:largeHeap="true" android:enableOnBackInvokedCallback="false" android:allowBackup="false">
<profileable android:shell="true" />

View File

@@ -0,0 +1,153 @@
package app.alextran.immich
import android.annotation.SuppressLint
import android.content.Context
import app.alextran.immich.core.SSLConfig
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.io.ByteArrayInputStream
import java.net.InetSocketAddress
import java.net.Socket
import java.security.KeyStore
import java.security.cert.X509Certificate
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.KeyManager
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLEngine
import javax.net.ssl.SSLSession
import javax.net.ssl.TrustManager
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509ExtendedTrustManager
/**
* Android plugin for Dart `HttpSSLOptions`
*/
class HttpSSLOptionsPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
private var methodChannel: MethodChannel? = null
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
onAttachedToEngine(binding.applicationContext, binding.binaryMessenger)
}
private fun onAttachedToEngine(ctx: Context, messenger: BinaryMessenger) {
methodChannel = MethodChannel(messenger, "immich/httpSSLOptions")
methodChannel?.setMethodCallHandler(this)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
onDetachedFromEngine()
}
private fun onDetachedFromEngine() {
methodChannel?.setMethodCallHandler(null)
methodChannel = null
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
try {
when (call.method) {
"apply" -> {
val args = call.arguments<ArrayList<*>>()!!
val allowSelfSigned = args[0] as Boolean
val serverHost = args[1] as? String
val clientCertHash = (args[2] as? ByteArray)
var tm: Array<TrustManager>? = null
if (allowSelfSigned) {
tm = arrayOf(AllowSelfSignedTrustManager(serverHost))
}
var km: Array<KeyManager>? = null
if (clientCertHash != null) {
val cert = ByteArrayInputStream(clientCertHash)
val password = (args[3] as String).toCharArray()
val keyStore = KeyStore.getInstance("PKCS12")
keyStore.load(cert, password)
val keyManagerFactory =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
keyManagerFactory.init(keyStore, null)
km = keyManagerFactory.keyManagers
}
// Update shared SSL config for OkHttp and other HTTP clients
SSLConfig.apply(km, tm, allowSelfSigned, serverHost, clientCertHash?.contentHashCode() ?: 0)
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(km, tm, null)
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory)
HttpsURLConnection.setDefaultHostnameVerifier(AllowSelfSignedHostnameVerifier(args[1] as? String))
result.success(true)
}
else -> result.notImplemented()
}
} catch (e: Throwable) {
result.error("error", e.message, null)
}
}
@SuppressLint("CustomX509TrustManager")
class AllowSelfSignedTrustManager(private val serverHost: String?) : X509ExtendedTrustManager() {
private val defaultTrustManager: X509ExtendedTrustManager = getDefaultTrustManager()
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) =
defaultTrustManager.checkClientTrusted(chain, authType)
override fun checkClientTrusted(
chain: Array<out X509Certificate>?, authType: String?, socket: Socket?
) = defaultTrustManager.checkClientTrusted(chain, authType, socket)
override fun checkClientTrusted(
chain: Array<out X509Certificate>?, authType: String?, engine: SSLEngine?
) = defaultTrustManager.checkClientTrusted(chain, authType, engine)
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
if (serverHost == null) return
defaultTrustManager.checkServerTrusted(chain, authType)
}
override fun checkServerTrusted(
chain: Array<out X509Certificate>?, authType: String?, socket: Socket?
) {
if (serverHost == null) return
val socketAddress = socket?.remoteSocketAddress
if (socketAddress is InetSocketAddress && socketAddress.hostName == serverHost) return
defaultTrustManager.checkServerTrusted(chain, authType, socket)
}
override fun checkServerTrusted(
chain: Array<out X509Certificate>?, authType: String?, engine: SSLEngine?
) {
if (serverHost == null || engine?.peerHost == serverHost) return
defaultTrustManager.checkServerTrusted(chain, authType, engine)
}
override fun getAcceptedIssuers(): Array<X509Certificate> = defaultTrustManager.acceptedIssuers
private fun getDefaultTrustManager(): X509ExtendedTrustManager {
val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
factory.init(null as KeyStore?)
return factory.trustManagers.filterIsInstance<X509ExtendedTrustManager>().first()
}
}
class AllowSelfSignedHostnameVerifier(private val serverHost: String?) : HostnameVerifier {
companion object {
private val _defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier()
}
override fun verify(hostname: String?, session: SSLSession?): Boolean {
if (serverHost == null || hostname == serverHost) {
return true
} else {
return _defaultHostnameVerifier.verify(hostname, session)
}
}
}
}

View File

@@ -9,9 +9,7 @@ import app.alextran.immich.background.BackgroundWorkerFgHostApi
import app.alextran.immich.background.BackgroundWorkerLockApi
import app.alextran.immich.connectivity.ConnectivityApi
import app.alextran.immich.connectivity.ConnectivityApiImpl
import app.alextran.immich.core.HttpClientManager
import app.alextran.immich.core.ImmichPlugin
import app.alextran.immich.core.NetworkApiPlugin
import app.alextran.immich.images.LocalImageApi
import app.alextran.immich.images.LocalImagesImpl
import app.alextran.immich.images.RemoteImageApi
@@ -30,9 +28,6 @@ class MainActivity : FlutterFragmentActivity() {
companion object {
fun registerPlugins(ctx: Context, flutterEngine: FlutterEngine) {
HttpClientManager.initialize(ctx)
flutterEngine.plugins.add(NetworkApiPlugin())
val messenger = flutterEngine.dartExecutor.binaryMessenger
val backgroundEngineLockImpl = BackgroundEngineLock(ctx)
BackgroundWorkerLockApi.setUp(messenger, backgroundEngineLockImpl)
@@ -50,6 +45,7 @@ class MainActivity : FlutterFragmentActivity() {
ConnectivityApi.setUp(messenger, ConnectivityApiImpl(ctx))
flutterEngine.plugins.add(BackgroundServicePlugin())
flutterEngine.plugins.add(HttpSSLOptionsPlugin())
flutterEngine.plugins.add(backgroundEngineLockImpl)
flutterEngine.plugins.add(nativeSyncApiImpl)
}

View File

@@ -1,149 +0,0 @@
package app.alextran.immich.core
import android.content.Context
import app.alextran.immich.BuildConfig
import okhttp3.Cache
import okhttp3.ConnectionPool
import okhttp3.Dispatcher
import okhttp3.OkHttpClient
import java.io.ByteArrayInputStream
import java.io.File
import java.net.Socket
import java.security.KeyStore
import java.security.Principal
import java.security.PrivateKey
import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509KeyManager
import javax.net.ssl.X509TrustManager
const val CERT_ALIAS = "client_cert"
const val USER_AGENT = "Immich_Android_${BuildConfig.VERSION_NAME}"
/**
* Manages a shared OkHttpClient with SSL configuration support.
*/
object HttpClientManager {
private const val CACHE_SIZE_BYTES = 100L * 1024 * 1024 // 100MiB
private const val KEEP_ALIVE_CONNECTIONS = 10
private const val KEEP_ALIVE_DURATION_MINUTES = 5L
private const val MAX_REQUESTS_PER_HOST = 64
private var initialized = false
private val clientChangedListeners = mutableListOf<() -> Unit>()
private lateinit var client: OkHttpClient
private val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val isMtls: Boolean get() = keyStore.containsAlias(CERT_ALIAS)
fun initialize(context: Context) {
if (initialized) return
synchronized(this) {
if (initialized) return
val cacheDir = File(File(context.cacheDir, "okhttp"), "api")
client = build(cacheDir)
initialized = true
}
}
fun setKeyEntry(clientData: ByteArray, password: CharArray) {
synchronized(this) {
val wasMtls = isMtls
val tmpKeyStore = KeyStore.getInstance("PKCS12").apply {
ByteArrayInputStream(clientData).use { stream -> load(stream, password) }
}
val tmpAlias = tmpKeyStore.aliases().asSequence().firstOrNull { tmpKeyStore.isKeyEntry(it) }
?: throw IllegalArgumentException("No private key found in PKCS12")
val key = tmpKeyStore.getKey(tmpAlias, password)
val chain = tmpKeyStore.getCertificateChain(tmpAlias)
if (wasMtls) {
keyStore.deleteEntry(CERT_ALIAS)
}
keyStore.setKeyEntry(CERT_ALIAS, key, null, chain)
if (wasMtls != isMtls) {
clientChangedListeners.forEach { it() }
}
}
}
fun deleteKeyEntry() {
synchronized(this) {
if (!isMtls) {
return
}
keyStore.deleteEntry(CERT_ALIAS)
clientChangedListeners.forEach { it() }
}
}
@JvmStatic
fun getClient(): OkHttpClient {
return client
}
fun addClientChangedListener(listener: () -> Unit) {
synchronized(this) { clientChangedListeners.add(listener) }
}
private fun build(cacheDir: File): OkHttpClient {
val connectionPool = ConnectionPool(
maxIdleConnections = KEEP_ALIVE_CONNECTIONS,
keepAliveDuration = KEEP_ALIVE_DURATION_MINUTES,
timeUnit = TimeUnit.MINUTES
)
val managerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
managerFactory.init(null as KeyStore?)
val trustManager = managerFactory.trustManagers.filterIsInstance<X509TrustManager>().first()
val sslContext = SSLContext.getInstance("TLS")
.apply { init(arrayOf(DynamicKeyManager()), arrayOf(trustManager), null) }
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory)
return OkHttpClient.Builder()
.addInterceptor { chain ->
chain.proceed(chain.request().newBuilder().header("User-Agent", USER_AGENT).build())
}
.connectionPool(connectionPool)
.dispatcher(Dispatcher().apply { maxRequestsPerHost = MAX_REQUESTS_PER_HOST })
.cache(Cache(cacheDir.apply { mkdirs() }, CACHE_SIZE_BYTES))
.sslSocketFactory(sslContext.socketFactory, trustManager)
.build()
}
// Reads from the key store rather than taking a snapshot at initialization time
private class DynamicKeyManager : X509KeyManager {
override fun getClientAliases(keyType: String, issuers: Array<Principal>?): Array<String>? =
if (isMtls) arrayOf(CERT_ALIAS) else null
override fun chooseClientAlias(
keyTypes: Array<String>,
issuers: Array<Principal>?,
socket: Socket?
): String? =
if (isMtls) CERT_ALIAS else null
override fun getCertificateChain(alias: String): Array<X509Certificate>? =
keyStore.getCertificateChain(alias)?.map { it as X509Certificate }?.toTypedArray()
override fun getPrivateKey(alias: String): PrivateKey? =
keyStore.getKey(alias, null) as? PrivateKey
override fun getServerAliases(keyType: String, issuers: Array<Principal>?): Array<String>? =
null
override fun chooseServerAlias(
keyType: String,
issuers: Array<Principal>?,
socket: Socket?
): String? = null
}
}

View File

@@ -1,253 +0,0 @@
// Autogenerated from Pigeon (v26.0.2), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
package app.alextran.immich.core
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private object NetworkPigeonUtils {
fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}
fun wrapError(exception: Throwable): List<Any?> {
return if (exception is FlutterError) {
listOf(
exception.code,
exception.message,
exception.details
)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}
fun deepEquals(a: Any?, b: Any?): Boolean {
if (a is ByteArray && b is ByteArray) {
return a.contentEquals(b)
}
if (a is IntArray && b is IntArray) {
return a.contentEquals(b)
}
if (a is LongArray && b is LongArray) {
return a.contentEquals(b)
}
if (a is DoubleArray && b is DoubleArray) {
return a.contentEquals(b)
}
if (a is Array<*> && b is Array<*>) {
return a.size == b.size &&
a.indices.all{ deepEquals(a[it], b[it]) }
}
if (a is List<*> && b is List<*>) {
return a.size == b.size &&
a.indices.all{ deepEquals(a[it], b[it]) }
}
if (a is Map<*, *> && b is Map<*, *>) {
return a.size == b.size && a.all {
(b as Map<Any?, Any?>).containsKey(it.key) &&
deepEquals(it.value, b[it.key])
}
}
return a == b
}
}
/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class FlutterError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : Throwable()
/** Generated class from Pigeon that represents data sent in messages. */
data class ClientCertData (
val data: ByteArray,
val password: String
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): ClientCertData {
val data = pigeonVar_list[0] as ByteArray
val password = pigeonVar_list[1] as String
return ClientCertData(data, password)
}
}
fun toList(): List<Any?> {
return listOf(
data,
password,
)
}
override fun equals(other: Any?): Boolean {
if (other !is ClientCertData) {
return false
}
if (this === other) {
return true
}
return NetworkPigeonUtils.deepEquals(toList(), other.toList()) }
override fun hashCode(): Int = toList().hashCode()
}
/** Generated class from Pigeon that represents data sent in messages. */
data class ClientCertPrompt (
val title: String,
val message: String,
val cancel: String,
val confirm: String
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): ClientCertPrompt {
val title = pigeonVar_list[0] as String
val message = pigeonVar_list[1] as String
val cancel = pigeonVar_list[2] as String
val confirm = pigeonVar_list[3] as String
return ClientCertPrompt(title, message, cancel, confirm)
}
}
fun toList(): List<Any?> {
return listOf(
title,
message,
cancel,
confirm,
)
}
override fun equals(other: Any?): Boolean {
if (other !is ClientCertPrompt) {
return false
}
if (this === other) {
return true
}
return NetworkPigeonUtils.deepEquals(toList(), other.toList()) }
override fun hashCode(): Int = toList().hashCode()
}
private open class NetworkPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
129.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ClientCertData.fromList(it)
}
}
130.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ClientCertPrompt.fromList(it)
}
}
else -> super.readValueOfType(type, buffer)
}
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is ClientCertData -> {
stream.write(129)
writeValue(stream, value.toList())
}
is ClientCertPrompt -> {
stream.write(130)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface NetworkApi {
fun addCertificate(clientData: ClientCertData, callback: (Result<Unit>) -> Unit)
fun selectCertificate(promptText: ClientCertPrompt, callback: (Result<ClientCertData>) -> Unit)
fun removeCertificate(callback: (Result<Unit>) -> Unit)
companion object {
/** The codec used by NetworkApi. */
val codec: MessageCodec<Any?> by lazy {
NetworkPigeonCodec()
}
/** Sets up an instance of `NetworkApi` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: NetworkApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.addCertificate$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val clientDataArg = args[0] as ClientCertData
api.addCertificate(clientDataArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(NetworkPigeonUtils.wrapError(error))
} else {
reply.reply(NetworkPigeonUtils.wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.selectCertificate$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val promptTextArg = args[0] as ClientCertPrompt
api.selectCertificate(promptTextArg) { result: Result<ClientCertData> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(NetworkPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(NetworkPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.removeCertificate$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.removeCertificate{ result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(NetworkPigeonUtils.wrapError(error))
} else {
reply.reply(NetworkPigeonUtils.wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}

View File

@@ -1,159 +0,0 @@
package app.alextran.immich.core
import android.app.Activity
import android.content.Context
import android.net.Uri
import android.os.OperationCanceledException
import android.text.InputType
import android.view.ContextThemeWrapper
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
class NetworkApiPlugin : FlutterPlugin, ActivityAware {
private var networkApi: NetworkApiImpl? = null
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
networkApi = NetworkApiImpl(binding.applicationContext)
NetworkApi.setUp(binding.binaryMessenger, networkApi)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
NetworkApi.setUp(binding.binaryMessenger, null)
networkApi = null
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
networkApi?.onAttachedToActivity(binding)
}
override fun onDetachedFromActivityForConfigChanges() {
networkApi?.onDetachedFromActivityForConfigChanges()
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
networkApi?.onReattachedToActivityForConfigChanges(binding)
}
override fun onDetachedFromActivity() {
networkApi?.onDetachedFromActivity()
}
}
private class NetworkApiImpl(private val context: Context) : NetworkApi {
private var activity: Activity? = null
private var pendingCallback: ((Result<ClientCertData>) -> Unit)? = null
private var filePicker: ActivityResultLauncher<Array<String>>? = null
private var promptText: ClientCertPrompt? = null
fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
(binding.activity as? ComponentActivity)?.let { componentActivity ->
filePicker = componentActivity.registerForActivityResult(
ActivityResultContracts.OpenDocument()
) { uri -> uri?.let { handlePickedFile(it) } ?: pendingCallback?.invoke(Result.failure(OperationCanceledException())) }
}
}
fun onDetachedFromActivityForConfigChanges() {
activity = null
}
fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
}
fun onDetachedFromActivity() {
activity = null
}
override fun addCertificate(clientData: ClientCertData, callback: (Result<Unit>) -> Unit) {
try {
HttpClientManager.setKeyEntry(clientData.data, clientData.password.toCharArray())
callback(Result.success(Unit))
} catch (e: Exception) {
callback(Result.failure(e))
}
}
override fun selectCertificate(promptText: ClientCertPrompt, callback: (Result<ClientCertData>) -> Unit) {
val picker = filePicker ?: return callback(Result.failure(IllegalStateException("No activity")))
pendingCallback = callback
this.promptText = promptText
picker.launch(arrayOf("application/x-pkcs12", "application/x-pem-file"))
}
override fun removeCertificate(callback: (Result<Unit>) -> Unit) {
HttpClientManager.deleteKeyEntry()
callback(Result.success(Unit))
}
private fun handlePickedFile(uri: Uri) {
val callback = pendingCallback ?: return
pendingCallback = null
try {
val data = context.contentResolver.openInputStream(uri)?.use { it.readBytes() }
?: throw IllegalStateException("Could not read file")
val activity = activity ?: throw IllegalStateException("No activity")
promptForPassword(activity) { password ->
promptText = null
if (password == null) {
callback(Result.failure(OperationCanceledException()))
return@promptForPassword
}
try {
HttpClientManager.setKeyEntry(data, password.toCharArray())
callback(Result.success(ClientCertData(data, password)))
} catch (e: Exception) {
callback(Result.failure(e))
}
}
} catch (e: Exception) {
callback(Result.failure(e))
}
}
private fun promptForPassword(activity: Activity, callback: (String?) -> Unit) {
val themedContext = ContextThemeWrapper(activity, com.google.android.material.R.style.Theme_Material3_DayNight_Dialog)
val density = activity.resources.displayMetrics.density
val horizontalPadding = (24 * density).toInt()
val textInputLayout = TextInputLayout(themedContext).apply {
hint = "Password"
endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply {
setMargins(horizontalPadding, 0, horizontalPadding, 0)
}
}
val editText = TextInputEditText(textInputLayout.context).apply {
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
}
textInputLayout.addView(editText)
val container = FrameLayout(themedContext).apply { addView(textInputLayout) }
val text = promptText!!
MaterialAlertDialogBuilder(themedContext)
.setTitle(text.title)
.setMessage(text.message)
.setView(container)
.setPositiveButton(text.confirm) { _, _ -> callback(editText.text.toString()) }
.setNegativeButton(text.cancel) { _, _ -> callback(null) }
.setOnCancelListener { callback(null) }
.show()
}
}

View File

@@ -0,0 +1,73 @@
package app.alextran.immich.core
import java.security.KeyStore
import javax.net.ssl.KeyManager
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManager
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
/**
* Shared SSL configuration for OkHttp and HttpsURLConnection.
* Stores the SSLSocketFactory and X509TrustManager configured by HttpSSLOptionsPlugin.
*/
object SSLConfig {
var sslSocketFactory: SSLSocketFactory? = null
private set
var trustManager: X509TrustManager? = null
private set
var requiresCustomSSL: Boolean = false
private set
private val listeners = mutableListOf<() -> Unit>()
private var configHash: Int = 0
fun addListener(listener: () -> Unit) {
listeners.add(listener)
}
fun apply(
keyManagers: Array<KeyManager>?,
trustManagers: Array<TrustManager>?,
allowSelfSigned: Boolean,
serverHost: String?,
clientCertHash: Int
) {
synchronized(this) {
val newHash = computeHash(allowSelfSigned, serverHost, clientCertHash)
val newRequiresCustomSSL = allowSelfSigned || keyManagers != null
if (newHash == configHash && sslSocketFactory != null && requiresCustomSSL == newRequiresCustomSSL) {
return // Config unchanged, skip
}
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(keyManagers, trustManagers, null)
sslSocketFactory = sslContext.socketFactory
trustManager = trustManagers?.filterIsInstance<X509TrustManager>()?.firstOrNull()
?: getDefaultTrustManager()
requiresCustomSSL = newRequiresCustomSSL
configHash = newHash
notifyListeners()
}
}
private fun computeHash(allowSelfSigned: Boolean, serverHost: String?, clientCertHash: Int): Int {
var result = allowSelfSigned.hashCode()
result = 31 * result + (serverHost?.hashCode() ?: 0)
result = 31 * result + clientCertHash
return result
}
private fun notifyListeners() {
listeners.forEach { it() }
}
private fun getDefaultTrustManager(): X509TrustManager {
val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
factory.init(null as KeyStore?)
return factory.trustManagers.filterIsInstance<X509TrustManager>().first()
}
}

View File

@@ -3,15 +3,17 @@ package app.alextran.immich.images
import android.content.Context
import android.os.CancellationSignal
import android.os.OperationCanceledException
import app.alextran.immich.BuildConfig
import app.alextran.immich.INITIAL_BUFFER_SIZE
import app.alextran.immich.NativeBuffer
import app.alextran.immich.NativeByteBuffer
import app.alextran.immich.core.HttpClientManager
import app.alextran.immich.core.USER_AGENT
import app.alextran.immich.core.SSLConfig
import kotlinx.coroutines.*
import okhttp3.Cache
import okhttp3.Call
import okhttp3.Callback
import okhttp3.ConnectionPool
import okhttp3.Dispatcher
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
@@ -30,8 +32,15 @@ import java.nio.file.SimpleFileVisitor
import java.nio.file.attribute.BasicFileAttributes
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager
private const val USER_AGENT = "Immich_Android_${BuildConfig.VERSION_NAME}"
private const val MAX_REQUESTS_PER_HOST = 64
private const val KEEP_ALIVE_CONNECTIONS = 10
private const val KEEP_ALIVE_DURATION_MINUTES = 5L
private const val CACHE_SIZE_BYTES = 1024L * 1024 * 1024
private class RemoteRequest(val cancellationSignal: CancellationSignal)
@@ -112,7 +121,7 @@ private object ImageFetcherManager {
appContext = context.applicationContext
cacheDir = context.cacheDir
fetcher = build()
HttpClientManager.addClientChangedListener(::invalidate)
SSLConfig.addListener(::invalidate)
initialized = true
}
}
@@ -134,14 +143,18 @@ private object ImageFetcherManager {
private fun invalidate() {
synchronized(this) {
val oldFetcher = fetcher
if (oldFetcher is OkHttpImageFetcher && SSLConfig.requiresCustomSSL) {
fetcher = oldFetcher.reconfigure(SSLConfig.sslSocketFactory, SSLConfig.trustManager)
return
}
fetcher = build()
oldFetcher.drain()
}
}
private fun build(): ImageFetcher {
return if (HttpClientManager.isMtls) {
OkHttpImageFetcher.create(cacheDir)
return if (SSLConfig.requiresCustomSSL) {
OkHttpImageFetcher.create(cacheDir, SSLConfig.sslSocketFactory, SSLConfig.trustManager)
} else {
CronetImageFetcher(appContext, cacheDir)
}
@@ -367,17 +380,51 @@ private class OkHttpImageFetcher private constructor(
private var draining = false
companion object {
fun create(cacheDir: File): OkHttpImageFetcher {
fun create(
cacheDir: File,
sslSocketFactory: SSLSocketFactory?,
trustManager: X509TrustManager?,
): OkHttpImageFetcher {
val dir = File(cacheDir, "okhttp")
val connectionPool = ConnectionPool(
maxIdleConnections = KEEP_ALIVE_CONNECTIONS,
keepAliveDuration = KEEP_ALIVE_DURATION_MINUTES,
timeUnit = TimeUnit.MINUTES
)
val client = HttpClientManager.getClient().newBuilder()
val builder = OkHttpClient.Builder()
.addInterceptor { chain ->
chain.proceed(
chain.request().newBuilder()
.header("User-Agent", USER_AGENT)
.build()
)
}
.dispatcher(Dispatcher().apply { maxRequestsPerHost = MAX_REQUESTS_PER_HOST })
.connectionPool(connectionPool)
.cache(Cache(File(dir, "thumbnails"), CACHE_SIZE_BYTES))
.build()
return OkHttpImageFetcher(client)
if (sslSocketFactory != null && trustManager != null) {
builder.sslSocketFactory(sslSocketFactory, trustManager)
}
return OkHttpImageFetcher(builder.build())
}
}
fun reconfigure(
sslSocketFactory: SSLSocketFactory?,
trustManager: X509TrustManager?,
): OkHttpImageFetcher {
val builder = client.newBuilder()
if (sslSocketFactory != null && trustManager != null) {
builder.sslSocketFactory(sslSocketFactory, trustManager)
}
// Evict idle connections using old SSL config
client.connectionPool.evictAll()
return OkHttpImageFetcher(builder.build())
}
private fun onComplete() {
val shouldClose = synchronized(stateLock) {
activeCount--
@@ -465,6 +512,7 @@ private class OkHttpImageFetcher private constructor(
draining = true
activeCount == 0
}
client.connectionPool.evictAll()
if (shouldClose) {
client.cache?.close()
}

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>

View File

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

@@ -121,6 +121,4 @@ post_install do |installer|
end
# End of the permission_handler configuration
end
system("defaults write com.apple.dt.Xcode IDESkipPackagePluginFingerprintValidatation -bool YES")
system("defaults write com.apple.dt.Xcode IDESkipMacroFingerprintValidation -bool YES")
end

View File

@@ -11,6 +11,40 @@ PODS:
- FlutterMacOS
- device_info_plus (0.0.1):
- Flutter
- DKImagePickerController/Core (4.3.9):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.9)
- DKImagePickerController/PhotoGallery (4.3.9):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.9)
- DKPhotoGallery (0.0.19):
- DKPhotoGallery/Core (= 0.0.19)
- DKPhotoGallery/Model (= 0.0.19)
- DKPhotoGallery/Preview (= 0.0.19)
- DKPhotoGallery/Resource (= 0.0.19)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.19):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.19):
- SDWebImage
- SwiftyGif
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0)
- flutter_local_notifications (0.0.1):
- Flutter
@@ -59,6 +93,9 @@ PODS:
- Flutter
- FlutterMacOS
- SAMKeychain (1.5.3)
- SDWebImage (5.21.0):
- SDWebImage/Core (= 5.21.0)
- SDWebImage/Core (5.21.0)
- share_handler_ios (0.0.14):
- Flutter
- share_handler_ios/share_handler_ios_models (= 0.0.14)
@@ -94,6 +131,7 @@ PODS:
- sqlite3/fts5
- sqlite3/perf-threadsafe
- sqlite3/rtree
- SwiftyGif (5.4.5)
- url_launcher_ios (0.0.1):
- Flutter
- wakelock_plus (0.0.1):
@@ -105,6 +143,7 @@ DEPENDENCIES:
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
@@ -137,9 +176,13 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- DKImagePickerController
- DKPhotoGallery
- MapLibre
- SAMKeychain
- SDWebImage
- sqlite3
- SwiftyGif
EXTERNAL SOURCES:
background_downloader:
@@ -152,6 +195,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/cupertino_http/darwin"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
flutter_local_notifications:
@@ -217,6 +262,9 @@ SPEC CHECKSUMS:
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
@@ -240,6 +288,7 @@ SPEC CHECKSUMS:
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
photo_manager: 1d80ae07a89a67dfbcae95953a1e5a24af7c3e62
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
share_handler_ios: e2244e990f826b2c8eaa291ac3831569438ba0fb
share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
@@ -247,9 +296,10 @@ SPEC CHECKSUMS:
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
sqlite3_flutter_libs: f8fc13346870e73fe35ebf6dbb997fbcd156b241
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
PODFILE CHECKSUM: 938abbae4114b9c2140c550a2a0d8f7c674f5dfe
PODFILE CHECKSUM: 7ce312f2beab01395db96f6969d90a447279cf45
COCOAPODS: 1.16.2

View File

@@ -33,7 +33,6 @@
FE5499F42F1197D8006016CB /* RemoteImages.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE5499F22F1197D8006016CB /* RemoteImages.g.swift */; };
FE5499F62F11980E006016CB /* LocalImagesImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE5499F52F11980E006016CB /* LocalImagesImpl.swift */; };
FE5499F82F1198E2006016CB /* RemoteImagesImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE5499F72F1198DE006016CB /* RemoteImagesImpl.swift */; };
FE5FE4AE2F30FBC000A71243 /* ImageProcessing.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE5FE4AD2F30FBC000A71243 /* ImageProcessing.swift */; };
FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */; };
FEE084F82EC172460045228E /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084F72EC172460045228E /* SQLiteData */; };
FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FA2EC1725A0045228E /* RawStructuredFieldValues */; };
@@ -125,7 +124,6 @@
FE5499F22F1197D8006016CB /* RemoteImages.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteImages.g.swift; sourceTree = "<group>"; };
FE5499F52F11980E006016CB /* LocalImagesImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalImagesImpl.swift; sourceTree = "<group>"; };
FE5499F72F1198DE006016CB /* RemoteImagesImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteImagesImpl.swift; sourceTree = "<group>"; };
FE5FE4AD2F30FBC000A71243 /* ImageProcessing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProcessing.swift; sourceTree = "<group>"; };
FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbhash.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -327,7 +325,6 @@
FED3B1952E253E9B0030FD97 /* Images */ = {
isa = PBXGroup;
children = (
FE5FE4AD2F30FBC000A71243 /* ImageProcessing.swift */,
FE5499F72F1198DE006016CB /* RemoteImagesImpl.swift */,
FE5499F52F11980E006016CB /* LocalImagesImpl.swift */,
FE5499F12F1197D8006016CB /* LocalImages.g.swift */,
@@ -612,7 +609,6 @@
FE5499F32F1197D8006016CB /* LocalImages.g.swift in Sources */,
FE5499F62F11980E006016CB /* LocalImagesImpl.swift in Sources */,
FE5499F42F1197D8006016CB /* RemoteImages.g.swift in Sources */,
FE5FE4AE2F30FBC000A71243 /* ImageProcessing.swift in Sources */,
B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */,
FE5499F82F1198E2006016CB /* RemoteImagesImpl.swift in Sources */,
FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */,

View File

@@ -15,8 +15,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/groue/GRDB.swift",
"state" : {
"revision" : "aa0079aeb82a4bf00324561a40bffe68c6fe1c26",
"version" : "7.9.0"
"revision" : "18497b68fdbb3a09528d260a0a0e1e7e61c8c53d",
"version" : "7.8.0"
}
},
{
@@ -24,8 +24,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/sqlite-data",
"state" : {
"revision" : "05704b563ecb7f0bd7e49b6f360a6383a3e53e7d",
"version" : "1.5.1"
"revision" : "b66b894b9a5710f1072c8eb6448a7edfc2d743d9",
"version" : "1.3.0"
}
},
{
"identity" : "swift-case-paths",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-case-paths",
"state" : {
"revision" : "6989976265be3f8d2b5802c722f9ba168e227c71",
"version" : "1.7.2"
}
},
{
@@ -123,8 +132,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-structured-queries",
"state" : {
"revision" : "d8163b3a98f3c8434c4361e85126db449d84bc66",
"version" : "0.30.0"
"revision" : "1447ea20550f6f02c4b48cc80931c3ed40a9c756",
"version" : "0.25.0"
}
},
{
@@ -136,6 +145,15 @@
"version" : "602.0.0"
}
},
{
"identity" : "swift-tagged",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-tagged",
"state" : {
"revision" : "3907a9438f5b57d317001dc99f3f11b46882272b",
"version" : "0.10.0"
}
},
{
"identity" : "xctest-dynamic-overlay",
"kind" : "remoteSourceControl",

View File

@@ -15,12 +15,12 @@ import UIKit
) -> Bool {
// Required for flutter_local_notification
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
GeneratedPluginRegistrant.register(with: self)
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
AppDelegate.registerPlugins(with: controller.engine, controller: controller)
AppDelegate.registerPlugins(with: controller.engine)
BackgroundServicePlugin.register(with: self.registrar(forPlugin: "BackgroundServicePlugin")!)
BackgroundServicePlugin.registerBackgroundProcessing()
@@ -51,13 +51,12 @@ import UIKit
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
public static func registerPlugins(with engine: FlutterEngine, controller: FlutterViewController?) {
public static func registerPlugins(with engine: FlutterEngine) {
NativeSyncApiImpl.register(with: engine.registrar(forPlugin: NativeSyncApiImpl.name)!)
LocalImageApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: LocalImageApiImpl())
RemoteImageApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: RemoteImageApiImpl())
BackgroundWorkerFgHostApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: BackgroundWorkerApiImpl())
ConnectivityApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: ConnectivityApiImpl())
NetworkApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: NetworkApiImpl(viewController: controller))
}
public static func cancelPlugins(with engine: FlutterEngine) {

View File

@@ -95,7 +95,7 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
// Register plugins in the new engine
GeneratedPluginRegistrant.register(with: engine)
// Register custom plugins
AppDelegate.registerPlugins(with: engine, controller: nil)
AppDelegate.registerPlugins(with: engine)
flutterApi = BackgroundWorkerFlutterApi(binaryMessenger: engine.binaryMessenger)
BackgroundWorkerBgHostApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: self)

View File

@@ -1,284 +0,0 @@
// Autogenerated from Pigeon (v26.0.2), do not edit directly.
// See also: https://pub.dev/packages/pigeon
import Foundation
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}
private func wrapError(_ error: Any) -> [Any?] {
if let pigeonError = error as? PigeonError {
return [
pigeonError.code,
pigeonError.message,
pigeonError.details,
]
}
if let flutterError = error as? FlutterError {
return [
flutterError.code,
flutterError.message,
flutterError.details,
]
}
return [
"\(error)",
"\(type(of: error))",
"Stacktrace: \(Thread.callStackSymbols)",
]
}
private func isNullish(_ value: Any?) -> Bool {
return value is NSNull || value == nil
}
private func nilOrValue<T>(_ value: Any?) -> T? {
if value is NSNull { return nil }
return value as! T?
}
func deepEqualsNetwork(_ lhs: Any?, _ rhs: Any?) -> Bool {
let cleanLhs = nilOrValue(lhs) as Any?
let cleanRhs = nilOrValue(rhs) as Any?
switch (cleanLhs, cleanRhs) {
case (nil, nil):
return true
case (nil, _), (_, nil):
return false
case is (Void, Void):
return true
case let (cleanLhsHashable, cleanRhsHashable) as (AnyHashable, AnyHashable):
return cleanLhsHashable == cleanRhsHashable
case let (cleanLhsArray, cleanRhsArray) as ([Any?], [Any?]):
guard cleanLhsArray.count == cleanRhsArray.count else { return false }
for (index, element) in cleanLhsArray.enumerated() {
if !deepEqualsNetwork(element, cleanRhsArray[index]) {
return false
}
}
return true
case let (cleanLhsDictionary, cleanRhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]):
guard cleanLhsDictionary.count == cleanRhsDictionary.count else { return false }
for (key, cleanLhsValue) in cleanLhsDictionary {
guard cleanRhsDictionary.index(forKey: key) != nil else { return false }
if !deepEqualsNetwork(cleanLhsValue, cleanRhsDictionary[key]!) {
return false
}
}
return true
default:
// Any other type shouldn't be able to be used with pigeon. File an issue if you find this to be untrue.
return false
}
}
func deepHashNetwork(value: Any?, hasher: inout Hasher) {
if let valueList = value as? [AnyHashable] {
for item in valueList { deepHashNetwork(value: item, hasher: &hasher) }
return
}
if let valueDict = value as? [AnyHashable: AnyHashable] {
for key in valueDict.keys {
hasher.combine(key)
deepHashNetwork(value: valueDict[key]!, hasher: &hasher)
}
return
}
if let hashableValue = value as? AnyHashable {
hasher.combine(hashableValue.hashValue)
}
return hasher.combine(String(describing: value))
}
/// Generated class from Pigeon that represents data sent in messages.
struct ClientCertData: Hashable {
var data: FlutterStandardTypedData
var password: String
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> ClientCertData? {
let data = pigeonVar_list[0] as! FlutterStandardTypedData
let password = pigeonVar_list[1] as! String
return ClientCertData(
data: data,
password: password
)
}
func toList() -> [Any?] {
return [
data,
password,
]
}
static func == (lhs: ClientCertData, rhs: ClientCertData) -> Bool {
return deepEqualsNetwork(lhs.toList(), rhs.toList()) }
func hash(into hasher: inout Hasher) {
deepHashNetwork(value: toList(), hasher: &hasher)
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct ClientCertPrompt: Hashable {
var title: String
var message: String
var cancel: String
var confirm: String
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> ClientCertPrompt? {
let title = pigeonVar_list[0] as! String
let message = pigeonVar_list[1] as! String
let cancel = pigeonVar_list[2] as! String
let confirm = pigeonVar_list[3] as! String
return ClientCertPrompt(
title: title,
message: message,
cancel: cancel,
confirm: confirm
)
}
func toList() -> [Any?] {
return [
title,
message,
cancel,
confirm,
]
}
static func == (lhs: ClientCertPrompt, rhs: ClientCertPrompt) -> Bool {
return deepEqualsNetwork(lhs.toList(), rhs.toList()) }
func hash(into hasher: inout Hasher) {
deepHashNetwork(value: toList(), hasher: &hasher)
}
}
private class NetworkPigeonCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? {
switch type {
case 129:
return ClientCertData.fromList(self.readValue() as! [Any?])
case 130:
return ClientCertPrompt.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
}
}
}
private class NetworkPigeonCodecWriter: FlutterStandardWriter {
override func writeValue(_ value: Any) {
if let value = value as? ClientCertData {
super.writeByte(129)
super.writeValue(value.toList())
} else if let value = value as? ClientCertPrompt {
super.writeByte(130)
super.writeValue(value.toList())
} else {
super.writeValue(value)
}
}
}
private class NetworkPigeonCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return NetworkPigeonCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return NetworkPigeonCodecWriter(data: data)
}
}
class NetworkPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = NetworkPigeonCodec(readerWriter: NetworkPigeonCodecReaderWriter())
}
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol NetworkApi {
func addCertificate(clientData: ClientCertData, completion: @escaping (Result<Void, Error>) -> Void)
func selectCertificate(promptText: ClientCertPrompt, completion: @escaping (Result<ClientCertData, Error>) -> Void)
func removeCertificate(completion: @escaping (Result<Void, Error>) -> Void)
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class NetworkApiSetup {
static var codec: FlutterStandardMessageCodec { NetworkPigeonCodec.shared }
/// Sets up an instance of `NetworkApi` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: NetworkApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
let addCertificateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.addCertificate\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
addCertificateChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let clientDataArg = args[0] as! ClientCertData
api.addCertificate(clientData: clientDataArg) { result in
switch result {
case .success:
reply(wrapResult(nil))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
addCertificateChannel.setMessageHandler(nil)
}
let selectCertificateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.selectCertificate\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
selectCertificateChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let promptTextArg = args[0] as! ClientCertPrompt
api.selectCertificate(promptText: promptTextArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
selectCertificateChannel.setMessageHandler(nil)
}
let removeCertificateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.removeCertificate\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
removeCertificateChannel.setMessageHandler { _, reply in
api.removeCertificate { result in
switch result {
case .success:
reply(wrapResult(nil))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
removeCertificateChannel.setMessageHandler(nil)
}
}
}

View File

@@ -1,157 +0,0 @@
import Foundation
import UniformTypeIdentifiers
enum ImportError: Error {
case noFile
case noViewController
case keychainError(OSStatus)
case cancelled
}
class NetworkApiImpl: NetworkApi {
weak var viewController: UIViewController?
private var activeImporter: CertImporter?
init(viewController: UIViewController?) {
self.viewController = viewController
}
func selectCertificate(promptText: ClientCertPrompt, completion: @escaping (Result<ClientCertData, any Error>) -> Void) {
let importer = CertImporter(promptText: promptText, completion: { [weak self] result in
self?.activeImporter = nil
completion(result.map { ClientCertData(data: FlutterStandardTypedData(bytes: $0.0), password: $0.1) })
}, viewController: viewController)
activeImporter = importer
importer.load()
}
func removeCertificate(completion: @escaping (Result<Void, any Error>) -> Void) {
let status = clearCerts()
if status == errSecSuccess || status == errSecItemNotFound {
return completion(.success(()))
}
completion(.failure(ImportError.keychainError(status)))
}
func addCertificate(clientData: ClientCertData, completion: @escaping (Result<Void, any Error>) -> Void) {
let status = importCert(clientData: clientData.data.data, password: clientData.password)
if status == errSecSuccess {
return completion(.success(()))
}
completion(.failure(ImportError.keychainError(status)))
}
}
private class CertImporter: NSObject, UIDocumentPickerDelegate {
private let promptText: ClientCertPrompt
private var completion: ((Result<(Data, String), Error>) -> Void)
private weak var viewController: UIViewController?
init(promptText: ClientCertPrompt, completion: (@escaping (Result<(Data, String), Error>) -> Void), viewController: UIViewController?) {
self.promptText = promptText
self.completion = completion
self.viewController = viewController
}
func load() {
guard let vc = viewController else { return completion(.failure(ImportError.noViewController)) }
let picker = UIDocumentPickerViewController(forOpeningContentTypes: [
UTType(filenameExtension: "p12")!,
UTType(filenameExtension: "pfx")!,
])
picker.delegate = self
picker.allowsMultipleSelection = false
vc.present(picker, animated: true)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let url = urls.first else {
return completion(.failure(ImportError.noFile))
}
Task { @MainActor in
do {
let data = try readSecurityScoped(url: url)
guard let password = await promptForPassword() else {
return completion(.failure(ImportError.cancelled))
}
let status = importCert(clientData: data, password: password)
if status != errSecSuccess {
return completion(.failure(ImportError.keychainError(status)))
}
await URLSessionManager.shared.session.flush()
self.completion(.success((data, password)))
} catch {
completion(.failure(error))
}
}
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
completion(.failure(ImportError.cancelled))
}
private func promptForPassword() async -> String? {
guard let vc = viewController else { return nil }
return await withCheckedContinuation { continuation in
let alert = UIAlertController(
title: promptText.title,
message: promptText.message,
preferredStyle: .alert
)
alert.addTextField { $0.isSecureTextEntry = true }
alert.addAction(UIAlertAction(title: promptText.cancel, style: .cancel) { _ in
continuation.resume(returning: nil)
})
alert.addAction(UIAlertAction(title: promptText.confirm, style: .default) { _ in
continuation.resume(returning: alert.textFields?.first?.text ?? "")
})
vc.present(alert, animated: true)
}
}
private func readSecurityScoped(url: URL) throws -> Data {
guard url.startAccessingSecurityScopedResource() else {
throw ImportError.noFile
}
defer { url.stopAccessingSecurityScopedResource() }
return try Data(contentsOf: url)
}
}
private func importCert(clientData: Data, password: String) -> OSStatus {
let options = [kSecImportExportPassphrase: password] as CFDictionary
var items: CFArray?
let status = SecPKCS12Import(clientData as CFData, options, &items)
guard status == errSecSuccess,
let array = items as? [[String: Any]],
let first = array.first,
let identity = first[kSecImportItemIdentity as String] else {
return status
}
clearCerts()
let addQuery: [String: Any] = [
kSecClass as String: kSecClassIdentity,
kSecValueRef as String: identity,
kSecAttrLabel as String: CLIENT_CERT_LABEL,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
]
return SecItemAdd(addQuery as CFDictionary, nil)
}
@discardableResult private func clearCerts() -> OSStatus {
let deleteQuery: [String: Any] = [
kSecClass as String: kSecClassIdentity,
kSecAttrLabel as String: CLIENT_CERT_LABEL,
]
return SecItemDelete(deleteQuery as CFDictionary)
}

View File

@@ -1,87 +0,0 @@
import Foundation
let CLIENT_CERT_LABEL = "app.alextran.immich.client_identity"
/// Manages a shared URLSession with SSL configuration support.
class URLSessionManager: NSObject {
static let shared = URLSessionManager()
let session: URLSession
private let configuration = {
let config = URLSessionConfiguration.default
let cacheDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
.first!
.appendingPathComponent("api", isDirectory: true)
try! FileManager.default.createDirectory(at: cacheDir, withIntermediateDirectories: true)
config.urlCache = URLCache(
memoryCapacity: 0,
diskCapacity: 1024 * 1024 * 1024,
directory: cacheDir
)
config.httpMaximumConnectionsPerHost = 64
config.timeoutIntervalForRequest = 60
config.timeoutIntervalForResource = 300
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "unknown"
config.httpAdditionalHeaders = ["User-Agent": "Immich_iOS_\(version)"]
return config
}()
private override init() {
session = URLSession(configuration: configuration, delegate: URLSessionManagerDelegate(), delegateQueue: nil)
super.init()
}
}
class URLSessionManagerDelegate: NSObject, URLSessionTaskDelegate {
func urlSession(
_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
handleChallenge(challenge, completionHandler: completionHandler)
}
func urlSession(
_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
handleChallenge(challenge, completionHandler: completionHandler)
}
func handleChallenge(
_ challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
switch challenge.protectionSpace.authenticationMethod {
case NSURLAuthenticationMethodClientCertificate: handleClientCertificate(completion: completionHandler)
default: completionHandler(.performDefaultHandling, nil)
}
}
private func handleClientCertificate(
completion: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
let query: [String: Any] = [
kSecClass as String: kSecClassIdentity,
kSecAttrLabel as String: CLIENT_CERT_LABEL,
kSecReturnRef as String: true,
]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
if status == errSecSuccess, let identity = item {
let credential = URLCredential(identity: identity as! SecIdentity,
certificates: nil,
persistence: .forSession)
return completion(.useCredential, credential)
}
completion(.performDefaultHandling, nil)
}
}

View File

@@ -1,7 +0,0 @@
import Foundation
enum ImageProcessing {
static let queue = DispatchQueue(label: "thumbnail.processing", qos: .userInitiated, attributes: .concurrent)
static let semaphore = DispatchSemaphore(value: ProcessInfo.processInfo.activeProcessorCount * 2)
static let cancelledResult = Result<[String: Int64]?, any Error>.success(nil)
}

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