From 3da750117f49c10685d27a8b69d180f052980adf Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 13 Jan 2025 19:30:34 -0600 Subject: [PATCH 01/18] refactor: migrate user repository to kysely (#15296) * refactor: migrate user repository to kysely * refactor: migrate user repository to kysely * refactor: migrate user repository to kysely * refactor: migrate user repository to kysely * fix: test * clean up * fix: metadata retrieval bug * use correct typeing for upsert metadata * pr feedback * pr feedback * fix: add deletedAt check * fix: get non deleted user by default * remove console.log * fix: stop kysely after command finishes * final clean up --------- Co-authored-by: Jason Rasmussen --- server/src/app.module.ts | 9 +- server/src/decorators.ts | 1 + server/src/entities/user.entity.ts | 9 + server/src/interfaces/database.interface.ts | 1 + server/src/interfaces/user.interface.ts | 16 +- server/src/queries/user.repository.sql | 365 ++++++++++-------- .../src/repositories/database.repository.ts | 8 +- server/src/repositories/user.repository.ts | 299 ++++++++------ server/src/services/album.service.spec.ts | 4 +- server/src/services/auth.service.spec.ts | 24 +- server/src/services/auth.service.ts | 2 +- server/src/services/base.service.ts | 6 +- server/src/services/cli.service.spec.ts | 2 +- server/src/services/cli.service.ts | 4 + .../src/services/user-admin.service.spec.ts | 14 +- server/src/services/user.service.spec.ts | 8 +- .../repositories/database.repository.mock.ts | 1 + web/package-lock.json | 2 +- 18 files changed, 455 insertions(+), 320 deletions(-) diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 9d96a0499ba26..d0422756b6e31 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -24,6 +24,7 @@ import { repositories } from 'src/repositories'; import { ConfigRepository } from 'src/repositories/config.repository'; import { teardownTelemetry } from 'src/repositories/telemetry.repository'; import { services } from 'src/services'; +import { CliService } from 'src/services/cli.service'; import { DatabaseService } from 'src/services/database.service'; const common = [...services, ...repositories]; @@ -106,4 +107,10 @@ export class MicroservicesModule extends BaseModule {} imports: [...imports], providers: [...common, ...commands, SchedulerRegistry], }) -export class ImmichAdminModule {} +export class ImmichAdminModule implements OnModuleDestroy { + constructor(private service: CliService) {} + + async onModuleDestroy() { + await this.service.cleanup(); + } +} diff --git a/server/src/decorators.ts b/server/src/decorators.ts index c2bbe19b28fd9..047b9ec4a7079 100644 --- a/server/src/decorators.ts +++ b/server/src/decorators.ts @@ -99,6 +99,7 @@ export const DummyValue = { BUFFER: Buffer.from('abcdefghi'), DATE: new Date(), TIME_BUCKET: '2024-01-01T00:00:00.000Z', + BOOLEAN: true, }; export const GENERATE_SQL_KEY = 'generate-sql-key'; diff --git a/server/src/entities/user.entity.ts b/server/src/entities/user.entity.ts index ea446be390844..3f5b470ce467f 100644 --- a/server/src/entities/user.entity.ts +++ b/server/src/entities/user.entity.ts @@ -1,3 +1,6 @@ +import { ExpressionBuilder } from 'kysely'; +import { jsonArrayFrom } from 'kysely/helpers/postgres'; +import { DB } from 'src/db'; import { AssetEntity } from 'src/entities/asset.entity'; import { TagEntity } from 'src/entities/tag.entity'; import { UserMetadataEntity } from 'src/entities/user-metadata.entity'; @@ -71,3 +74,9 @@ export class UserEntity { @Column({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' }) profileChangedAt!: Date; } + +export const withMetadata = (eb: ExpressionBuilder) => { + return jsonArrayFrom( + eb.selectFrom('user_metadata').selectAll('user_metadata').whereRef('users.id', '=', 'user_metadata.userId'), + ).as('metadata'); +}; diff --git a/server/src/interfaces/database.interface.ts b/server/src/interfaces/database.interface.ts index 5ad37efa71ee5..8cfc040271433 100644 --- a/server/src/interfaces/database.interface.ts +++ b/server/src/interfaces/database.interface.ts @@ -61,6 +61,7 @@ export const IDatabaseRepository = 'IDatabaseRepository'; export interface IDatabaseRepository { init(): void; reconnect(): Promise; + shutdown(): Promise; getExtensionVersion(extension: DatabaseExtension): Promise; getExtensionVersionRange(extension: VectorExtension): string; getPostgresVersion(): Promise; diff --git a/server/src/interfaces/user.interface.ts b/server/src/interfaces/user.interface.ts index 385a4d3d50e91..6ff3fc824aabe 100644 --- a/server/src/interfaces/user.interface.ts +++ b/server/src/interfaces/user.interface.ts @@ -1,3 +1,5 @@ +import { Insertable, Updateable } from 'kysely'; +import { Users } from 'src/db'; import { UserMetadata } from 'src/entities/user-metadata.entity'; import { UserEntity } from 'src/entities/user.entity'; @@ -23,17 +25,17 @@ export interface UserFindOptions { export const IUserRepository = 'IUserRepository'; export interface IUserRepository { - get(id: string, options: UserFindOptions): Promise; - getAdmin(): Promise; + get(id: string, options: UserFindOptions): Promise; + getAdmin(): Promise; hasAdmin(): Promise; - getByEmail(email: string, withPassword?: boolean): Promise; - getByStorageLabel(storageLabel: string): Promise; - getByOAuthId(oauthId: string): Promise; + getByEmail(email: string, withPassword?: boolean): Promise; + getByStorageLabel(storageLabel: string): Promise; + getByOAuthId(oauthId: string): Promise; getDeletedUsers(): Promise; getList(filter?: UserListFilter): Promise; getUserStats(): Promise; - create(user: Partial): Promise; - update(id: string, user: Partial): Promise; + create(user: Insertable): Promise; + update(id: string, user: Updateable): Promise; upsertMetadata(id: string, item: { key: T; value: UserMetadata[T] }): Promise; deleteMetadata(id: string, key: T): Promise; delete(user: UserEntity, hard?: boolean): Promise; diff --git a/server/src/queries/user.repository.sql b/server/src/queries/user.repository.sql index c35dc540cef42..7ae8003a0962b 100644 --- a/server/src/queries/user.repository.sql +++ b/server/src/queries/user.repository.sql @@ -1,195 +1,222 @@ -- NOTE: This file is auto generated by ./sql-generator +-- UserRepository.get +select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "user_metadata".* + from + "user_metadata" + where + "users"."id" = "user_metadata"."userId" + ) as agg + ) as "metadata" +from + "users" +where + "users"."id" = $1 + and "users"."deletedAt" is null + -- UserRepository.getAdmin -SELECT - "UserEntity"."id" AS "UserEntity_id", - "UserEntity"."name" AS "UserEntity_name", - "UserEntity"."isAdmin" AS "UserEntity_isAdmin", - "UserEntity"."email" AS "UserEntity_email", - "UserEntity"."storageLabel" AS "UserEntity_storageLabel", - "UserEntity"."oauthId" AS "UserEntity_oauthId", - "UserEntity"."profileImagePath" AS "UserEntity_profileImagePath", - "UserEntity"."shouldChangePassword" AS "UserEntity_shouldChangePassword", - "UserEntity"."createdAt" AS "UserEntity_createdAt", - "UserEntity"."deletedAt" AS "UserEntity_deletedAt", - "UserEntity"."status" AS "UserEntity_status", - "UserEntity"."updatedAt" AS "UserEntity_updatedAt", - "UserEntity"."quotaSizeInBytes" AS "UserEntity_quotaSizeInBytes", - "UserEntity"."quotaUsageInBytes" AS "UserEntity_quotaUsageInBytes", - "UserEntity"."profileChangedAt" AS "UserEntity_profileChangedAt" -FROM - "users" "UserEntity" -WHERE - ((("UserEntity"."isAdmin" = $1))) - AND ("UserEntity"."deletedAt" IS NULL) -LIMIT - 1 +select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt" +from + "users" +where + "users"."isAdmin" = $1 + and "users"."deletedAt" is null -- UserRepository.hasAdmin -SELECT - 1 AS "row_exists" -FROM - ( - SELECT - 1 AS dummy_column - ) "dummy_table" -WHERE - EXISTS ( - SELECT - 1 - FROM - "users" "UserEntity" - WHERE - ((("UserEntity"."isAdmin" = $1))) - AND ("UserEntity"."deletedAt" IS NULL) - ) -LIMIT - 1 +select + "users"."id" +from + "users" +where + "users"."isAdmin" = $1 + and "users"."deletedAt" is null -- UserRepository.getByEmail -SELECT - "user"."id" AS "user_id", - "user"."name" AS "user_name", - "user"."isAdmin" AS "user_isAdmin", - "user"."email" AS "user_email", - "user"."storageLabel" AS "user_storageLabel", - "user"."oauthId" AS "user_oauthId", - "user"."profileImagePath" AS "user_profileImagePath", - "user"."shouldChangePassword" AS "user_shouldChangePassword", - "user"."createdAt" AS "user_createdAt", - "user"."deletedAt" AS "user_deletedAt", - "user"."status" AS "user_status", - "user"."updatedAt" AS "user_updatedAt", - "user"."quotaSizeInBytes" AS "user_quotaSizeInBytes", - "user"."quotaUsageInBytes" AS "user_quotaUsageInBytes", - "user"."profileChangedAt" AS "user_profileChangedAt" -FROM - "users" "user" -WHERE - ("user"."email" = $1) - AND ("user"."deletedAt" IS NULL) +select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt" +from + "users" +where + "email" = $1 + and "users"."deletedAt" is null -- UserRepository.getByStorageLabel -SELECT - "UserEntity"."id" AS "UserEntity_id", - "UserEntity"."name" AS "UserEntity_name", - "UserEntity"."isAdmin" AS "UserEntity_isAdmin", - "UserEntity"."email" AS "UserEntity_email", - "UserEntity"."storageLabel" AS "UserEntity_storageLabel", - "UserEntity"."oauthId" AS "UserEntity_oauthId", - "UserEntity"."profileImagePath" AS "UserEntity_profileImagePath", - "UserEntity"."shouldChangePassword" AS "UserEntity_shouldChangePassword", - "UserEntity"."createdAt" AS "UserEntity_createdAt", - "UserEntity"."deletedAt" AS "UserEntity_deletedAt", - "UserEntity"."status" AS "UserEntity_status", - "UserEntity"."updatedAt" AS "UserEntity_updatedAt", - "UserEntity"."quotaSizeInBytes" AS "UserEntity_quotaSizeInBytes", - "UserEntity"."quotaUsageInBytes" AS "UserEntity_quotaUsageInBytes", - "UserEntity"."profileChangedAt" AS "UserEntity_profileChangedAt" -FROM - "users" "UserEntity" -WHERE - ((("UserEntity"."storageLabel" = $1))) - AND ("UserEntity"."deletedAt" IS NULL) -LIMIT - 1 +select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt" +from + "users" +where + "users"."storageLabel" = $1 + and "users"."deletedAt" is null -- UserRepository.getByOAuthId -SELECT - "UserEntity"."id" AS "UserEntity_id", - "UserEntity"."name" AS "UserEntity_name", - "UserEntity"."isAdmin" AS "UserEntity_isAdmin", - "UserEntity"."email" AS "UserEntity_email", - "UserEntity"."storageLabel" AS "UserEntity_storageLabel", - "UserEntity"."oauthId" AS "UserEntity_oauthId", - "UserEntity"."profileImagePath" AS "UserEntity_profileImagePath", - "UserEntity"."shouldChangePassword" AS "UserEntity_shouldChangePassword", - "UserEntity"."createdAt" AS "UserEntity_createdAt", - "UserEntity"."deletedAt" AS "UserEntity_deletedAt", - "UserEntity"."status" AS "UserEntity_status", - "UserEntity"."updatedAt" AS "UserEntity_updatedAt", - "UserEntity"."quotaSizeInBytes" AS "UserEntity_quotaSizeInBytes", - "UserEntity"."quotaUsageInBytes" AS "UserEntity_quotaUsageInBytes", - "UserEntity"."profileChangedAt" AS "UserEntity_profileChangedAt" -FROM - "users" "UserEntity" -WHERE - ((("UserEntity"."oauthId" = $1))) - AND ("UserEntity"."deletedAt" IS NULL) -LIMIT - 1 +select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt" +from + "users" +where + "users"."oauthId" = $1 + and "users"."deletedAt" is null -- UserRepository.getUserStats -SELECT - "users"."id" AS "userId", - "users"."name" AS "userName", - "users"."quotaSizeInBytes" AS "quotaSizeInBytes", - COUNT("assets"."id") FILTER ( - WHERE - "assets"."type" = 'IMAGE' - AND "assets"."isVisible" - ) AS "photos", - COUNT("assets"."id") FILTER ( - WHERE - "assets"."type" = 'VIDEO' - AND "assets"."isVisible" - ) AS "videos", - COALESCE( - SUM("exif"."fileSizeInByte") FILTER ( - WHERE - "assets"."libraryId" IS NULL +select + "users"."id" as "userId", + "users"."name" as "userName", + "users"."quotaSizeInBytes" as "quotaSizeInBytes", + count(*) filter ( + where + ( + "assets"."type" = $1 + and "assets"."isVisible" = $2 + ) + ) as "photos", + count(*) filter ( + where + ( + "assets"."type" = $3 + and "assets"."isVisible" = $4 + ) + ) as "videos", + coalesce( + sum("exif"."fileSizeInByte") filter ( + where + "assets"."libraryId" is null ), 0 - ) AS "usage", - COALESCE( - SUM("exif"."fileSizeInByte") FILTER ( - WHERE - "assets"."libraryId" IS NULL - AND "assets"."type" = 'IMAGE' + ) as "usage", + coalesce( + sum("exif"."fileSizeInByte") filter ( + where + ( + "assets"."libraryId" is null + and "assets"."type" = $5 + ) ), 0 - ) AS "usagePhotos", - COALESCE( - SUM("exif"."fileSizeInByte") FILTER ( - WHERE - "assets"."libraryId" IS NULL - AND "assets"."type" = 'VIDEO' + ) as "usagePhotos", + coalesce( + sum("exif"."fileSizeInByte") filter ( + where + ( + "assets"."libraryId" is null + and "assets"."type" = $6 + ) ), 0 - ) AS "usageVideos" -FROM - "users" "users" - LEFT JOIN "assets" "assets" ON "assets"."ownerId" = "users"."id" - AND ("assets"."deletedAt" IS NULL) - LEFT JOIN "exif" "exif" ON "exif"."assetId" = "assets"."id" -WHERE - "users"."deletedAt" IS NULL -GROUP BY + ) as "usageVideos" +from + "users" + left join "assets" on "assets"."ownerId" = "users"."id" + left join "exif" on "exif"."assetId" = "assets"."id" +where + "assets"."deletedAt" is null +group by "users"."id" -ORDER BY - "users"."createdAt" ASC +order by + "users"."createdAt" asc -- UserRepository.updateUsage -UPDATE "users" -SET - "quotaUsageInBytes" = "quotaUsageInBytes" + 50, - "updatedAt" = CURRENT_TIMESTAMP -WHERE - "id" = $1 +update "users" +set + "quotaUsageInBytes" = "quotaUsageInBytes" + $1, + "updatedAt" = $2 +where + "id" = $3::uuid + and "users"."deletedAt" is null -- UserRepository.syncUsage -UPDATE "users" -SET +update "users" +set "quotaUsageInBytes" = ( - SELECT - COALESCE(SUM(exif."fileSizeInByte"), 0) - FROM - "assets" "assets" - LEFT JOIN "exif" "exif" ON "exif"."assetId" = "assets"."id" - WHERE - "assets"."ownerId" = users.id - AND "assets"."libraryId" IS NULL + select + coalesce(sum("exif"."fileSizeInByte"), 0) as "usage" + from + "assets" + left join "exif" on "exif"."assetId" = "assets"."id" + where + "assets"."libraryId" is null + and "assets"."ownerId" = "users"."id" ), - "updatedAt" = CURRENT_TIMESTAMP -WHERE - users.id = $1 + "updatedAt" = $1 +where + "users"."deletedAt" is null + and "users"."id" = $2::uuid diff --git a/server/src/repositories/database.repository.ts b/server/src/repositories/database.repository.ts index 0eefce0cd24e9..7188678212276 100644 --- a/server/src/repositories/database.repository.ts +++ b/server/src/repositories/database.repository.ts @@ -1,7 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { InjectDataSource } from '@nestjs/typeorm'; import AsyncLock from 'async-lock'; -import { sql } from 'kysely'; +import { Kysely, sql } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; import semver from 'semver'; import { POSTGRES_VERSION_RANGE, VECTOR_VERSION_RANGE, VECTORS_VERSION_RANGE } from 'src/constants'; import { DB } from 'src/db'; @@ -27,6 +28,7 @@ export class DatabaseRepository implements IDatabaseRepository { private readonly asyncLock = new AsyncLock(); constructor( + @InjectKysely() private db: Kysely, @InjectDataSource() private dataSource: DataSource, @Inject(ILoggerRepository) private logger: ILoggerRepository, @Inject(IConfigRepository) configRepository: IConfigRepository, @@ -35,6 +37,10 @@ export class DatabaseRepository implements IDatabaseRepository { this.logger.setContext(DatabaseRepository.name); } + async shutdown() { + await this.db.destroy(); + } + init() { for (const metadata of this.dataSource.entityMetadatas) { const table = metadata.tableName as keyof DB; diff --git a/server/src/repositories/user.repository.ts b/server/src/repositories/user.repository.ts index a2e4375701a2a..e7c65b3f01775 100644 --- a/server/src/repositories/user.repository.ts +++ b/server/src/repositories/user.repository.ts @@ -1,127 +1,212 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { Insertable, Kysely, sql, Updateable } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB, UserMetadata as DbUserMetadata, Users } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; -import { AssetEntity } from 'src/entities/asset.entity'; -import { UserMetadata, UserMetadataEntity } from 'src/entities/user-metadata.entity'; -import { UserEntity } from 'src/entities/user.entity'; +import { UserMetadata } from 'src/entities/user-metadata.entity'; +import { UserEntity, withMetadata } from 'src/entities/user.entity'; import { IUserRepository, UserFindOptions, UserListFilter, UserStatsQueryResponse, } from 'src/interfaces/user.interface'; -import { IsNull, Not, Repository } from 'typeorm'; +import { asUuid } from 'src/utils/database'; + +const columns = [ + 'id', + 'email', + 'createdAt', + 'profileImagePath', + 'isAdmin', + 'shouldChangePassword', + 'deletedAt', + 'oauthId', + 'updatedAt', + 'storageLabel', + 'name', + 'quotaSizeInBytes', + 'quotaUsageInBytes', + 'status', + 'profileChangedAt', +] as const; + +type Upsert = Insertable; @Injectable() export class UserRepository implements IUserRepository { - constructor( - @InjectRepository(AssetEntity) private assetRepository: Repository, - @InjectRepository(UserEntity) private userRepository: Repository, - @InjectRepository(UserMetadataEntity) private metadataRepository: Repository, - ) {} + constructor(@InjectKysely() private db: Kysely) {} - async get(userId: string, options: UserFindOptions): Promise { + @GenerateSql({ params: [DummyValue.UUID, DummyValue.BOOLEAN] }) + get(userId: string, options: UserFindOptions): Promise { options = options || {}; - return this.userRepository.findOne({ - where: { id: userId }, - withDeleted: options.withDeleted, - relations: { - metadata: true, - }, - }); + + return this.db + .selectFrom('users') + .select(columns) + .select(withMetadata) + .where('users.id', '=', userId) + .$if(!options.withDeleted, (eb) => eb.where('users.deletedAt', 'is', null)) + .executeTakeFirst() as Promise; } @GenerateSql() - async getAdmin(): Promise { - return this.userRepository.findOne({ where: { isAdmin: true } }); + getAdmin(): Promise { + return this.db + .selectFrom('users') + .select(columns) + .where('users.isAdmin', '=', true) + .where('users.deletedAt', 'is', null) + .executeTakeFirst() as Promise; } @GenerateSql() async hasAdmin(): Promise { - return this.userRepository.exists({ where: { isAdmin: true } }); + const admin = await this.db + .selectFrom('users') + .select('users.id') + .where('users.isAdmin', '=', true) + .where('users.deletedAt', 'is', null) + .executeTakeFirst(); + + return !!admin; } @GenerateSql({ params: [DummyValue.EMAIL] }) - async getByEmail(email: string, withPassword?: boolean): Promise { - const builder = this.userRepository.createQueryBuilder('user').where({ email }); - - if (withPassword) { - builder.addSelect('user.password'); - } - - return builder.getOne(); + getByEmail(email: string, withPassword?: boolean): Promise { + return this.db + .selectFrom('users') + .select(columns) + .$if(!!withPassword, (eb) => eb.select('password')) + .where('email', '=', email) + .where('users.deletedAt', 'is', null) + .executeTakeFirst() as Promise; } @GenerateSql({ params: [DummyValue.STRING] }) - async getByStorageLabel(storageLabel: string): Promise { - return this.userRepository.findOne({ where: { storageLabel } }); + getByStorageLabel(storageLabel: string): Promise { + return this.db + .selectFrom('users') + .select(columns) + .where('users.storageLabel', '=', storageLabel) + .where('users.deletedAt', 'is', null) + .executeTakeFirst() as Promise; } @GenerateSql({ params: [DummyValue.STRING] }) - async getByOAuthId(oauthId: string): Promise { - return this.userRepository.findOne({ where: { oauthId } }); - } - - async getDeletedUsers(): Promise { - return this.userRepository.find({ withDeleted: true, where: { deletedAt: Not(IsNull()) } }); - } - - async getList({ withDeleted }: UserListFilter = {}): Promise { - return this.userRepository.find({ - withDeleted, - order: { - createdAt: 'DESC', - }, - relations: { - metadata: true, - }, - }); - } - - create(user: Partial): Promise { - return this.save(user); - } - - // TODO change to (user: Partial) - update(id: string, user: Partial): Promise { - return this.save({ ...user, id }); + getByOAuthId(oauthId: string): Promise { + return this.db + .selectFrom('users') + .select(columns) + .where('users.oauthId', '=', oauthId) + .where('users.deletedAt', 'is', null) + .executeTakeFirst() as Promise; + } + + getDeletedUsers(): Promise { + return this.db + .selectFrom('users') + .select(columns) + .where('users.deletedAt', 'is not', null) + .execute() as unknown as Promise; + } + + getList({ withDeleted }: UserListFilter = {}): Promise { + return this.db + .selectFrom('users') + .select(columns) + .select(withMetadata) + .$if(!withDeleted, (eb) => eb.where('users.deletedAt', 'is', null)) + .orderBy('createdAt', 'desc') + .execute() as unknown as Promise; + } + + async create(dto: Insertable): Promise { + return this.db + .insertInto('users') + .values(dto) + .returning(columns) + .executeTakeFirst() as unknown as Promise; + } + + update(id: string, dto: Updateable): Promise { + return this.db + .updateTable('users') + .set(dto) + .where('users.id', '=', asUuid(id)) + .where('users.deletedAt', 'is', null) + .returning(columns) + .returning(withMetadata) + .executeTakeFirst() as unknown as Promise; } async upsertMetadata(id: string, { key, value }: { key: T; value: UserMetadata[T] }) { - await this.metadataRepository.upsert({ userId: id, key, value }, { conflictPaths: { userId: true, key: true } }); + await this.db + .insertInto('user_metadata') + .values({ userId: id, key, value } as Upsert) + .onConflict((oc) => + oc.columns(['userId', 'key']).doUpdateSet({ + key, + value, + } as Upsert), + ) + .execute(); } async deleteMetadata(id: string, key: T) { - await this.metadataRepository.delete({ userId: id, key }); + await this.db.deleteFrom('user_metadata').where('userId', '=', id).where('key', '=', key).execute(); } - async delete(user: UserEntity, hard?: boolean): Promise { - return hard ? this.userRepository.remove(user) : this.userRepository.softRemove(user); + delete(user: UserEntity, hard?: boolean): Promise { + return hard + ? (this.db.deleteFrom('users').where('id', '=', user.id).execute() as unknown as Promise) + : (this.db + .updateTable('users') + .set({ deletedAt: new Date() }) + .where('id', '=', user.id) + .execute() as unknown as Promise); } @GenerateSql() async getUserStats(): Promise { - const stats = await this.userRepository - .createQueryBuilder('users') - .select('users.id', 'userId') - .addSelect('users.name', 'userName') - .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'IMAGE' AND assets.isVisible)`, 'photos') - .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'VIDEO' AND assets.isVisible)`, 'videos') - .addSelect('COALESCE(SUM(exif.fileSizeInByte) FILTER (WHERE assets.libraryId IS NULL), 0)', 'usage') - .addSelect( - `COALESCE(SUM(exif.fileSizeInByte) FILTER (WHERE assets.libraryId IS NULL AND assets.type = 'IMAGE'), 0)`, - 'usagePhotos', - ) - .addSelect( - `COALESCE(SUM(exif.fileSizeInByte) FILTER (WHERE assets.libraryId IS NULL AND assets.type = 'VIDEO'), 0)`, - 'usageVideos', - ) - .addSelect('users.quotaSizeInBytes', 'quotaSizeInBytes') - .leftJoin('users.assets', 'assets') - .leftJoin('assets.exifInfo', 'exif') + const stats = (await this.db + .selectFrom('users') + .leftJoin('assets', 'assets.ownerId', 'users.id') + .leftJoin('exif', 'exif.assetId', 'assets.id') + .select(['users.id as userId', 'users.name as userName', 'users.quotaSizeInBytes as quotaSizeInBytes']) + .select((eb) => [ + eb.fn + .countAll() + .filterWhere((eb) => eb.and([eb('assets.type', '=', 'IMAGE'), eb('assets.isVisible', '=', true)])) + .as('photos'), + eb.fn + .countAll() + .filterWhere((eb) => eb.and([eb('assets.type', '=', 'VIDEO'), eb('assets.isVisible', '=', true)])) + .as('videos'), + eb.fn + .coalesce(eb.fn.sum('exif.fileSizeInByte').filterWhere('assets.libraryId', 'is', null), eb.lit(0)) + .as('usage'), + eb.fn + .coalesce( + eb.fn + .sum('exif.fileSizeInByte') + .filterWhere((eb) => eb.and([eb('assets.libraryId', 'is', null), eb('assets.type', '=', 'IMAGE')])), + eb.lit(0), + ) + .as('usagePhotos'), + eb.fn + .coalesce( + eb.fn + .sum('exif.fileSizeInByte') + .filterWhere((eb) => eb.and([eb('assets.libraryId', 'is', null), eb('assets.type', '=', 'VIDEO')])), + eb.lit(0), + ) + .as('usageVideos'), + ]) + .where('assets.deletedAt', 'is', null) .groupBy('users.id') - .orderBy('users.createdAt', 'ASC') - .getRawMany(); + .orderBy('users.createdAt', 'asc') + .execute()) as UserStatsQueryResponse[]; for (const stat of stats) { stat.photos = Number(stat.photos); @@ -137,41 +222,31 @@ export class UserRepository implements IUserRepository { @GenerateSql({ params: [DummyValue.UUID, DummyValue.NUMBER] }) async updateUsage(id: string, delta: number): Promise { - await this.userRepository.increment({ id }, 'quotaUsageInBytes', delta); + await this.db + .updateTable('users') + .set({ quotaUsageInBytes: sql`"quotaUsageInBytes" + ${delta}`, updatedAt: new Date() }) + .where('id', '=', asUuid(id)) + .where('users.deletedAt', 'is', null) + .execute(); } @GenerateSql({ params: [DummyValue.UUID] }) async syncUsage(id?: string) { - // we can't use parameters with getQuery, hence the template string - const subQuery = this.assetRepository - .createQueryBuilder('assets') - .select('COALESCE(SUM(exif."fileSizeInByte"), 0)') - .leftJoin('assets.exifInfo', 'exif') - .where('assets.ownerId = users.id') - .andWhere(`assets.libraryId IS NULL`) - .withDeleted(); - - const query = this.userRepository - .createQueryBuilder('users') - .leftJoin('users.assets', 'assets') - .update() - .set({ quotaUsageInBytes: () => `(${subQuery.getQuery()})` }); - - if (id) { - query.where('users.id = :id', { id }); - } + const query = this.db + .updateTable('users') + .set({ + quotaUsageInBytes: (eb) => + eb + .selectFrom('assets') + .leftJoin('exif', 'exif.assetId', 'assets.id') + .select((eb) => eb.fn.coalesce(eb.fn.sum('exif.fileSizeInByte'), eb.lit(0)).as('usage')) + .where('assets.libraryId', 'is', null) + .where('assets.ownerId', '=', eb.ref('users.id')), + updatedAt: new Date(), + }) + .where('users.deletedAt', 'is', null) + .$if(id != undefined, (eb) => eb.where('users.id', '=', asUuid(id!))); await query.execute(); } - - private async save(user: Partial) { - const { id } = await this.userRepository.save(user); - return this.userRepository.findOneOrFail({ - where: { id }, - withDeleted: true, - relations: { - metadata: true, - }, - }); - } } diff --git a/server/src/services/album.service.spec.ts b/server/src/services/album.service.spec.ts index 12c93ee1273ed..bb1aac8e6e48b 100644 --- a/server/src/services/album.service.spec.ts +++ b/server/src/services/album.service.spec.ts @@ -153,7 +153,7 @@ describe(AlbumService.name, () => { }); it('should require valid userIds', async () => { - userMock.get.mockResolvedValue(null); + userMock.get.mockResolvedValue(void 0); await expect( sut.create(authStub.admin, { albumName: 'Empty album', @@ -299,7 +299,7 @@ describe(AlbumService.name, () => { it('should throw an error if the userId does not exist', async () => { accessMock.album.checkOwnerAccess.mockResolvedValue(new Set([albumStub.sharedWithAdmin.id])); albumMock.getById.mockResolvedValue(albumStub.sharedWithAdmin); - userMock.get.mockResolvedValue(null); + userMock.get.mockResolvedValue(void 0); await expect( sut.addUsers(authStub.user1, albumStub.sharedWithAdmin.id, { albumUsers: [{ userId: 'user-3' }] }), ).rejects.toBeInstanceOf(BadRequestException); diff --git a/server/src/services/auth.service.spec.ts b/server/src/services/auth.service.spec.ts index 06035b03a2937..6494a735b191f 100644 --- a/server/src/services/auth.service.spec.ts +++ b/server/src/services/auth.service.spec.ts @@ -96,7 +96,7 @@ describe('AuthService', () => { }); it('should check the user exists', async () => { - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); await expect(sut.login(fixtures.login, loginDetails)).rejects.toBeInstanceOf(UnauthorizedException); expect(userMock.getByEmail).toHaveBeenCalledTimes(1); }); @@ -144,7 +144,7 @@ describe('AuthService', () => { const auth = { user: { email: 'test@imimch.com' } } as AuthDto; const dto = { password: 'old-password', newPassword: 'new-password' }; - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); await expect(sut.changePassword(auth, dto)).rejects.toBeInstanceOf(UnauthorizedException); }); @@ -227,7 +227,7 @@ describe('AuthService', () => { }); it('should sign up the admin', async () => { - userMock.getAdmin.mockResolvedValue(null); + userMock.getAdmin.mockResolvedValue(void 0); userMock.create.mockResolvedValue({ ...dto, id: 'admin', @@ -309,7 +309,7 @@ describe('AuthService', () => { it('should not accept a key without a user', async () => { sharedLinkMock.getByKey.mockResolvedValue(sharedLinkStub.expired); - userMock.get.mockResolvedValue(null); + userMock.get.mockResolvedValue(void 0); await expect( sut.authenticate({ headers: { 'x-immich-share-key': 'key' }, @@ -473,7 +473,7 @@ describe('AuthService', () => { it('should not allow auto registering', async () => { systemMock.get.mockResolvedValue(systemConfigStub.oauthEnabled); - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).rejects.toBeInstanceOf( BadRequestException, ); @@ -510,7 +510,7 @@ describe('AuthService', () => { it('should allow auto registering by default', async () => { systemMock.get.mockResolvedValue(systemConfigStub.enabled); - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); userMock.getAdmin.mockResolvedValue(userStub.user1); userMock.create.mockResolvedValue(userStub.user1); sessionMock.create.mockResolvedValue(sessionStub.valid); @@ -525,7 +525,7 @@ describe('AuthService', () => { it('should throw an error if user should be auto registered but the email claim does not exist', async () => { systemMock.get.mockResolvedValue(systemConfigStub.enabled); - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); userMock.getAdmin.mockResolvedValue(userStub.user1); userMock.create.mockResolvedValue(userStub.user1); sessionMock.create.mockResolvedValue(sessionStub.valid); @@ -559,7 +559,7 @@ describe('AuthService', () => { it('should use the default quota', async () => { systemMock.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota); - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); userMock.getAdmin.mockResolvedValue(userStub.user1); userMock.create.mockResolvedValue(userStub.user1); @@ -572,7 +572,7 @@ describe('AuthService', () => { it('should ignore an invalid storage quota', async () => { systemMock.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota); - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); userMock.getAdmin.mockResolvedValue(userStub.user1); userMock.create.mockResolvedValue(userStub.user1); oauthMock.getProfile.mockResolvedValue({ sub, email, immich_quota: 'abc' }); @@ -586,7 +586,7 @@ describe('AuthService', () => { it('should ignore a negative quota', async () => { systemMock.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota); - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); userMock.getAdmin.mockResolvedValue(userStub.user1); userMock.create.mockResolvedValue(userStub.user1); oauthMock.getProfile.mockResolvedValue({ sub, email, immich_quota: -5 }); @@ -600,7 +600,7 @@ describe('AuthService', () => { it('should not set quota for 0 quota', async () => { systemMock.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota); - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); userMock.getAdmin.mockResolvedValue(userStub.user1); userMock.create.mockResolvedValue(userStub.user1); oauthMock.getProfile.mockResolvedValue({ sub, email, immich_quota: 0 }); @@ -620,7 +620,7 @@ describe('AuthService', () => { it('should use a valid storage quota', async () => { systemMock.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota); - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); userMock.getAdmin.mockResolvedValue(userStub.user1); userMock.create.mockResolvedValue(userStub.user1); oauthMock.getProfile.mockResolvedValue({ sub, email, immich_quota: 5 }); diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index d6154976f9ffe..29b73954650cf 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -65,7 +65,7 @@ export class AuthService extends BaseService { if (user) { const isAuthenticated = this.validatePassword(dto.password, user); if (!isAuthenticated) { - user = null; + user = undefined; } } diff --git a/server/src/services/base.service.ts b/server/src/services/base.service.ts index 3630d69c1804f..82852c27e2400 100644 --- a/server/src/services/base.service.ts +++ b/server/src/services/base.service.ts @@ -1,8 +1,10 @@ import { BadRequestException, Inject } from '@nestjs/common'; +import { Insertable } from 'kysely'; import sanitize from 'sanitize-filename'; import { SystemConfig } from 'src/config'; import { SALT_ROUNDS } from 'src/constants'; import { StorageCore } from 'src/cores/storage.core'; +import { Users } from 'src/db'; import { UserEntity } from 'src/entities/user.entity'; import { IAccessRepository } from 'src/interfaces/access.interface'; import { IActivityRepository } from 'src/interfaces/activity.interface'; @@ -131,7 +133,7 @@ export class BaseService { return checkAccess(this.accessRepository, request); } - async createUser(dto: Partial & { email: string }): Promise { + async createUser(dto: Insertable & { email: string }): Promise { const user = await this.userRepository.getByEmail(dto.email); if (user) { throw new BadRequestException('User exists'); @@ -144,7 +146,7 @@ export class BaseService { } } - const payload: Partial = { ...dto }; + const payload: Insertable = { ...dto }; if (payload.password) { payload.password = await this.cryptoRepository.hashBcrypt(payload.password, SALT_ROUNDS); } diff --git a/server/src/services/cli.service.spec.ts b/server/src/services/cli.service.spec.ts index ef520070eaeb5..149b030e502a6 100644 --- a/server/src/services/cli.service.spec.ts +++ b/server/src/services/cli.service.spec.ts @@ -25,7 +25,7 @@ describe(CliService.name, () => { describe('resetAdminPassword', () => { it('should only work when there is an admin account', async () => { - userMock.getAdmin.mockResolvedValue(null); + userMock.getAdmin.mockResolvedValue(void 0); const ask = vitest.fn().mockResolvedValue('new-password'); await expect(sut.resetAdminPassword(ask)).rejects.toThrowError('Admin account does not exist'); diff --git a/server/src/services/cli.service.ts b/server/src/services/cli.service.ts index 18a79108c4468..87e004845d66e 100644 --- a/server/src/services/cli.service.ts +++ b/server/src/services/cli.service.ts @@ -48,4 +48,8 @@ export class CliService extends BaseService { config.oauth.enabled = true; await this.updateConfig(config); } + + cleanup() { + return this.databaseRepository.shutdown(); + } } diff --git a/server/src/services/user-admin.service.spec.ts b/server/src/services/user-admin.service.spec.ts index 70999332dc26a..6d2bc31cb7022 100644 --- a/server/src/services/user-admin.service.spec.ts +++ b/server/src/services/user-admin.service.spec.ts @@ -19,13 +19,13 @@ describe(UserAdminService.name, () => { ({ sut, jobMock, userMock } = newTestService(UserAdminService)); userMock.get.mockImplementation((userId) => - Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? null), + Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? undefined), ); }); describe('create', () => { it('should not create a user if there is no local admin account', async () => { - userMock.getAdmin.mockResolvedValueOnce(null); + userMock.getAdmin.mockResolvedValueOnce(void 0); await expect( sut.create({ @@ -66,8 +66,8 @@ describe(UserAdminService.name, () => { email: 'immich@test.com', storageLabel: 'storage_label', }; - userMock.getByEmail.mockResolvedValue(null); - userMock.getByStorageLabel.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); + userMock.getByStorageLabel.mockResolvedValue(void 0); userMock.update.mockResolvedValue(userStub.user1); await sut.update(authStub.user1, userStub.user1.id, update); @@ -108,7 +108,7 @@ describe(UserAdminService.name, () => { }); it('update user information should throw error if user not found', async () => { - userMock.get.mockResolvedValueOnce(null); + userMock.get.mockResolvedValueOnce(void 0); await expect( sut.update(authStub.admin, userStub.user1.id, { shouldChangePassword: true }), @@ -118,7 +118,7 @@ describe(UserAdminService.name, () => { describe('delete', () => { it('should throw error if user could not be found', async () => { - userMock.get.mockResolvedValue(null); + userMock.get.mockResolvedValue(void 0); await expect(sut.delete(authStub.admin, userStub.admin.id, {})).rejects.toThrowError(BadRequestException); expect(userMock.delete).not.toHaveBeenCalled(); @@ -166,7 +166,7 @@ describe(UserAdminService.name, () => { describe('restore', () => { it('should throw error if user could not be found', async () => { - userMock.get.mockResolvedValue(null); + userMock.get.mockResolvedValue(void 0); await expect(sut.restore(authStub.admin, userStub.admin.id)).rejects.toThrowError(BadRequestException); expect(userMock.update).not.toHaveBeenCalled(); }); diff --git a/server/src/services/user.service.spec.ts b/server/src/services/user.service.spec.ts index 08b663046bc31..cb7c2f08ada37 100644 --- a/server/src/services/user.service.spec.ts +++ b/server/src/services/user.service.spec.ts @@ -33,7 +33,7 @@ describe(UserService.name, () => { ({ sut, albumMock, jobMock, storageMock, systemMock, userMock } = newTestService(UserService)); userMock.get.mockImplementation((userId) => - Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? null), + Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? undefined), ); }); @@ -81,7 +81,7 @@ describe(UserService.name, () => { }); it('should throw an error if a user is not found', async () => { - userMock.get.mockResolvedValue(null); + userMock.get.mockResolvedValue(void 0); await expect(sut.get(authStub.admin.user.id)).rejects.toBeInstanceOf(BadRequestException); expect(userMock.get).toHaveBeenCalledWith(authStub.admin.user.id, { withDeleted: false }); }); @@ -100,7 +100,7 @@ describe(UserService.name, () => { describe('createProfileImage', () => { it('should throw an error if the user does not exist', async () => { const file = { path: '/profile/path' } as Express.Multer.File; - userMock.get.mockResolvedValue(null); + userMock.get.mockResolvedValue(void 0); userMock.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path }); await expect(sut.createProfileImage(authStub.admin, file)).rejects.toThrowError(BadRequestException); @@ -155,7 +155,7 @@ describe(UserService.name, () => { describe('getUserProfileImage', () => { it('should throw an error if the user does not exist', async () => { - userMock.get.mockResolvedValue(null); + userMock.get.mockResolvedValue(void 0); await expect(sut.getProfileImage(userStub.admin.id)).rejects.toBeInstanceOf(BadRequestException); diff --git a/server/test/repositories/database.repository.mock.ts b/server/test/repositories/database.repository.mock.ts index bfb931105a326..c135772518681 100644 --- a/server/test/repositories/database.repository.mock.ts +++ b/server/test/repositories/database.repository.mock.ts @@ -4,6 +4,7 @@ import { Mocked, vitest } from 'vitest'; export const newDatabaseRepositoryMock = (): Mocked => { return { init: vitest.fn(), + shutdown: vitest.fn(), reconnect: vitest.fn(), getExtensionVersion: vitest.fn(), getExtensionVersionRange: vitest.fn(), diff --git a/web/package-lock.json b/web/package-lock.json index b25947dd3dc2f..9450b76834318 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -80,7 +80,7 @@ "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^22.10.2", + "@types/node": "^22.10.5", "typescript": "^5.3.3" } }, From 36eef9807b47b9ba64343cc825c4d77172e2c1b3 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Mon, 13 Jan 2025 20:38:11 -0500 Subject: [PATCH 02/18] fix: version history sql (#15321) --- server/src/queries/version.history.repository.sql | 8 ++++++++ server/src/repositories/version-history.repository.ts | 10 +++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/server/src/queries/version.history.repository.sql b/server/src/queries/version.history.repository.sql index 2e898cac31f86..a9805e8c2530e 100644 --- a/server/src/queries/version.history.repository.sql +++ b/server/src/queries/version.history.repository.sql @@ -15,3 +15,11 @@ from "version_history" order by "createdAt" desc + +-- VersionHistoryRepository.create +insert into + "version_history" ("version") +values + ($1) +returning + * diff --git a/server/src/repositories/version-history.repository.ts b/server/src/repositories/version-history.repository.ts index a5016873508cd..e6ec8edcf4406 100644 --- a/server/src/repositories/version-history.repository.ts +++ b/server/src/repositories/version-history.repository.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { Kysely } from 'kysely'; +import { Insertable, Kysely } from 'kysely'; import { InjectKysely } from 'nestjs-kysely'; -import { DB } from 'src/db'; -import { DummyValue, GenerateSql } from 'src/decorators'; +import { DB, VersionHistory } from 'src/db'; +import { GenerateSql } from 'src/decorators'; import { VersionHistoryEntity } from 'src/entities/version-history.entity'; import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface'; @@ -20,8 +20,8 @@ export class VersionHistoryRepository implements IVersionHistoryRepository { return this.db.selectFrom('version_history').selectAll().orderBy('createdAt', 'desc').executeTakeFirst(); } - @GenerateSql({ params: [DummyValue.STRING] }) - create(version: Omit): Promise { + @GenerateSql({ params: [{ version: 'v1.123.0' }] }) + create(version: Insertable): Promise { return this.db.insertInto('version_history').values(version).returningAll().executeTakeFirstOrThrow(); } } From 79726acc7232e539f780f633b83f8c002a39b01d Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 13 Jan 2025 19:45:52 -0600 Subject: [PATCH 03/18] refactor: migrate sessions repository to kysely (#15268) * wip: search * wip: getByToken * wip: getByToken * wip: getByUserId * wip: create/update/delete * remove unused code * clean up and pr feedback * fix: test * fix: e2e test * pr feedback --- e2e/src/api/specs/user.e2e-spec.ts | 4 + server/src/entities/session.entity.ts | 36 +++++ server/src/interfaces/session.interface.ts | 8 +- server/src/queries/session.repository.sql | 147 +++++++++++------- server/src/repositories/session.repository.ts | 78 ++++++---- server/src/services/auth.service.spec.ts | 4 +- server/src/services/auth.service.ts | 4 +- 7 files changed, 185 insertions(+), 96 deletions(-) diff --git a/e2e/src/api/specs/user.e2e-spec.ts b/e2e/src/api/specs/user.e2e-spec.ts index 1964dc6793642..9cffa5d754d1f 100644 --- a/e2e/src/api/specs/user.e2e-spec.ts +++ b/e2e/src/api/specs/user.e2e-spec.ts @@ -129,6 +129,8 @@ describe('/users', () => { expect(body).toEqual({ ...before, updatedAt: expect.any(String), + profileChangedAt: expect.any(String), + createdAt: expect.any(String), name: 'Name', }); }); @@ -177,6 +179,8 @@ describe('/users', () => { ...before, email: 'non-admin@immich.cloud', updatedAt: expect.anything(), + createdAt: expect.anything(), + profileChangedAt: expect.anything(), }); }); }); diff --git a/server/src/entities/session.entity.ts b/server/src/entities/session.entity.ts index 1cc9ad98572ab..e21c6d52ba469 100644 --- a/server/src/entities/session.entity.ts +++ b/server/src/entities/session.entity.ts @@ -1,3 +1,5 @@ +import { ExpressionBuilder } from 'kysely'; +import { DB } from 'src/db'; import { UserEntity } from 'src/entities/user.entity'; import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; @@ -27,3 +29,37 @@ export class SessionEntity { @Column({ default: '' }) deviceOS!: string; } + +const userColumns = [ + 'id', + 'email', + 'createdAt', + 'profileImagePath', + 'isAdmin', + 'shouldChangePassword', + 'deletedAt', + 'oauthId', + 'updatedAt', + 'storageLabel', + 'name', + 'quotaSizeInBytes', + 'quotaUsageInBytes', + 'status', + 'profileChangedAt', +] as const; + +export const withUser = (eb: ExpressionBuilder) => { + return eb + .selectFrom('users') + .select(userColumns) + .select((eb) => + eb + .selectFrom('user_metadata') + .whereRef('users.id', '=', 'user_metadata.userId') + .select((eb) => eb.fn('array_agg', [eb.table('user_metadata')]).as('metadata')) + .as('metadata'), + ) + .whereRef('users.id', '=', 'sessions.userId') + .where('users.deletedAt', 'is', null) + .as('user'); +}; diff --git a/server/src/interfaces/session.interface.ts b/server/src/interfaces/session.interface.ts index 33b48045a2376..8d695fbfc29c0 100644 --- a/server/src/interfaces/session.interface.ts +++ b/server/src/interfaces/session.interface.ts @@ -1,3 +1,5 @@ +import { Insertable, Updateable } from 'kysely'; +import { Sessions } from 'src/db'; import { SessionEntity } from 'src/entities/session.entity'; export const ISessionRepository = 'ISessionRepository'; @@ -7,9 +9,9 @@ export type SessionSearchOptions = { updatedBefore: Date }; export interface ISessionRepository { search(options: SessionSearchOptions): Promise; - create>(dto: T): Promise; - update>(dto: T): Promise; + create(dto: Insertable): Promise; + update(id: string, dto: Updateable): Promise; delete(id: string): Promise; - getByToken(token: string): Promise; + getByToken(token: string): Promise; getByUserId(userId: string): Promise; } diff --git a/server/src/queries/session.repository.sql b/server/src/queries/session.repository.sql index 2f0613b4d0398..b928195e72009 100644 --- a/server/src/queries/session.repository.sql +++ b/server/src/queries/session.repository.sql @@ -1,64 +1,97 @@ -- NOTE: This file is auto generated by ./sql-generator -- SessionRepository.search -SELECT - "SessionEntity"."id" AS "SessionEntity_id", - "SessionEntity"."userId" AS "SessionEntity_userId", - "SessionEntity"."createdAt" AS "SessionEntity_createdAt", - "SessionEntity"."updatedAt" AS "SessionEntity_updatedAt", - "SessionEntity"."deviceType" AS "SessionEntity_deviceType", - "SessionEntity"."deviceOS" AS "SessionEntity_deviceOS" -FROM - "sessions" "SessionEntity" -WHERE - (("SessionEntity"."updatedAt" <= $1)) +select + * +from + "sessions" +where + "sessions"."updatedAt" <= $1 -- SessionRepository.getByToken -SELECT DISTINCT - "distinctAlias"."SessionEntity_id" AS "ids_SessionEntity_id" -FROM - ( - SELECT - "SessionEntity"."id" AS "SessionEntity_id", - "SessionEntity"."userId" AS "SessionEntity_userId", - "SessionEntity"."createdAt" AS "SessionEntity_createdAt", - "SessionEntity"."updatedAt" AS "SessionEntity_updatedAt", - "SessionEntity"."deviceType" AS "SessionEntity_deviceType", - "SessionEntity"."deviceOS" AS "SessionEntity_deviceOS", - "SessionEntity__SessionEntity_user"."id" AS "SessionEntity__SessionEntity_user_id", - "SessionEntity__SessionEntity_user"."name" AS "SessionEntity__SessionEntity_user_name", - "SessionEntity__SessionEntity_user"."isAdmin" AS "SessionEntity__SessionEntity_user_isAdmin", - "SessionEntity__SessionEntity_user"."email" AS "SessionEntity__SessionEntity_user_email", - "SessionEntity__SessionEntity_user"."storageLabel" AS "SessionEntity__SessionEntity_user_storageLabel", - "SessionEntity__SessionEntity_user"."oauthId" AS "SessionEntity__SessionEntity_user_oauthId", - "SessionEntity__SessionEntity_user"."profileImagePath" AS "SessionEntity__SessionEntity_user_profileImagePath", - "SessionEntity__SessionEntity_user"."shouldChangePassword" AS "SessionEntity__SessionEntity_user_shouldChangePassword", - "SessionEntity__SessionEntity_user"."createdAt" AS "SessionEntity__SessionEntity_user_createdAt", - "SessionEntity__SessionEntity_user"."deletedAt" AS "SessionEntity__SessionEntity_user_deletedAt", - "SessionEntity__SessionEntity_user"."status" AS "SessionEntity__SessionEntity_user_status", - "SessionEntity__SessionEntity_user"."updatedAt" AS "SessionEntity__SessionEntity_user_updatedAt", - "SessionEntity__SessionEntity_user"."quotaSizeInBytes" AS "SessionEntity__SessionEntity_user_quotaSizeInBytes", - "SessionEntity__SessionEntity_user"."quotaUsageInBytes" AS "SessionEntity__SessionEntity_user_quotaUsageInBytes", - "SessionEntity__SessionEntity_user"."profileChangedAt" AS "SessionEntity__SessionEntity_user_profileChangedAt", - "469e6aa7ff79eff78f8441f91ba15bb07d3634dd"."userId" AS "469e6aa7ff79eff78f8441f91ba15bb07d3634dd_userId", - "469e6aa7ff79eff78f8441f91ba15bb07d3634dd"."key" AS "469e6aa7ff79eff78f8441f91ba15bb07d3634dd_key", - "469e6aa7ff79eff78f8441f91ba15bb07d3634dd"."value" AS "469e6aa7ff79eff78f8441f91ba15bb07d3634dd_value" - FROM - "sessions" "SessionEntity" - LEFT JOIN "users" "SessionEntity__SessionEntity_user" ON "SessionEntity__SessionEntity_user"."id" = "SessionEntity"."userId" - AND ( - "SessionEntity__SessionEntity_user"."deletedAt" IS NULL - ) - LEFT JOIN "user_metadata" "469e6aa7ff79eff78f8441f91ba15bb07d3634dd" ON "469e6aa7ff79eff78f8441f91ba15bb07d3634dd"."userId" = "SessionEntity__SessionEntity_user"."id" - WHERE - (("SessionEntity"."token" = $1)) - ) "distinctAlias" -ORDER BY - "SessionEntity_id" ASC -LIMIT - 1 +select + "sessions".*, + to_json("user") as "user" +from + "sessions" + inner join lateral ( + select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt", + ( + select + array_agg("user_metadata") as "metadata" + from + "user_metadata" + where + "users"."id" = "user_metadata"."userId" + ) as "metadata" + from + "users" + where + "users"."id" = "sessions"."userId" + and "users"."deletedAt" is null + ) as "user" on true +where + "sessions"."token" = $1 + +-- SessionRepository.getByUserId +select + "sessions".*, + to_json("user") as "user" +from + "sessions" + inner join lateral ( + select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt", + ( + select + array_agg("user_metadata") as "metadata" + from + "user_metadata" + where + "users"."id" = "user_metadata"."userId" + ) as "metadata" + from + "users" + where + "users"."id" = "sessions"."userId" + and "users"."deletedAt" is null + ) as "user" on true +where + "sessions"."userId" = $1 +order by + "sessions"."updatedAt" desc, + "sessions"."createdAt" desc -- SessionRepository.delete -DELETE FROM "sessions" -WHERE - "id" = $1 +delete from "sessions" +where + "id" = $1::uuid diff --git a/server/src/repositories/session.repository.ts b/server/src/repositories/session.repository.ts index 3a0af1ef69d0f..3e6c8977212a7 100644 --- a/server/src/repositories/session.repository.ts +++ b/server/src/repositories/session.repository.ts @@ -1,56 +1,70 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { Insertable, Kysely, Updateable } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB, Sessions } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; -import { SessionEntity } from 'src/entities/session.entity'; +import { SessionEntity, withUser } from 'src/entities/session.entity'; import { ISessionRepository, SessionSearchOptions } from 'src/interfaces/session.interface'; -import { LessThanOrEqual, Repository } from 'typeorm'; +import { asUuid } from 'src/utils/database'; @Injectable() export class SessionRepository implements ISessionRepository { - constructor(@InjectRepository(SessionEntity) private repository: Repository) {} + constructor(@InjectKysely() private db: Kysely) {} - @GenerateSql({ params: [DummyValue.DATE] }) + @GenerateSql({ params: [{ updatedBefore: DummyValue.DATE }] }) search(options: SessionSearchOptions): Promise { - return this.repository.find({ where: { updatedAt: LessThanOrEqual(options.updatedBefore) } }); + return this.db + .selectFrom('sessions') + .selectAll() + .where('sessions.updatedAt', '<=', options.updatedBefore) + .execute() as Promise; } @GenerateSql({ params: [DummyValue.STRING] }) - getByToken(token: string): Promise { - return this.repository.findOne({ - where: { token }, - relations: { - user: { - metadata: true, - }, - }, - }); + getByToken(token: string): Promise { + return this.db + .selectFrom('sessions') + .innerJoinLateral(withUser, (join) => join.onTrue()) + .selectAll('sessions') + .select((eb) => eb.fn.toJson('user').as('user')) + .where('sessions.token', '=', token) + .executeTakeFirst() as Promise; } + @GenerateSql({ params: [DummyValue.UUID] }) getByUserId(userId: string): Promise { - return this.repository.find({ - where: { - userId, - }, - relations: { - user: true, - }, - order: { - updatedAt: 'desc', - createdAt: 'desc', - }, - }); + return this.db + .selectFrom('sessions') + .innerJoinLateral(withUser, (join) => join.onTrue()) + .selectAll('sessions') + .select((eb) => eb.fn.toJson('user').as('user')) + .where('sessions.userId', '=', userId) + .orderBy('sessions.updatedAt', 'desc') + .orderBy('sessions.createdAt', 'desc') + .execute() as unknown as Promise; } - create>(dto: T): Promise { - return this.repository.save(dto); + async create(dto: Insertable): Promise { + const { id, token, userId, createdAt, updatedAt, deviceType, deviceOS } = await this.db + .insertInto('sessions') + .values(dto) + .returningAll() + .executeTakeFirstOrThrow(); + + return { id, token, userId, createdAt, updatedAt, deviceType, deviceOS } as SessionEntity; } - update>(dto: T): Promise { - return this.repository.save(dto); + update(id: string, dto: Updateable): Promise { + return this.db + .updateTable('sessions') + .set(dto) + .where('sessions.id', '=', asUuid(id)) + .returningAll() + .executeTakeFirstOrThrow() as Promise; } @GenerateSql({ params: [DummyValue.UUID] }) async delete(id: string): Promise { - await this.repository.delete({ id }); + await this.db.deleteFrom('sessions').where('id', '=', asUuid(id)).execute(); } } diff --git a/server/src/services/auth.service.spec.ts b/server/src/services/auth.service.spec.ts index 6494a735b191f..da25663f38750 100644 --- a/server/src/services/auth.service.spec.ts +++ b/server/src/services/auth.service.spec.ts @@ -354,7 +354,7 @@ describe('AuthService', () => { describe('validate - user token', () => { it('should throw if no token is found', async () => { - sessionMock.getByToken.mockResolvedValue(null); + sessionMock.getByToken.mockResolvedValue(void 0); await expect( sut.authenticate({ headers: { 'x-immich-user-token': 'auth_token' }, @@ -399,7 +399,7 @@ describe('AuthService', () => { metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' }, }), ).resolves.toBeDefined(); - expect(sessionMock.update.mock.calls[0][0]).toMatchObject({ id: 'not_active', updatedAt: expect.any(Date) }); + expect(sessionMock.update.mock.calls[0][1]).toMatchObject({ id: 'not_active', updatedAt: expect.any(Date) }); }); }); diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index 29b73954650cf..9999c16f64ba2 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -331,7 +331,7 @@ export class AuthService extends BaseService { const updatedAt = DateTime.fromJSDate(session.updatedAt); const diff = now.diff(updatedAt, ['hours']); if (diff.hours > 1) { - await this.sessionRepository.update({ id: session.id, updatedAt: new Date() }); + await this.sessionRepository.update(session.id, { id: session.id, updatedAt: new Date() }); } return { user: session.user, session }; @@ -346,9 +346,9 @@ export class AuthService extends BaseService { await this.sessionRepository.create({ token, - user, deviceOS: loginDetails.deviceOS, deviceType: loginDetails.deviceType, + userId: user.id, }); return mapLoginResponse(user, key); From b74f013b5394cc4d0710dc285201dc2372222c42 Mon Sep 17 00:00:00 2001 From: Matthew Momjian <50788000+mmomjian@users.noreply.github.com> Date: Mon, 13 Jan 2025 20:57:19 -0500 Subject: [PATCH 04/18] fix(docs): database name for restore commands (#15276) * cleanup dbname * 2 * Update database-queries.md * Update backup-and-restore.md * Update backup-and-restore.md --- docs/docs/FAQ.mdx | 4 ++-- .../docs/administration/backup-and-restore.md | 24 +++++++++---------- docs/docs/guides/database-queries.md | 4 ++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/docs/FAQ.mdx b/docs/docs/FAQ.mdx index f82bc74f5ffa9..71ddcf0d33f13 100644 --- a/docs/docs/FAQ.mdx +++ b/docs/docs/FAQ.mdx @@ -425,7 +425,7 @@ A result of `on` means that checksums are enabled. Check if checksums are enabled ```bash -docker exec -it immich_postgres psql --dbname=immich --username= --command="show data_checksums" +docker exec -it immich_postgres psql --dbname=postgres --username= --command="show data_checksums" data_checksums ---------------- on @@ -440,7 +440,7 @@ If checksums are enabled, you can check the status of the database with the foll Check for database corruption ```bash -docker exec -it immich_postgres psql --dbname=immich --username= --command="SELECT datname, checksum_failures, checksum_last_failure FROM pg_stat_database WHERE datname IS NOT NULL" +docker exec -it immich_postgres psql --dbname=postgres --username= --command="SELECT datname, checksum_failures, checksum_last_failure FROM pg_stat_database WHERE datname IS NOT NULL" datname | checksum_failures | checksum_last_failure -----------+-------------------+----------------------- postgres | 0 | diff --git a/docs/docs/administration/backup-and-restore.md b/docs/docs/administration/backup-and-restore.md index 1b1775018efe3..cd58604e1f4d6 100644 --- a/docs/docs/administration/backup-and-restore.md +++ b/docs/docs/administration/backup-and-restore.md @@ -55,7 +55,7 @@ sleep 10 # Wait for Postgres server to start up # Check the database user if you deviated from the default gunzip < "/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 --username=postgres # Restore Backup +| docker exec -i immich_postgres psql --dbname=postgres --username= # Restore Backup docker compose up -d # Start remainder of Immich apps ``` @@ -70,18 +70,18 @@ docker compose up -d # Start remainder of Immich apps docker compose down -v # CAUTION! Deletes all Immich data to start from scratch ## Uncomment the next line and replace DB_DATA_LOCATION with your Postgres path to permanently reset the Postgres database # Remove-Item -Recurse -Force DB_DATA_LOCATION # CAUTION! Deletes all Immich data to start from scratch -## You should mount the backup (as a volume, example: - 'C:\path\to\backup\dump.sql':/dump.sql) into the immich_postgres container using the docker-compose.yml -docker compose pull # Update to latest version of Immich (if desired) -docker compose create # Create Docker containers for Immich apps without running them -docker start immich_postgres # Start Postgres server -sleep 10 # Wait for Postgres server to start up -docker exec -it immich_postgres bash # Enter the Docker shell and run the following command -# Check the database user if you deviated from the default -cat "/dump.sql" \ +## You should mount the backup (as a volume, example: `- 'C:\path\to\backup\dump.sql:/dump.sql'`) into the immich_postgres container using the docker-compose.yml +docker compose pull # Update to latest version of Immich (if desired) +docker compose create # Create Docker containers for Immich apps without running them +docker start immich_postgres # Start Postgres server +sleep 10 # Wait for Postgres server to start up +docker exec -it immich_postgres bash # Enter the Docker shell and run the following command +# Check the database user if you deviated from the default. If your backup ends in `.gz`, replace `cat` with `gunzip` +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 --username=postgres # Restore Backup -exit # Exit the Docker shell -docker compose up -d # Start remainder of Immich apps +| psql --dbname=postgres --username= # Restore Backup +exit # Exit the Docker shell +docker compose up -d # Start remainder of Immich apps ``` diff --git a/docs/docs/guides/database-queries.md b/docs/docs/guides/database-queries.md index 0e58d84f90c01..e71fa21c8b041 100644 --- a/docs/docs/guides/database-queries.md +++ b/docs/docs/guides/database-queries.md @@ -5,9 +5,9 @@ Keep in mind that mucking around in the database might set the moon on fire. Avo ::: :::tip -Run `docker exec -it immich_postgres psql --dbname=immich --username=` to connect to the database via the container directly. +Run `docker exec -it immich_postgres psql --dbname= --username=` to connect to the database via the container directly. -(Replace `` with the value from your [`.env` file](/docs/install/environment-variables#database)). +(Replace `` and `` with the values from your [`.env` file](/docs/install/environment-variables#database)). ::: ## Assets From 28b08ed4178b4d7d7c6fbbe994b89ab2de7f4ae3 Mon Sep 17 00:00:00 2001 From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> Date: Tue, 14 Jan 2025 03:23:12 +0100 Subject: [PATCH 05/18] refactor: migrate audit repository to kysely (#15269) --- server/src/queries/audit.repository.sql | 21 +++++++++++ server/src/repositories/audit.repository.ts | 39 ++++++++++++--------- 2 files changed, 44 insertions(+), 16 deletions(-) create mode 100644 server/src/queries/audit.repository.sql diff --git a/server/src/queries/audit.repository.sql b/server/src/queries/audit.repository.sql new file mode 100644 index 0000000000000..3c83d2d3e8916 --- /dev/null +++ b/server/src/queries/audit.repository.sql @@ -0,0 +1,21 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- AuditRepository.getAfter +select distinct + on ("audit"."entityId", "audit"."entityType") "audit"."entityId" +from + "audit" +where + "audit"."createdAt" > $1 + and "audit"."action" = $2 + and "audit"."entityType" = $3 + and "audit"."ownerId" in ($4) +order by + "audit"."entityId" desc, + "audit"."entityType" desc, + "audit"."createdAt" desc + +-- AuditRepository.removeBefore +delete from "audit" +where + "createdAt" < $1 diff --git a/server/src/repositories/audit.repository.ts b/server/src/repositories/audit.repository.ts index ac73c3a8b9d82..5731087aef554 100644 --- a/server/src/repositories/audit.repository.ts +++ b/server/src/repositories/audit.repository.ts @@ -1,31 +1,38 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { AuditEntity } from 'src/entities/audit.entity'; +import { Kysely } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB } from 'src/db'; +import { DummyValue, GenerateSql } from 'src/decorators'; +import { DatabaseAction, EntityType } from 'src/enum'; import { AuditSearch, IAuditRepository } from 'src/interfaces/audit.interface'; -import { In, LessThan, MoreThan, Repository } from 'typeorm'; @Injectable() export class AuditRepository implements IAuditRepository { - constructor(@InjectRepository(AuditEntity) private repository: Repository) {} + constructor(@InjectKysely() private db: Kysely) {} + @GenerateSql({ + params: [ + DummyValue.DATE, + { action: DatabaseAction.CREATE, entityType: EntityType.ASSET, userIds: [DummyValue.UUID] }, + ], + }) async getAfter(since: Date, options: AuditSearch): Promise { - const records = await this.repository - .createQueryBuilder('audit') - .where({ - createdAt: MoreThan(since), - action: options.action, - entityType: options.entityType, - ownerId: In(options.userIds), - }) + const records = await this.db + .selectFrom('audit') + .where('audit.createdAt', '>', since) + .$if(!!options.action, (qb) => qb.where('audit.action', '=', options.action!)) + .$if(!!options.entityType, (qb) => qb.where('audit.entityType', '=', options.entityType!)) + .where('audit.ownerId', 'in', options.userIds) .distinctOn(['audit.entityId', 'audit.entityType']) - .orderBy('audit.entityId, audit.entityType, audit.createdAt', 'DESC') + .orderBy(['audit.entityId desc', 'audit.entityType desc', 'audit.createdAt desc']) .select('audit.entityId') - .getMany(); + .execute(); - return records.map((r) => r.entityId); + return records.map(({ entityId }) => entityId); } + @GenerateSql({ params: [DummyValue.DATE] }) async removeBefore(before: Date): Promise { - await this.repository.delete({ createdAt: LessThan(before) }); + await this.db.deleteFrom('audit').where('createdAt', '<', before).execute(); } } From dc53e2a9b94d197a4544ad8a8b5d66ef0fedb2bf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:32:52 -0500 Subject: [PATCH 06/18] chore(deps): update docker.io/redis:6.2-alpine docker digest to 905c4ee (#15193) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docker/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 4b8453ce58b0c..df3fc3ce56d4d 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -48,7 +48,7 @@ services: redis: container_name: immich_redis - image: docker.io/redis:6.2-alpine@sha256:eaba718fecd1196d88533de7ba49bf903ad33664a92debb24660a922ecd9cac8 + image: docker.io/redis:6.2-alpine@sha256:905c4ee67b8e0aa955331960d2aa745781e6bd89afc44a8584bfd13bc890f0ae healthcheck: test: redis-cli ping || exit 1 restart: always From 3b0622021996ba9c0001310136ef56354ca18986 Mon Sep 17 00:00:00 2001 From: Zer0x00 Date: Tue, 14 Jan 2025 03:42:32 +0100 Subject: [PATCH 07/18] feat: Upgrade devcontainer setup (#14419) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Upgrade devcontainer * Style: Format devcontainer.json * Chore: Remove settings from devcontainer * chore: add shebang * chore: fix shellcheck --------- Co-authored-by: Bünyamin Olgun Co-authored-by: Jason Rasmussen --- .devcontainer/.gitignore | 2 ++ .devcontainer/Dockerfile | 14 ++++++++ .devcontainer/devcontainer.json | 42 ++++++++++++---------- .devcontainer/docker-compose.yml | 8 +++++ .devcontainer/scripts/initializeCommand.sh | 6 ++++ .devcontainer/scripts/onCreateCommand.sh | 25 +++++++++++++ 6 files changed, 79 insertions(+), 18 deletions(-) create mode 100644 .devcontainer/.gitignore create mode 100644 .devcontainer/docker-compose.yml create mode 100644 .devcontainer/scripts/initializeCommand.sh create mode 100644 .devcontainer/scripts/onCreateCommand.sh diff --git a/.devcontainer/.gitignore b/.devcontainer/.gitignore new file mode 100644 index 0000000000000..6bf3b5d9e5c1d --- /dev/null +++ b/.devcontainer/.gitignore @@ -0,0 +1,2 @@ +.env +library \ No newline at end of file diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 9ae47b93759a4..a107c1ac3ada5 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,2 +1,16 @@ ARG BASEIMAGE=mcr.microsoft.com/devcontainers/typescript-node:22@sha256:9791f4aa527774bc370c6bd2f6705ce5a686f1e6f204badd8dfaacce28c631ae FROM ${BASEIMAGE} + +# Flutter SDK +# https://flutter.dev/docs/development/tools/sdk/releases?tab=linux +ENV FLUTTER_CHANNEL="stable" +ENV FLUTTER_VERSION="3.24.5" +ENV FLUTTER_HOME=/flutter +ENV PATH=${PATH}:${FLUTTER_HOME}/bin + +# Flutter SDK +RUN mkdir -p ${FLUTTER_HOME} \ + && curl -C - --output flutter.tar.xz https://storage.googleapis.com/flutter_infra_release/releases/${FLUTTER_CHANNEL}/linux/flutter_linux_${FLUTTER_VERSION}-${FLUTTER_CHANNEL}.tar.xz \ + && tar -xf flutter.tar.xz --strip-components=1 -C ${FLUTTER_HOME} \ + && rm flutter.tar.xz \ + && chown -R 1000:1000 ${FLUTTER_HOME} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b297f9a2d8cc1..2d567f033a81d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,20 +1,26 @@ { - "name": "Immich devcontainers", - "build": { - "dockerfile": "Dockerfile", - "args": { - "BASEIMAGE": "mcr.microsoft.com/devcontainers/typescript-node:22" - } - }, - "customizations": { - "vscode": { - "extensions": [ - "svelte.svelte-vscode" - ] - } - }, - "forwardPorts": [], - "postCreateCommand": "make install-all", - "remoteUser": "node" + "name": "Immich", + "service": "immich-devcontainer", + "dockerComposeFile": [ + "docker-compose.yml", + "../docker/docker-compose.dev.yml" + ], + "customizations": { + "vscode": { + "extensions": [ + "Dart-Code.dart-code", + "Dart-Code.flutter", + "dbaeumer.vscode-eslint", + "dcmdev.dcm-vscode-extension", + "esbenp.prettier-vscode", + "svelte.svelte-vscode" + ] + } + }, + "forwardPorts": [], + "initializeCommand": "bash .devcontainer/scripts/initializeCommand.sh", + "onCreateCommand": "bash .devcontainer/scripts/onCreateCommand.sh", + "overrideCommand": true, + "workspaceFolder": "/immich", + "remoteUser": "node" } - diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000000000..25719641d2ddf --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,8 @@ +services: + immich-devcontainer: + build: + dockerfile: Dockerfile + extra_hosts: + - 'host.docker.internal:host-gateway' + volumes: + - ..:/immich:cached diff --git a/.devcontainer/scripts/initializeCommand.sh b/.devcontainer/scripts/initializeCommand.sh new file mode 100644 index 0000000000000..9d9d196696ab5 --- /dev/null +++ b/.devcontainer/scripts/initializeCommand.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# If .env file does not exist, create it by copying example.env from the docker folder +if [ ! -f ".devcontainer/.env" ]; then + cp docker/example.env .devcontainer/.env +fi diff --git a/.devcontainer/scripts/onCreateCommand.sh b/.devcontainer/scripts/onCreateCommand.sh new file mode 100644 index 0000000000000..2f898ec32e8b9 --- /dev/null +++ b/.devcontainer/scripts/onCreateCommand.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Enable multiarch for arm64 if necessary +if [ "$(dpkg --print-architecture)" = "arm64" ]; then + sudo dpkg --add-architecture amd64 && \ + sudo apt-get update && \ + sudo apt-get install -y --no-install-recommends \ + qemu-user-static \ + libc6:amd64 \ + libstdc++6:amd64 \ + libgcc1:amd64 +fi + +# Install DCM +wget -qO- https://dcm.dev/pgp-key.public | sudo gpg --dearmor -o /usr/share/keyrings/dcm.gpg +sudo echo 'deb [signed-by=/usr/share/keyrings/dcm.gpg arch=amd64] https://dcm.dev/debian stable main' | sudo tee /etc/apt/sources.list.d/dart_stable.list + +sudo apt-get update +sudo apt-get install dcm + +dart --disable-analytics + +# Install immich +cd /immich || exit +make install-all From e978b8c685009587d5e6fd6d0c75866823606aa6 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Tue, 14 Jan 2025 03:57:54 +0100 Subject: [PATCH 08/18] chore(web): update translations (#15145) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translate-URL: https://hosted.weblate.org/projects/immich/immich/bg/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/ca/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/el/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/et/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/he/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/hu/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/id/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/lt/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/lv/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/ms/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/nb_NO/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/pt/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/pt_BR/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/sk/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/sl/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/sr_Cyrl/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/sr_Latn/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/sv/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/ta/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/uk/ Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/ Translation: Immich/immich Co-authored-by: Andreas Johansen Co-authored-by: AtmosphericIgnition Co-authored-by: Bezruchenko Simon Co-authored-by: Damian Krysta Co-authored-by: Denis Pacquier Co-authored-by: Dmitry Banny Co-authored-by: Dominik Mielcarek Co-authored-by: Erik Järlestrand Co-authored-by: Filip Hanes Co-authored-by: Gerardo Doro Co-authored-by: Hurricane-32 Co-authored-by: Indrek Haav Co-authored-by: Jendrik Köhler Co-authored-by: Jordi Masip Co-authored-by: Kenji Opdam Co-authored-by: Krisztián Co-authored-by: Leo Bottaro Co-authored-by: Linerly Co-authored-by: Michal Micech Co-authored-by: Miki Mrvos Co-authored-by: Milan Šalka Co-authored-by: Milo Germanus Co-authored-by: Máté Molnár Co-authored-by: Mārtiņš Bruņenieks Co-authored-by: Rui Co-authored-by: Santiago Co-authored-by: Shawn Co-authored-by: Vykintas Vyšniauskas Co-authored-by: Xo Co-authored-by: chamdim Co-authored-by: grgergo Co-authored-by: rezi nagro Co-authored-by: scudo Co-authored-by: stelle Co-authored-by: thehijacker Co-authored-by: waclaw66 Co-authored-by: Вячеслав Лукьяненко Co-authored-by: Пламен Марков Co-authored-by: தமிழ்நேரம் --- i18n/bg.json | 22 +- i18n/ca.json | 5 +- i18n/cs.json | 8 +- i18n/de.json | 10 +- i18n/el.json | 13 +- i18n/es.json | 4 + i18n/et.json | 9 + i18n/fr.json | 4 + i18n/he.json | 64 +++--- i18n/hu.json | 6 +- i18n/id.json | 4 + i18n/it.json | 17 +- i18n/lt.json | 179 +++++++++------ i18n/lv.json | 17 +- i18n/ms.json | 59 ++++- i18n/nb_NO.json | 259 ++++++++++++++++++++-- i18n/nl.json | 4 + i18n/pl.json | 16 +- i18n/pt.json | 6 +- i18n/pt_BR.json | 4 + i18n/ru.json | 4 + i18n/sk.json | 475 +++++++++++++++++++++++++++------------- i18n/sl.json | 4 + i18n/sr_Cyrl.json | 4 + i18n/sr_Latn.json | 4 + i18n/sv.json | 8 +- i18n/ta.json | 2 +- i18n/uk.json | 6 +- i18n/zh_SIMPLIFIED.json | 4 + 29 files changed, 916 insertions(+), 305 deletions(-) diff --git a/i18n/bg.json b/i18n/bg.json index 6d66f0901c132..47c1a825087cb 100644 --- a/i18n/bg.json +++ b/i18n/bg.json @@ -437,8 +437,8 @@ "birthdate_set_description": "Датата на раждане се използва за изчисляване на възрастта на този човек към момента на снимката.", "blurred_background": "Замъглен заден фон", "bugs_and_feature_requests": "Бъгове и заявки за функции", - "build": "Създаване", - "build_image": "Създаване на изображение", + "build": "Версия", + "build_image": "Docker версия", "bulk_delete_duplicates_confirmation": "Сигурни ли сте, че искате да изтриете масово {count, plural, one {# дублиран файл} other {# дублирани файла}}? Това ще запази най-големия файл от всяка група и ще изтрие трайно всички други дубликати. Не можете да отмените това действие!", "bulk_keep_duplicates_confirmation": "Сигурни ли сте, че искате да запазите {count, plural, one {# дублиран файл} other {# дублирани файла}}? Това ще потвърди всички групи дубликати, без да изтрива нищо.", "bulk_trash_duplicates_confirmation": "Сигурни ли сте, че искате да преместите в кошчето масово {count, plural, one {# дублиран файл} other {# дублирани файла}}? Това ще запази най-големия файл от всяка група и ще премести в кошчето всички други дубликати.", @@ -523,6 +523,10 @@ "date_range": "Период от време", "day": "Ден", "deduplicate_all": "Дедупликиране на всички", + "deduplication_criteria_1": "Размер на снимката в байтове", + "deduplication_criteria_2": "Брой EXIF данни", + "deduplication_info": "Информация за дедупликацията", + "deduplication_info_description": "За автоматично предварително избиране на ресурси и премахване на дубликати на едро, разглеждаме:", "default_locale": "Локализация по подразбиране", "default_locale_description": "Форматиране на дати и числа в зависимост от местоположението на браузъра", "delete": "Изтрий", @@ -669,7 +673,7 @@ "unable_to_download_files": "Не могат да се изтеглят файловете", "unable_to_edit_exclusion_pattern": "Не може да се редактира шаблон за изключване", "unable_to_edit_import_path": "Пътят за импортиране не може да се редактира", - "unable_to_empty_trash": "Не може да изпразни кошчето", + "unable_to_empty_trash": "Неуспешно изпразване на кошчето", "unable_to_enter_fullscreen": "Не може да се отвори в цял екран", "unable_to_exit_fullscreen": "Не може да излезе от цял екран", "unable_to_get_comments_number": "Не може да получи брой коментари", @@ -765,7 +769,7 @@ "group_no": "Няма група", "group_owner": "Групиране по собственик", "group_year": "Групиране по година", - "has_quota": "Има лимит", + "has_quota": "Лимит", "hi_user": "Здравей, {name} {email}", "hide_all_people": "Скрий всички хора", "hide_gallery": "Скрий галерия", @@ -1009,7 +1013,7 @@ "purchase_button_select": "Избери", "purchase_failed_activation": "Неуспешна активация! Моля, проверете имейла си за правилния продуктов ключ!", "purchase_individual_description_1": "За индивидуален потребител", - "purchase_individual_description_2": "Поддръжнически статус", + "purchase_individual_description_2": "Статус на поддръжник", "purchase_individual_title": "Индивидуален", "purchase_input_suggestion": "Имате продуктов ключ? Въведете ключа по-долу", "purchase_license_subtitle": "Закупете Immich, за да подкрепите продължаващото развитие на услугата", @@ -1025,7 +1029,7 @@ "purchase_remove_server_product_key": "Премахни продуктовия ключ на сървъра", "purchase_remove_server_product_key_prompt": "Сигурни ли сте, че искате да премахнете продуктовия ключ на сървъра?", "purchase_server_description_1": "За целият сървър", - "purchase_server_description_2": "Статус на поддръжника", + "purchase_server_description_2": "Статус на поддръжник", "purchase_server_title": "Сървър", "purchase_settings_server_activated": "Продуктовият ключ на сървъра се управлява от администратора", "rating": "Оценка със звезди", @@ -1205,7 +1209,7 @@ "sort_people_by_similarity": "Сортиране на хора по прилика", "sort_recent": "Най-новата снимка", "sort_title": "Заглавие", - "source": "Източник", + "source": "Код", "stack": "Събери", "stack_duplicates": "Подреждане на дубликати", "stack_select_one_photo": "Избери една главна снимка за събраните снимки", @@ -1258,9 +1262,9 @@ "toggle_theme": "Превключване на тема", "total": "Общо", "total_usage": "Общо използвано", - "trash": "кошче", + "trash": "Кошче", "trash_all": "Изхвърли всички", - "trash_count": "Кошче {count, number}", + "trash_count": "В Кошчето {count, number}", "trash_delete_asset": "Вкарай в Кошчето/Изтрий елемент", "trash_no_results_message": "Изтритите снимки и видеоклипове ще се показват тук.", "trashed_items_will_be_permanently_deleted_after": "Изхвърлените в кошчето елементи ще бъдат изтрити за постоянно след {days, plural, one {# ден} other {# дни}}.", diff --git a/i18n/ca.json b/i18n/ca.json index 160a6cac8a748..7480f06664777 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -1,5 +1,5 @@ { - "about": "Sobre", + "about": "Quant a", "account": "Compte", "account_settings": "Configuració del compte", "acknowledge": "D'acord", @@ -523,6 +523,9 @@ "date_range": "Interval de dates", "day": "Dia", "deduplicate_all": "Desduplica-ho tot", + "deduplication_criteria_1": "Mida d'imatge en bytes", + "deduplication_criteria_2": "Quantitat de dades EXIF", + "deduplication_info_description": "Per preseleccionar recursos automàticament i eliminar els duplicats de manera massiva, ens fixem en:", "default_locale": "Localització predeterminada", "default_locale_description": "Format de dates i números segons la configuració del navegador", "delete": "Esborra", diff --git a/i18n/cs.json b/i18n/cs.json index edb9c3c105f2b..fbfbdf3bfc94c 100644 --- a/i18n/cs.json +++ b/i18n/cs.json @@ -523,6 +523,10 @@ "date_range": "Rozsah dat", "day": "Den", "deduplicate_all": "Odstranit všechny duplicity", + "deduplication_criteria_1": "Velikost obrázku v bajtech", + "deduplication_criteria_2": "Počet EXIF dat", + "deduplication_info": "Informace o deduplikaci", + "deduplication_info_description": "Pro automatický předvýběr položek a hromadné odstranění duplicit se zohledňuje:", "default_locale": "Výchozí jazyk", "default_locale_description": "Formátovat datumy a čísla podle místního prostředí prohlížeče", "delete": "Smazat", @@ -921,7 +925,7 @@ "oldest_first": "Nejstarší první", "onboarding": "Zahájení", "onboarding_privacy_description": "Následující (volitelné) funkce jsou závislé na externích službách a lze je kdykoli zakázat v nastavení správy.", - "onboarding_theme_description": "Zvolte si barevné téma pro svou instanci. Můžete to později změnit v nastavení.", + "onboarding_theme_description": "Zvolte si barevný motiv pro svou instanci. Můžete to později změnit v nastavení.", "onboarding_welcome_description": "Nastavíme vaši instanci pomocí několika běžných nastavení.", "onboarding_welcome_user": "Vítej, {user}", "online": "Online", @@ -1282,7 +1286,7 @@ "unselect_all": "Zrušit výběr všech", "unselect_all_duplicates": "Zrušit výběr všech duplicit", "unstack": "Zrušit seskupení", - "unstacked_assets_count": "{count, plural, one {Rozložena # položka} few {Rozloženy # položky} other {Rozloženo # položek}}", + "unstacked_assets_count": "{count, plural, one {Rozložená # položka} few {Rozložené # položky} other {Rozložených # položiek}}", "untracked_files": "Nesledované soubory", "untracked_files_decription": "Tyto soubory nejsou aplikaci známy. Mohou být výsledkem neúspěšných přesunů, přerušeného nahrávání nebo mohou zůstat pozadu kvůli chybě", "up_next": "To je prozatím vše", diff --git a/i18n/de.json b/i18n/de.json index 89eea9fd49cfb..45d5212ea0404 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -290,7 +290,7 @@ "transcoding_constant_rate_factor_description": "Video-Qualitätsstufe. Typische Werte sind 23 für H.264, 28 für HEVC, 31 für VP9 und 35 für AV1. Ein niedrigerer Wert ist besser, erzeugt aber größere Dateien.", "transcoding_disabled_description": "Videos nicht transkodieren, dies kann die Wiedergabe auf manchen Geräten beeinträchtigen", "transcoding_encoding_options": "Kodierungsoptionen", - "transcoding_encoding_options_description": "Setze Codec, Auflösung, Qualität und andere Optionen für Kodierte Videos", + "transcoding_encoding_options_description": "Setze Codec, Auflösung, Qualität und andere Optionen für kodierte Videos", "transcoding_hardware_acceleration": "Hardware-Beschleunigung", "transcoding_hardware_acceleration_description": "Experimentell; viel schneller, aber bei gleicher Bitrate mit geringerer Qualität", "transcoding_hardware_decoding": "Hardware-Dekodierung", @@ -304,7 +304,7 @@ "transcoding_max_keyframe_interval_description": "Legt den maximalen Frame-Abstand zwischen Keyframes fest. Niedrigere Werte verschlechtern die Komprimierungseffizienz, verbessern aber die Suchzeiten und können die Qualität in Szenen mit schnellen Bewegungen verbessern. Bei 0 wird dieser Wert automatisch eingestellt.", "transcoding_optimal_description": "Videos mit einer höheren Auflösung als der Zielauflösung oder in einem nicht akzeptierten Format", "transcoding_policy": "Transkodierungsrichtlinie", - "transcoding_policy_description": "Bestimme, wann ein Video Transkodiert wird", + "transcoding_policy_description": "Bestimme, wann ein Video transkodiert wird", "transcoding_preferred_hardware_device": "Bevorzugtes Hardwaregerät", "transcoding_preferred_hardware_device_description": "Gilt nur für VAAPI und QSV. Legt den für die Hardware-Transkodierung verwendeten dri-Node fest.", "transcoding_preset_preset": "Voreinstellung (-preset)", @@ -313,7 +313,7 @@ "transcoding_reference_frames_description": "Die Anzahl der Bilder, auf die bei der Komprimierung eines bestimmten Bildes Bezug genommen wird. Höhere Werte verbessern die Komprimierungseffizienz, verlangsamen aber die Kodierung. 0 setzt diesen Wert automatisch.", "transcoding_required_description": "Nur Videos in einem nicht akzeptierten Format", "transcoding_settings": "Video-Transkodierungseinstellungen", - "transcoding_settings_description": "Auflösungs- und Kodierungsinformationen von Videodateien verwalten", + "transcoding_settings_description": "Verwalten welche Videos transkodiert werden und wie diese verarbeitet werden", "transcoding_target_resolution": "Ziel-Auflösung", "transcoding_target_resolution_description": "Höhere Auflösungen können mehr Details erhalten, benötigen aber mehr Zeit für die Codierung, haben größere Dateigrößen und können die Reaktionszeit der Anwendung beeinträchtigen.", "transcoding_temporal_aq": "Temporäre AQ", @@ -523,10 +523,10 @@ "date_range": "Datumsbereich", "day": "Tag", "deduplicate_all": "Alle Duplikate entfernen", - "deduplication_info": "Deduplizierungsinformationen", - "deduplication_info_description": "Für die automatische Datei-Vorauswahl und das Deduplizieren aller Dateien berücksichtigen wir:", "deduplication_criteria_1": "Bildgröße in Bytes", "deduplication_criteria_2": "Anzahl der EXIF-Daten", + "deduplication_info": "Deduplizierungsinformationen", + "deduplication_info_description": "Für die automatische Datei-Vorauswahl und das Deduplizieren aller Dateien berücksichtigen wir:", "default_locale": "Standard-Sprache", "default_locale_description": "Datumsangaben und Zahlen basierend auf dem Gebietsschema des Browsers formatieren", "delete": "Löschen", diff --git a/i18n/el.json b/i18n/el.json index 6c0bcdf6b9c33..ed06812e77933 100644 --- a/i18n/el.json +++ b/i18n/el.json @@ -289,6 +289,8 @@ "transcoding_constant_rate_factor": "Σταθερός παράγοντας ρυθμού (-crf)", "transcoding_constant_rate_factor_description": "Επίπεδο ποιότητας βίντεο. Οι τυπικές τιμές είναι οι, 23 για το H.264, 28 για το HEVC, 31 για το VP9 και 35 για το AV1. Χαμηλότερες τιμές σημαίνουν καλύτερη ποιότητα, αλλά παράγουν μεγαλύτερα αρχεία.", "transcoding_disabled_description": "Να μην μετατραπεί κανένα βίντεο γιατί δύναται να προκαλέσει πρόβλημα αναπαραγωγής σε ορισμένες συσκευές/εφαρμογές", + "transcoding_encoding_options": "Επιλογές κωδικοποίησης", + "transcoding_encoding_options_description": "Ορίστε τους κωδικοποιητές, την ανάλυση, την ποιότητα και άλλες επιλογές για τα κωδικοποιημένα βίντεο", "transcoding_hardware_acceleration": "Επιτάχυνση υλικού", "transcoding_hardware_acceleration_description": "Πειραματικό· πολύ πιο γρήγορο, αλλά θα έχει χαμηλότερη ποιότητα με τον ίδιο ρυθμό μετάδοσης (bitrate)", "transcoding_hardware_decoding": "Αποκωδικοποίηση μέσω υλικού", @@ -301,6 +303,8 @@ "transcoding_max_keyframe_interval": "Μέγιστο χρονικό διάστημα μεταξύ των καρέ αναφοράς (keyframe)", "transcoding_max_keyframe_interval_description": "Ορίζει το μέγιστο διάστημα μεταξύ των καρέ αναφοράς. Χαμηλότερες τιμές μειώνουν την αποδοτικότητα συμπίεσης, αλλά βελτιώνουν τον χρόνο αναζήτησης και μπορεί να βελτιώσουν την ποιότητα σε σκηνές με γρήγορη κίνηση. Η τιμή 0 ρυθμίζει αυτό το διάστημα αυτόματα.", "transcoding_optimal_description": "Βίντεο με ανώτερη ανάλυση από την επιθυμητή ή σε μη αποδεκτή μορφή", + "transcoding_policy": "Πολιτική Μετακωδικοποίησης", + "transcoding_policy_description": "Ορίστε πότε θα γίνει η μετακωδικοποίηση ενός βίντεο", "transcoding_preferred_hardware_device": "Προτιμώμενη συσκευή", "transcoding_preferred_hardware_device_description": "Ισχύει μόνο για VAAPI και QSV. Ορίζει τον κόμβο DRI που χρησιμοποιείται για την επιτάχυνση υλικού κατά την κωδικοποίηση.", "transcoding_preset_preset": "Προκαθορισμένη ρύθμιση (-preset)", @@ -309,7 +313,7 @@ "transcoding_reference_frames_description": "Ο αριθμός των καρέ που χρησιμοποιούνται ως αναφορά κατά τη συμπίεση ενός δεδομένου καρέ. Υψηλότερες τιμές βελτιώνουν την αποδοτικότητα της συμπίεσης, αλλά επιβραδύνουν την κωδικοποίηση. Η τιμή 0 ρυθμίζει αυτό τον αριθμό, αυτόματα.", "transcoding_required_description": "Μόνο βίντεο που δεν είναι σε αποδεκτή μορφή", "transcoding_settings": "Ρυθμίσεις μετατροπής βίντεο", - "transcoding_settings_description": "Διαχείριση της ανάλυσης και των πληροφοριών κωδικοποίησης των αρχείων βίντεο", + "transcoding_settings_description": "Διαχείριση των βίντεο που θα μετακωδικοποιηθούν και του τρόπου επεξεργασίας τους", "transcoding_target_resolution": "Επιθυμητή ανάλυση", "transcoding_target_resolution_description": "Οι υψηλότερες αναλύσεις μπορούν να διατηρήσουν περισσότερες λεπτομέρειες, αλλά απαιτούν περισσότερο χρόνο για κωδικοποίηση, παράγουν μεγαλύτερα αρχεία και μπορεί να μειώσουν την απόκριση της εφαρμογής.", "transcoding_temporal_aq": "Χρονική Προσαρμοστική Ποιότητα AQ(Adaptive Quantization)", @@ -322,7 +326,7 @@ "transcoding_transcode_policy_description": "Πολιτική για το πότε πρέπει να μετατραπεί ένα βίντεο. Τα βίντεο HDR θα μετατρέπονται πάντα (εκτός αν η μετατροπή είναι απενεργοποιημένη).", "transcoding_two_pass_encoding": "Κωδικοποίηση δύο περασμάτων", "transcoding_two_pass_encoding_setting_description": "Μετατροπή σε δύο περάσματα για την παραγωγή βίντεο με καλύτερη κωδικοποίηση. Όταν είναι ενεργοποιημένος ο μέγιστος ρυθμός μετάδοσης (απαραίτητος για λειτουργία με H.264 και HEVC), αυτή η λειτουργία χρησιμοποιεί ένα εύρος ρυθμού μετάδοσης βάσει του μέγιστου ρυθμού μετάδοσης και αγνοεί το CRF. Στον κωδικοποιητή VP9, το CRF μπορεί να χρησιμοποιηθεί εάν ο μέγιστος ρυθμός μετάδοσης είναι απενεργοποιημένος.", - "transcoding_video_codec": "Κωδικοποιητής Βίντεο", + "transcoding_video_codec": "Κωδικοποιητής βίντεο", "transcoding_video_codec_description": "Ο VP9 έχει υψηλή απόδοση και συμβατότητα με τον ιστότοπο, αλλά απαιτεί περισσότερο χρόνο για μετατροπή. Ο HEVC έχει παρόμοια απόδοση, αλλά χαμηλότερη συμβατότητα με τον ιστότοπο. Ο H.264 είναι ευρέως συμβατός και γρήγορος στη μετατροπή, αλλά παράγει πολύ μεγαλύτερα αρχεία. Ο AV1 είναι ο πιο αποδοτικός κωδικοποιητής, αλλά δεν υποστηρίζεται σε παλαιότερες συσκευές.", "trash_enabled_description": "Ενεργοποίηση λειτουργιών Κάδου Απορριμμάτων", "trash_number_of_days": "Αριθμός ημερών", @@ -519,6 +523,10 @@ "date_range": "Εύρος ημερομηνιών", "day": "Ημέρα", "deduplicate_all": "Αφαίρεση όλων των διπλότυπων", + "deduplication_criteria_1": "Μέγεθος εικόνας σε byte", + "deduplication_criteria_2": "Αριθμός δεδομένων EXIF", + "deduplication_info": "Πληροφορίες Αφαίρεσης Διπλοτύπων", + "deduplication_info_description": "Για να προεπιλέξουμε αυτόματα τα αρχεία και να αφαιρέσουμε τα διπλότυπα σε μαζική επεξεργασία, εξετάζουμε σε:", "default_locale": "Προεπιλεγμένη Τοπική Ρύθμιση", "default_locale_description": "Μορφοποιήστε τις ημερομηνίες και τους αριθμούς με βάση την τοπική ρύθμιση του προγράμματος περιήγησής σας", "delete": "Διαγραφή", @@ -755,6 +763,7 @@ "get_help": "Ζητήστε βοήθεια", "getting_started": "Ξεκινώντας", "go_back": "Πηγαίνετε πίσω", + "go_to_folder": "Μετάβαση στο φάκελο", "go_to_search": "Πηγαίνετε στην αναζήτηση", "group_albums_by": "Ομαδοποίηση άλμπουμ κατά...", "group_no": "Καμία ομοδοποίηση", diff --git a/i18n/es.json b/i18n/es.json index 02b9b2fad7a82..c619fbfeb8992 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -523,6 +523,10 @@ "date_range": "Rango de fechas", "day": "Día", "deduplicate_all": "Deduplicar todo", + "deduplication_criteria_1": "Tamaño de imagen en bytes", + "deduplication_criteria_2": "Conteo de datos EXIF", + "deduplication_info": "Información de Deduplicación", + "deduplication_info_description": "Para automáticamente preseleccionar recursos y eliminar duplicados en conjunto, nosotros consideramos lo siguiente:", "default_locale": "Configuración regional predeterminada", "default_locale_description": "Formatee fechas y números según la configuración regional de su navegador", "delete": "Eliminar", diff --git a/i18n/et.json b/i18n/et.json index 4d64da159ce14..ab17fad19fe4c 100644 --- a/i18n/et.json +++ b/i18n/et.json @@ -523,6 +523,10 @@ "date_range": "Kuupäevavahemik", "day": "Päev", "deduplicate_all": "Dedubleeri kõik", + "deduplication_criteria_1": "Pildi suurus baitides", + "deduplication_criteria_2": "EXIF andmete hulk", + "deduplication_info": "Dedubleerimise info", + "deduplication_info_description": "Üksuste automaatsel eelvalimisel ja duplikaatide eemaldamisel võetakse arvesse:", "default_locale": "Vaikimisi lokaat", "default_locale_description": "Vorminda kuupäevad ja numbrid vastavalt brauseri lokaadile", "delete": "Kustuta", @@ -748,6 +752,7 @@ "filetype": "Failitüüp", "filter_people": "Filtreeri isikuid", "find_them_fast": "Leia teda kiiresti nime järgi otsides", + "fix_incorrect_match": "Paranda ebaõige vaste", "folders": "Kaustad", "folders_feature_description": "Kaustavaate abil failisüsteemis olevate fotode ja videote sirvimine", "forward": "Edasi", @@ -755,6 +760,7 @@ "get_help": "Küsi abi", "getting_started": "Alustamine", "go_back": "Tagasi", + "go_to_folder": "Mine kausta", "go_to_search": "Otsingusse", "group_albums_by": "Grupeeri albumid...", "group_no": "Ära grupeeri", @@ -1029,6 +1035,7 @@ "reassigned_assets_to_existing_person": "{count, plural, one {# üksus} other {# üksust}} seostatud {name, select, null {olemasoleva isikuga} other {isikuga {name}}}", "reassigned_assets_to_new_person": "{count, plural, one {# üksus} other {# üksust}} seostatud uue isikuga", "reassing_hint": "Seosta valitud üksused olemasoleva isikuga", + "recent": "Hiljutine", "recent-albums": "Hiljutised albumid", "recent_searches": "Hiljutised otsingud", "refresh": "Värskenda", @@ -1189,6 +1196,7 @@ "sort_items": "Üksuste arv", "sort_modified": "Muutmise aeg", "sort_oldest": "Vanim foto", + "sort_people_by_similarity": "Sorteeri isikud sarnasuse järgi", "sort_recent": "Uusim foto", "sort_title": "Pealkiri", "source": "Lähtekood", @@ -1309,6 +1317,7 @@ "view_all_users": "Vaata kõiki kasutajaid", "view_in_timeline": "Vaata ajajoonel", "view_links": "Vaata linke", + "view_name": "Vaade", "view_next_asset": "Vaata järgmist üksust", "view_previous_asset": "Vaata eelmist üksust", "view_stack": "Vaata virna", diff --git a/i18n/fr.json b/i18n/fr.json index c4bab44ee3770..2b5635fd9155a 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -523,6 +523,10 @@ "date_range": "Plage de dates", "day": "Jour", "deduplicate_all": "Dédupliquer tout", + "deduplication_criteria_1": "Taille de l'image en octets", + "deduplication_criteria_2": "Nombre de données EXIF", + "deduplication_info": "Info de déduplication", + "deduplication_info_description": "Pour présélectionner automatiquement les médias et supprimer les doublons en masse, nous examinons :", "default_locale": "Région par défaut", "default_locale_description": "Afficher les dates et nombres en fonction des paramètres de votre navigateur", "delete": "Supprimer", diff --git a/i18n/he.json b/i18n/he.json index 6026a0b4b33f0..9993d6cf48d0b 100644 --- a/i18n/he.json +++ b/i18n/he.json @@ -131,7 +131,7 @@ "machine_learning_smart_search_description": "חפש תמונות באופן סמנטי באמצעות הטמעות של CLIP", "machine_learning_smart_search_enabled": "אפשר חיפוש חכם", "machine_learning_smart_search_enabled_description": "אם מושבת, תמונות לא יקודדו לחיפוש חכם.", - "machine_learning_url_description": "כתובת האתר של שרת למידת המכונה. אם ניתן יותר מכתובת אחת, כל שרת ינסה בתורו עד אשר יענה בחיוב, בסדר התחלתי.", + "machine_learning_url_description": "כתובת האתר של שרת למידת המכונה. אם ניתנת יותר מכתובת אחת, כל שרת ינסה בתורו עד אשר יענה בחיוב, בסדר התחלתי.", "manage_concurrency": "נהל בו-זמניות", "manage_log_settings": "נהל הגדרות רישום ביומן", "map_dark_style": "עיצוב כהה", @@ -289,8 +289,8 @@ "transcoding_constant_rate_factor": "גורם קצב קבוע (-crf)", "transcoding_constant_rate_factor_description": "רמת איכות וידאו. ערכים אופייניים הם הערך 23 עבור H.264, הערך 28 עבור HEVC, הערך 31 עבור VP9, והערך 35 עבור AV1. נמוך יותר הוא טוב יותר, אבל מייצר קבצים גדולים יותר.", "transcoding_disabled_description": "אין להמיר את הקידוד של שום סרטון, עלול לגרום לכך שהניגון לא יפעל במכשירים מסוימים", - "transcoding_encoding_options": "אפשרויות הקידוד", - "transcoding_encoding_options_description": "הגדר מקודדים, רזולוציה, איכות ואפשרויות נוספות עבור הסרטונים המקודדים", + "transcoding_encoding_options": "אפשרויות קידוד", + "transcoding_encoding_options_description": "הגדר מקודדים, רזולוציה, איכות ואפשרויות אחרות עבור הסרטונים המקודדים", "transcoding_hardware_acceleration": "האצת חומרה", "transcoding_hardware_acceleration_description": "ניסיוני; המרה הרבה יותר מהירה, אבל תהיה באיכות נמוכה יותר באותו קצב סיביות", "transcoding_hardware_decoding": "פענוח חומרה", @@ -304,7 +304,7 @@ "transcoding_max_keyframe_interval_description": "מגדיר את מרחק הפריימים המרבי בין תמונות מפתח. ערכים נמוכים גורעים את יעילות הדחיסה, אך משפרים את זמני החיפוש ועשויים לשפר את האיכות בסצנות עם תנועה מהירה. 0 מגדיר ערך זה באופן אוטומטי.", "transcoding_optimal_description": "סרטונים גבוהים מרזולוציית היעד או לא בפורמט מקובל", "transcoding_policy": "מדיניות המרה", - "transcoding_policy_description": "הגדר מתי וידאו יעבור המרה", + "transcoding_policy_description": "הגדר מתי סרטון יעבור המרת קידוד", "transcoding_preferred_hardware_device": "מכשיר חומרה מועדף", "transcoding_preferred_hardware_device_description": "חל רק על VAAPI ו-QSV. מגדיר את צומת ה-dri המשמש להמרת קידוד של חומרה.", "transcoding_preset_preset": "הגדרות קבועות מראש (-preset)", @@ -313,7 +313,7 @@ "transcoding_reference_frames_description": "מספר הפריימים לייחוס בעת דחיסה של פריים נתון. ערכים גבוהים יותר משפרים את יעילות הדחיסה, אך מאטים את הקידוד. 0 מגדיר את הערך זה באופן אוטומטי.", "transcoding_required_description": "רק סרטונים שאינם בפורמט מקובל", "transcoding_settings": "הגדרות המרת קידוד סרטונים", - "transcoding_settings_description": "נהל אילו סרטונים לעבד וכיצד לעבד אותם", + "transcoding_settings_description": "נהל אילו סרטונים להמיר וכיצד לעבד אותם", "transcoding_target_resolution": "רזולוציה יעד", "transcoding_target_resolution_description": "רזולוציות גבוהות יותר יכולות לשמר פרטים רבים יותר אך לוקחות זמן רב יותר לקידוד, יש להן גדלי קבצים גדולים יותר, ויכולות להפחית את תגובתיות היישום.", "transcoding_temporal_aq": "Temporal AQ", @@ -326,7 +326,7 @@ "transcoding_transcode_policy_description": "מדיניות לגבי מתי יש להמיר קידוד של סרטון. תמיד יומר הקידוד של סרטוני HDR (למעט אם המרת קידוד מושבתת).", "transcoding_two_pass_encoding": "קידוד בשני מעברים", "transcoding_two_pass_encoding_setting_description": "המר קידוד בשני מעברים כדי לייצר סרטונים מקודדים טוב יותר. כאשר קצב סיביות מרבי מופעל (נדרש כדי שזה יעבוד עם H.264 ו-HEVC), מצב זה משתמש בטווח קצב סיביות המבוסס על קצב הסיביות המרבי ומתעלם מ-CRF. עבור VP9, ניתן להשתמש ב-CRF אם קצב סיביות מרבי מושבת.", - "transcoding_video_codec": "מקודדי וידאו", + "transcoding_video_codec": "מקודד סרטון", "transcoding_video_codec_description": "ל-VP9 יש יעילות גבוהה ותאימות רשת, אבל לוקח יותר זמן להמיר את הקידוד עבורו. HEVC מתפקד באופן דומה, אך בעל תאימות רשת נמוכה יותר. H.264 תואם באופן נרחב ומהיר להמיר את קידודו, אבל הוא מייצר קבצים גדולים בהרבה. AV1 הוא הקידוד היעיל ביותר אך לוקה בתמיכה במכשירים ישנים יותר.", "trash_enabled_description": "הפעל את תכונות האשפה", "trash_number_of_days": "מספר הימים", @@ -522,35 +522,39 @@ "date_of_birth_saved": "תאריך לידה נשמר בהצלחה", "date_range": "טווח תאריכים", "day": "יום", - "deduplicate_all": "בטל כפילויות של הכל", - "default_locale": "אזור שפה ברירת מחדל", - "default_locale_description": "עצב תאריכים ומספרים על סמך אזור השפה של הדפדפן שלך", - "delete": "מחק", - "delete_album": "מחק אלבום", - "delete_api_key_prompt": "האם את/ה בטוח/ה שברצונך למחוק מפתח API זה?", - "delete_duplicates_confirmation": "האם את/ה בטוח/ה שברצונך למחוק לצמיתות את הכפילויות האלה?", - "delete_key": "מחק מפתח", - "delete_library": "מחק ספרייה", - "delete_link": "מחק קישור", - "delete_others": "מחק אחרים", - "delete_shared_link": "מחק קישור משותף", - "delete_tag": "מחק תג", - "delete_tag_confirmation_prompt": "האם את/ה בטוח/ה שברצונך למחוק תג {tagName}?", - "delete_user": "מחק משתמש", - "deleted_shared_link": "קישור משותף נמחק", - "deletes_missing_assets": "מוחק נכסים שחסרים בדיסק", + "deduplicate_all": "ביטול כל הכפילויות", + "deduplication_criteria_1": "גודל תמונה בבתים", + "deduplication_criteria_2": "ספירת נתוני EXIF", + "deduplication_info": "מידע על ביטול כפילויות", + "deduplication_info_description": "כדי לבחור מראש נכסים באופן אוטומטי ולהסיר כפילויות בכמות גדולה, אנו מסתכלים על:", + "default_locale": "שפת ברירת מחדל", + "default_locale_description": "פורמט תאריכים ומספרים מבוסס שפת הדפדפן שלך", + "delete": "הסרה", + "delete_album": "הסרת אלבום", + "delete_api_key_prompt": "האם ברצונך למחוק מפתח ה-API הזה?", + "delete_duplicates_confirmation": "האם ברצונך להסיר לצמיתות את הכפילויות האלה?", + "delete_key": "הסרת מפתח", + "delete_library": "הסרת ספרייה", + "delete_link": "הסרת קישור", + "delete_others": "הסרת אחרים", + "delete_shared_link": "הסרת קישור משותף", + "delete_tag": "הסרת תג", + "delete_tag_confirmation_prompt": "האם ברצונך להסיר תג {tagName}?", + "delete_user": "הסרת משתמש", + "deleted_shared_link": "קישור משותף הוסר", + "deletes_missing_assets": "מסיר נכסים שחסרים בדיסק", "description": "תיאור", "details": "פרטים", "direction": "כיוון", "disabled": "מושבת", "disallow_edits": "אל תאפשר עריכות", - "discord": "דיסקורד", - "discover": "גלה", - "dismiss_all_errors": "התעלם מכל השגיאות", - "dismiss_error": "התעלם מהשגיאה", - "display_options": "הצג אפשרויות", - "display_order": "סדר תצוגה", - "display_original_photos": "הצג תמונות מקוריות", + "discord": "Discord", + "discover": "גילוי", + "dismiss_all_errors": "התעלמות מכל השגיאות", + "dismiss_error": "התעלמות מהשגיאה", + "display_options": "הצגת אפשרויות", + "display_order": "סידור תצוגה", + "display_original_photos": "הצגת תמונות מקוריות", "display_original_photos_setting_description": "העדף להציג את התמונה המקורית בעת צפיית נכס במקום תמונות ממוזערות כאשר הנכס המקורי תומך בתצוגה בדפדפן. זה עלול לגרום לתמונות להיות מוצגות באיטיות.", "do_not_show_again": "אל תציג את ההודעה הזאת שוב", "documentation": "תיעוד", diff --git a/i18n/hu.json b/i18n/hu.json index f8de5bac258bc..7c1616c9a0a7e 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -29,7 +29,7 @@ "added_to_favorites_count": "{count, number} hozzáadva a kedvencekhez", "admin": { "add_exclusion_pattern_description": "Kihagyási minták (pattern) megadása. A *, ** és ? helyettesítő karakterek engedélyezettek. Pl. a \"Raw\" könyvtárban tárolt összes fájl kihagyásához használható a \"**/Raw/**\". Minden \".tif\" fájl kihagyása az összes mappában: \"**/*.tif\". Abszolút elérési útvonal kihagyása: \"/kihagyni/kivant/mappa/**\".", - "asset_offline_description": "Ez a külső képtárban lévő elem már nem található, ezért a lomtárba került. Ha a fájl a képtáron belül lett áthelyezve, akkor ellenőrizd, hogy továbbra is látható az idővonaladon. Az elem visszaállításához győződj meg róla, hogy az alábbi mappa az Immich számára elérhető, majd újra átfésültesd át a képtárat.", + "asset_offline_description": "Ez a külső képtárban lévő elem már nem található, ezért a lomtárba került. Ha a fájl a képtáron belül lett áthelyezve, akkor ellenőrizd, hogy továbbra is látható az idővonaladon. Az elem visszaállításához győződj meg róla, hogy az alábbi mappa az Immich számára elérhető, majd újra fésüld át a képtárat.", "authentication_settings": "Hitelesítési beállítások", "authentication_settings_description": "Jelszó, OAuth és egyéb hitelesítési beállítások kezelése", "authentication_settings_disable_all": "Biztosan letiltod az összes bejelentkezési módot? A bejelentkezés teljesen le lesz tiltva.", @@ -523,6 +523,10 @@ "date_range": "Dátum intervallum", "day": "Nap", "deduplicate_all": "Az Összes Deduplikálása", + "deduplication_criteria_1": "Kép mérete bájtokban", + "deduplication_criteria_2": "EXIF adatok mennyisége", + "deduplication_info": "Deduplikációs Infó", + "deduplication_info_description": "Az automatikus előválogatáshoz és a duplikátumok tömeges eltávolításához a következőket vizsgáljuk:", "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", diff --git a/i18n/id.json b/i18n/id.json index b76dc95e24dda..41ef0b008cae0 100644 --- a/i18n/id.json +++ b/i18n/id.json @@ -523,6 +523,10 @@ "date_range": "Jangka tanggal", "day": "Hari", "deduplicate_all": "Deduplikat Semua", + "deduplication_criteria_1": "Ukuran gambar dalam bita", + "deduplication_criteria_2": "Hitungan data EXIF", + "deduplication_info": "Info deduplikasi", + "deduplication_info_description": "Untuk memilih aset secara otomatis dan menghapus duplikat secara massal, kami melihat:", "default_locale": "Lokal Bawaan", "default_locale_description": "Format tanggal dan angka berdasarkan lokal peramban Anda", "delete": "Hapus", diff --git a/i18n/it.json b/i18n/it.json index 11e86c4a852f8..bd05f8e55591e 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -289,6 +289,8 @@ "transcoding_constant_rate_factor": "Fattore di rateo costante (-crf)", "transcoding_constant_rate_factor_description": "Livello di qualità video. I valori tipici sono 23 per H.264, 28 per HEVC, 31 per VP9 e 35 per AV1. Un valore inferiore indica una qualità migliore, ma produce file di dimensioni maggiori.", "transcoding_disabled_description": "Non transcodificare alcun video, potrebbe rompere la riproduzione su alcuni client", + "transcoding_encoding_options": "Opzioni di codifica", + "transcoding_encoding_options_description": "Imposta codecs, risoluzione, qualità ed altre opzioni per i video codificati", "transcoding_hardware_acceleration": "Accelerazione Hardware", "transcoding_hardware_acceleration_description": "Sperimentale; molto più veloce, ma avrà una qualità inferiore allo stesso bitrate", "transcoding_hardware_decoding": "Decodifica hardware", @@ -301,6 +303,8 @@ "transcoding_max_keyframe_interval": "Intervallo massimo dei keyframe", "transcoding_max_keyframe_interval_description": "Imposta la distanza massima tra i keyframe. Valori più bassi peggiorano l'efficienza di compressione, però migliorano i tempi di ricerca e possono migliorare la qualità nelle scene con movimenti rapidi. 0 imposta questo valore automaticamente.", "transcoding_optimal_description": "Video con risoluzione più alta rispetto alla risoluzione desiderata o in formato non accettato", + "transcoding_policy": "Politiche di transcodifica", + "transcoding_policy_description": "Imposta quando un video sarà transcodificato", "transcoding_preferred_hardware_device": "Dispositivo hardware preferito", "transcoding_preferred_hardware_device_description": "Si applica solo a VAAPI e QSV. Imposta il nodo DRI utilizzato per la transcodifica hardware.", "transcoding_preset_preset": "Preset (-preset)", @@ -309,7 +313,7 @@ "transcoding_reference_frames_description": "Il numero di frame da prendere in considerazione nel comprimere un determinato frame. Valori più alti migliorano l'efficienza di compressione, ma rallentano la codifica. 0 imposta questo valore automaticamente.", "transcoding_required_description": "Solo video che non sono in un formato accettato", "transcoding_settings": "Impostazioni Trascodifica Video", - "transcoding_settings_description": "Gestisci le impostazioni di risoluzione e codifica dei file video", + "transcoding_settings_description": "Gestisci quali video transcodificare e come processarli", "transcoding_target_resolution": "Risoluzione desiderata", "transcoding_target_resolution_description": "Risoluzioni più elevate possono preservare più dettagli ma richiedono più tempo per la codifica, producono file di dimensioni maggiori e possono ridurre la reattività dell'applicazione.", "transcoding_temporal_aq": "AQ temporale", @@ -322,7 +326,7 @@ "transcoding_transcode_policy_description": "Politica che determina quando un video deve essere trascodificato. I video HDR verranno sempre trascodificati (eccetto quando la trascodifica è disabilitata).", "transcoding_two_pass_encoding": "Codifica a due passaggi", "transcoding_two_pass_encoding_setting_description": "Trascodifica in due passaggi per produrre video codificati migliori. Quando il bitrate massimo è abilitato (necessario affinché funzioni con H.264 e HEVC), questa modalità utilizza un intervallo di bitrate basato sul bitrate massimo e ignora CRF. Per VP9, CRF può essere utilizzato se il bitrate massimo è disabilitato.", - "transcoding_video_codec": "Codifica Video", + "transcoding_video_codec": "Codec video", "transcoding_video_codec_description": "VP9 ha alta efficienza e compatibilità web, ma richiede più tempo per la trascodifica. HEVC ha prestazioni simili, ma una minore compatibilità web. H.264 è ampiamente compatibile e veloce da transcodificare, ma produce file molto più grandi. AV1 è il codec più efficiente, ma non è supportato sui dispositivi più vecchi.", "trash_enabled_description": "Abilita Funzionalità Cestino", "trash_number_of_days": "Numero di giorni", @@ -519,14 +523,18 @@ "date_range": "Intervallo di date", "day": "Giorno", "deduplicate_all": "De-duplica Tutti", + "deduplication_criteria_1": "Dimensione immagine in byte", + "deduplication_criteria_2": "Numero di dati EXIF", + "deduplication_info": "Informazioni di deduplicazione", + "deduplication_info_description": "Per preselezionare automaticamente gli asset e rimuovere i duplicati in massa, verifichiamo:", "default_locale": "Localizzazione preimpostata", - "default_locale_description": "Formatta la data e i numeri in base al locale del tuo browser", + "default_locale_description": "Formatta la data e i numeri in base alle impostazioni del tuo browser", "delete": "Elimina", "delete_album": "Elimina album", "delete_api_key_prompt": "Sei sicuro di voler eliminare questa chiave API?", "delete_duplicates_confirmation": "Sei sicuro di voler eliminare questi duplicati per sempre?", "delete_key": "Elimina chiave", - "delete_library": "Elimina Libreria", + "delete_library": "Elimina libreria", "delete_link": "Elimina link", "delete_others": "Elimina gli altri", "delete_shared_link": "Elimina link condiviso", @@ -755,6 +763,7 @@ "get_help": "Chiedi Aiuto", "getting_started": "Iniziamo", "go_back": "Torna indietro", + "go_to_folder": "Vai alla cartella", "go_to_search": "Vai alla ricerca", "group_albums_by": "Raggruppa album in base a...", "group_no": "Nessun raggruppamento", diff --git a/i18n/lt.json b/i18n/lt.json index cfb9701c1612c..7e24eedc743fd 100644 --- a/i18n/lt.json +++ b/i18n/lt.json @@ -1,5 +1,5 @@ { - "about": "Atnaujinti", + "about": "Apie", "account": "Paskyra", "account_settings": "Paskyros nustatymai", "acknowledge": "Patvirtinti", @@ -28,6 +28,7 @@ "added_to_favorites": "Pridėta prie mėgstamiausių", "added_to_favorites_count": "{count, plural, one {# pridėtas} few {# pridėti} other {# pridėta}} prie mėgstamiausių", "admin": { + "asset_offline_description": "Šis išorinės bibliotekos elementas nebepasiekiamas diske ir buvo perkeltas į šiukšliadėžę. Jei failas buvo perkeltas toje pačioje bibliotekoje, laiko skalėje rasite naują atitinkamą elementą. Jei norite šį elementą atkurti, įsitikinkite, kad Immich gali pasiekti failą žemiau nurodytu adresu, ir suvykdykite bibliotekos skanavimą.", "authentication_settings": "Autentifikavimo nustatymai", "authentication_settings_description": "Tvarkyti slaptažodžių, OAuth ir kitus autentifikavimo parametrus", "authentication_settings_disable_all": "Ar tikrai norite išjungti visus prisijungimo būdus? Prisijungimas bus visiškai išjungtas.", @@ -44,7 +45,7 @@ "confirm_reprocess_all_faces": "Ar tikrai norite iš naujo apdoroti visus veidus? Tai taip pat ištrins įvardytus asmenis.", "confirm_user_password_reset": "Ar tikrai norite iš naujo nustatyti {user} slaptažodį?", "disable_login": "Išjungti prisijungimą", - "duplicate_detection_job_description": "Vykdykite mašininį mokymąsi tam, kad aptiktumėte panašius vaizdus. Nuo šios funkcijos priklauso išmanioji paieška", + "duplicate_detection_job_description": "Vykdykite mašininį mokymąsi panašių vaizdų aptikimui. Priklauso nuo išmaniosios paieškos.", "exclusion_pattern_description": "Išimčių šablonai leidžia nepaisyti failų ir aplankų skenuojant jūsų biblioteką. Tai yra naudinga, jei turite aplankų su failais, kurių nenorite importuoti, pavyzdžiui, RAW failai.", "external_library_created_at": "Išorinė biblioteka (sukurta {date})", "external_library_management": "Išorinių bibliotekų tvarkymas", @@ -53,7 +54,7 @@ "facial_recognition_job_description": "Aptiktų veidų atpažinimas ir priskyrimas žmonėms. Šis darbas vykdomas pasibaigus \"veidų aptikimo\" darbui. \"Atstatyti\" (per)grupuoja visus aptiktus veidus. \"Trūkstami\" apdoroja jokiam žmogui dar nepriskirtus aptiktus veidus.", "failed_job_command": "Darbo {job} komanda {command} nepavyko", "force_delete_user_warning": "ĮSPĖJIMAS: Šis veiksmas iš karto pašalins naudotoją ir visą jo informaciją. Šis žingsnis nesugrąžinamas ir failų nebus galima atkurti.", - "forcing_refresh_library_files": "Priverstinai atnaujinami visi failai bilbiotekoje", + "forcing_refresh_library_files": "Priverstinai atnaujinami visi failai bibliotekoje", "image_format": "Formatas", "image_format_description": "WebP sukuria mažesnius failus nei JPEG, bet lėčiau juos apdoroja.", "image_prefer_embedded_preview": "Pageidautinai rodyti įterptą peržiūrą", @@ -111,7 +112,7 @@ "machine_learning_smart_search_description": "", "machine_learning_smart_search_enabled": "Įjungti išmaniąją paiešką", "machine_learning_smart_search_enabled_description": "Jei išjungta, vaizdai nebus užkoduoti išmaniajai paieškai.", - "machine_learning_url_description": "Mašininio mokymosi serverio URL", + "machine_learning_url_description": "Mašininio mokymosi serverio URL. Jei pateikta daugiau nei vienas URL, serveriai bus bandomi eilės tvarka nuo pirmo iki paskutinio tol, kol bus rastas vienas veikiantis serveris.", "manage_concurrency": "Tvarkyti lygiagretumą", "manage_log_settings": "", "map_dark_style": "Tamsioji tema", @@ -152,20 +153,21 @@ "notification_settings": "Pranešimų nustatymai", "notification_settings_description": "Tvarkyti pranešimų nustatymus, įskaitant el. pašto", "oauth_auto_launch": "Paleisti automatiškai", - "oauth_auto_launch_description": "", - "oauth_auto_register": "", - "oauth_auto_register_description": "", + "oauth_auto_launch_description": "Prisijungimo puslapyje automatiškai pradėti OAuth prisijungimo procesą", + "oauth_auto_register": "Automatinis registravimas", + "oauth_auto_register_description": "Automatiškai užregistruoti naujus naudotojus po prisijungimo per OAuth", "oauth_button_text": "Mygtuko tekstas", "oauth_client_id": "Kliento ID", "oauth_client_secret": "Kliento paslaptis", "oauth_enable_description": "Prisijungti su OAuth", - "oauth_issuer_url": "", - "oauth_mobile_redirect_uri": "", - "oauth_mobile_redirect_uri_override": "", - "oauth_mobile_redirect_uri_override_description": "", + "oauth_issuer_url": "Teikėjo URL", + "oauth_mobile_redirect_uri": "Mobiliojo peradresavimo URI", + "oauth_mobile_redirect_uri_override": "Mobiliojo peradresavimo URI pakeitimas", + "oauth_mobile_redirect_uri_override_description": "Įjunkite, kai OAuth teikėjas nepalaiko mobiliojo URI, tokio kaip '{callback}'", "oauth_scope": "", - "oauth_settings": "", + "oauth_settings": "OAuth", "oauth_settings_description": "Tvarkyti OAuth prisijungimo nustatymus", + "oauth_settings_more_details": "Detaliau apie šią funkciją galite paskaityti dokumentacijoje.", "oauth_signing_algorithm": "", "oauth_storage_label_claim": "", "oauth_storage_label_claim_description": "", @@ -173,6 +175,7 @@ "oauth_storage_quota_claim_description": "", "oauth_storage_quota_default": "", "oauth_storage_quota_default_description": "", + "offline_paths": "Nepasiekiami adresai", "offline_paths_description": "Šie rezultatai gali būti dėl rankinio failų ištrynimo, kurie nėra išorinės bibliotekos dalis.", "password_enable_description": "Prisijungti su el. paštu ir slaptažodžiu", "password_settings": "Prisijungimas slaptažodžiu", @@ -187,13 +190,13 @@ "reset_settings_to_recent_saved": "Nustatymų atstatymas į neseniai išsaugotus nustatymus", "send_welcome_email": "Siųsti sveikinimo el. laišką", "server_external_domain_settings": "Išorinis domenas", - "server_external_domain_settings_description": "", + "server_external_domain_settings_description": "Bendrinimo nuorodų domenas, įskaitant http(s)://", "server_settings": "Serverio nustatymai", "server_settings_description": "Tvarkyti serverio nustatymus", "server_welcome_message": "Sveikinimo pranešimas", "server_welcome_message_description": "Žinutė, rodoma prisijungimo puslapyje.", "sidecar_job_description": "", - "slideshow_duration_description": "", + "slideshow_duration_description": "Sekundžių skaičius, kiek viena nuotrauka rodoma", "smart_search_job_description": "Vykdykite mašininį mokymąsi bibliotekos elementų išmaniajai paieškai", "storage_template_enable_description": "", "storage_template_hash_verification_enabled": "", @@ -269,6 +272,7 @@ "trash_settings": "Šiukšliadėžės nustatymai", "trash_settings_description": "Tvarkyti šiukšliadėžės nustatymus", "untracked_files": "Nesekami failai", + "untracked_files_description": "Šie failai aplikacijos nesekami. Jie galėjo atsirasti dėl nepavykusio perkėlimo, nutraukto įkėlimo ar palikti per klaidą", "user_delete_delay_settings": "Ištrynimo delsa", "user_delete_delay_settings_description": "", "user_management": "Naudotojų valdymas", @@ -328,7 +332,9 @@ "asset_added_to_album": "Pridėta į albumą", "asset_adding_to_album": "Pridedama į albumą...", "asset_description_updated": "Elemento aprašymas buvo atnaujintas", - "asset_offline": "", + "asset_filename_is_offline": "Elementas {filename} nepasiekiamas", + "asset_offline": "Elementas nepasiekiamas", + "asset_offline_description": "Šis išorinis elementas neberandamas diske. Dėl pagalbos susisiekite su savo Immich administratoriumi.", "asset_uploaded": "Įkelta", "asset_uploading": "Įkeliama...", "assets": "Elementai", @@ -339,6 +345,7 @@ "assets_moved_to_trash_count": "{count, plural, one {# elementas perkeltas} few {# elementai perkelti} other {# elementų perkelta}} į šiukšliadėžę", "assets_permanently_deleted_count": "{count, plural, one {# elementas ištrintas} few {# elementai ištrinti} other {# elementų ištrinta}} visam laikui", "assets_removed_count": "{count, plural, one {Pašalintas # elementas} few {Pašalinti # elementai} other {Pašalinta # elementų}}", + "assets_restore_confirmation": "Ar tikrai norite atkurti visus šiukšliadėžėje esančius perkeltus elementus? Šio veiksmo atšaukti negalėsite! Pastaba: nepasiekiami elementai tokiu būdu atkurti nebus.", "assets_restored_count": "{count, plural, one {Atkurtas # elementas} few {Atkurti # elementai} other {Atkurta # elementų}}", "assets_were_part_of_album_count": "{count, plural, one {# elementas} few {# elementai} other {# elementų}} jau prieš tai buvo albume", "authorized_devices": "Autorizuoti įrenginiai", @@ -348,6 +355,9 @@ "birthdate_saved": "Sėkmingai išsaugota gimimo data", "blurred_background": "Neryškus fonas", "bugs_and_feature_requests": "Klaidų ir funkcijų užklausos", + "bulk_delete_duplicates_confirmation": "Ar tikrai norite ištrinti visus {count, plural, one {# besidubliuojantį elementą} few {# besidubliuojančius elementus} other {# besidubliuojančių elementų}}? Bus paliktas didžiausias kiekvienos grupės elementas ir negrįžtamai ištrinti kiti besidubliuojantys elementai. Šio veiksmo atšaukti negalėsite!", + "bulk_keep_duplicates_confirmation": "Ar tikrai norite palikti visus {count, plural, one {# besidubliuojantį elementą} few {# besidubliuojančius elementus} other {# besidubliuojančių elementų}}? Tokiu būdu nieko netrinant bus sutvarkytos visos dublikatų grupės.", + "bulk_trash_duplicates_confirmation": "Ar tikrai norite perkelti į šiukšliadėžę visus {count, plural, one {# besidubliuojantį elementą} few {# besidubliuojančius elementus} other {# besidubliuojančių elementų}}? Bus paliktas didžiausias kiekvienos grupės elementas ir į šiukšliadėžę perkelti kiti besidubliuojantys elementai.", "buy": "Įsigyti Immich", "camera": "Fotoaparatas", "camera_brand": "Fotoaparato prekės ženklas", @@ -382,7 +392,7 @@ "comments_are_disabled": "Komentarai yra išjungti", "confirm": "Patvirtinti", "confirm_admin_password": "Patvirtinti administratoriaus slaptažodį", - "confirm_delete_shared_link": "Ar tikrai norite ištrinti šią bendrinamą nuorodą?", + "confirm_delete_shared_link": "Ar tikrai norite ištrinti šią bendrinimo nuorodą?", "confirm_password": "Patvirtinti slaptažodį", "contain": "", "context": "Kontekstas", @@ -422,6 +432,11 @@ "date_of_birth_saved": "Gimimo data sėkmingai išsaugota", "date_range": "", "day": "Diena", + "deduplicate_all": "Šalinti visus dublikatus", + "deduplication_criteria_1": "Failo dydis baitais", + "deduplication_criteria_2": "EXIF metaduomenų įrašų skaičius", + "deduplication_info": "Dublikatų šalinimo informacija", + "deduplication_info_description": "Automatinis elementų parinkimas ir masinis dublikatų šalinimas atliekamas atsižvelgiant į:", "default_locale": "", "default_locale_description": "Formatuoti datas ir skaičius pagal jūsų naršyklės lokalę", "delete": "Ištrinti", @@ -431,11 +446,11 @@ "delete_key": "Ištrinti raktą", "delete_library": "Ištrinti biblioteką", "delete_link": "Ištrinti nuorodą", - "delete_shared_link": "Ištrinti bendrinamą nuorodą", + "delete_shared_link": "Ištrinti bendrinimo nuorodą", "delete_tag": "Ištrinti žymą", "delete_tag_confirmation_prompt": "Ar tikrai norite ištrinti žymą {tagName}?", "delete_user": "Ištrinti naudotoją", - "deleted_shared_link": "Bendrinama nuoroda ištrinta", + "deleted_shared_link": "Bendrinimo nuoroda ištrinta", "description": "Aprašymas", "details": "Detalės", "direction": "Kryptis", @@ -455,6 +470,7 @@ "download_settings": "Atsisiųsti", "downloading": "Siunčiama", "duplicates": "Dublikatai", + "duplicates_description": "Sutvarkykite kiekvieną elementų grupę nurodydami elementus, kurie yra dublikatai (jei tokių yra)", "duration": "Trukmė", "edit": "Redaguoti", "edit_album": "Redaguoti albumą", @@ -492,8 +508,8 @@ "error_removing_assets_from_album": "Klaida šalinant elementus iš albumo, patikrinkite konsolę dėl išsamesnės informacijos", "exclusion_pattern_already_exists": "Šis išimčių šablonas jau egzistuoja.", "failed_to_create_album": "Nepavyko sukurti albumo", - "failed_to_create_shared_link": "Nepavyko sukurti bendrinamos nuorodos", - "failed_to_edit_shared_link": "Nepavyko redaguoti bendrinamos nuorodos", + "failed_to_create_shared_link": "Nepavyko sukurti bendrinimo nuorodos", + "failed_to_edit_shared_link": "Nepavyko redaguoti bendrinimo nuorodos", "failed_to_load_people": "Nepavyko užkrauti žmonių", "failed_to_remove_product_key": "Nepavyko pašalinti produkto rakto", "failed_to_stack_assets": "Nepavyko sugrupuoti elementų", @@ -503,6 +519,7 @@ "profile_picture_transparent_pixels": "Profilio nuotrauka negali turėti permatomų pikselių. Prašome priartinti ir/arba perkelkite nuotrauką.", "quota_higher_than_disk_size": "Nustatyta kvota, viršija disko dydį", "unable_to_add_album_users": "Nepavyksta pridėti naudotojų prie albumo", + "unable_to_add_assets_to_shared_link": "Nepavyko į bendrinimo nuorodą pridėti elementų", "unable_to_add_comment": "Nepavyksta pridėti komentaro", "unable_to_add_exclusion_pattern": "Nepavyksta pridėti išimčių šablono", "unable_to_add_import_path": "Nepavyksta pridėti importavimo kelio", @@ -511,6 +528,7 @@ "unable_to_change_date": "Negalima pakeisti datos", "unable_to_change_location": "Negalima pakeisti vietos", "unable_to_change_password": "Negalima pakeisti slaptažodžio", + "unable_to_complete_oauth_login": "Nepavyko prisijungti su OAuth", "unable_to_connect": "Nepavyko prisijungti", "unable_to_connect_to_server": "Nepavyko prisijungti prie serverio", "unable_to_copy_to_clipboard": "Negalima kopijuoti į iškarpinę, įsitikinkite, kad prie puslapio prieinate per https", @@ -522,45 +540,48 @@ "unable_to_delete_asset": "", "unable_to_delete_exclusion_pattern": "Nepavyksta ištrinti išimčių šablono", "unable_to_delete_import_path": "Nepavyksta ištrinti importavimo kelio", - "unable_to_delete_shared_link": "Nepavyksta ištrinti bendrinimo nuorodos", + "unable_to_delete_shared_link": "Nepavyko ištrinti bendrinimo nuorodos", "unable_to_delete_user": "Nepavyksta ištrinti naudotojo", "unable_to_edit_exclusion_pattern": "Nepavyksta redaguoti išimčių šablono", "unable_to_edit_import_path": "Nepavyksta redaguoti išimčių kelio", "unable_to_empty_trash": "", "unable_to_enter_fullscreen": "Nepavyksta pereiti į viso ekrano režimą", "unable_to_exit_fullscreen": "Nepavyksta išeiti iš viso ekrano režimo", - "unable_to_get_shared_link": "Nepavyksta gauti bendrinamos nuorodos", + "unable_to_get_shared_link": "Nepavyko gauti bendrinimo nuorodos", "unable_to_hide_person": "Nepavyksta paslėpti žmogaus", + "unable_to_link_oauth_account": "Nepavyko susieti su OAuth paskyra", "unable_to_load_album": "Nepavyksta užkrauti albumo", "unable_to_load_asset_activity": "", "unable_to_load_items": "", "unable_to_load_liked_status": "", "unable_to_log_out_all_devices": "Nepavyksta atjungti visų įrenginių", "unable_to_log_out_device": "Nepavyksta atjungti įrenginio", - "unable_to_login_with_oauth": "Nepavyksta prisijungti su OAuth", + "unable_to_login_with_oauth": "Nepavyko prisijungti su OAuth", "unable_to_play_video": "Nepavyksta paleisti vaizdo įrašo", "unable_to_refresh_user": "Nepavyksta atnaujinti naudotojo", "unable_to_remove_album_users": "", "unable_to_remove_api_key": "Nepavyko pašalinti API rakto", + "unable_to_remove_assets_from_shared_link": "Nepavyko iš bendrinimo nuorodos pašalinti elementų", + "unable_to_remove_deleted_assets": "Nepavyko pašalinti nepasiekiamų elementų", "unable_to_remove_library": "Nepavyksta pašalinti bibliotekos", "unable_to_remove_partner": "Nepavyksta pašalinti partnerio", "unable_to_remove_reaction": "Nepavyksta pašalinti reakcijos", "unable_to_repair_items": "", "unable_to_reset_password": "", - "unable_to_resolve_duplicate": "", + "unable_to_resolve_duplicate": "Nepavyko sutvarkyti dublikatų", "unable_to_restore_assets": "", "unable_to_restore_trash": "", "unable_to_restore_user": "", "unable_to_save_album": "", "unable_to_save_name": "", - "unable_to_save_profile": "", + "unable_to_save_profile": "Nepavyko išsaugoti profilio", "unable_to_save_settings": "Nepavyksta išsaugoti nustatymų", "unable_to_scan_libraries": "Nepavyksta nuskaityti bibliotekų", "unable_to_scan_library": "Nepavyksta nuskaityti bibliotekos", "unable_to_set_feature_photo": "Nepavyksta nustatyti mėgstamiausios nuotraukos", "unable_to_set_profile_picture": "Nepavyksta nustatyti profilio nuotraukos", "unable_to_submit_job": "", - "unable_to_trash_asset": "", + "unable_to_trash_asset": "Nepavyko perkelti į šiukšliadėžę", "unable_to_unlink_account": "", "unable_to_update_library": "", "unable_to_update_location": "", @@ -569,7 +590,7 @@ "unable_to_upload_file": "Nepavyksta įkelti failo" }, "exif": "Exif", - "exit_slideshow": "", + "exit_slideshow": "Išeiti iš skaidrių peržiūros", "expand_all": "Išskleisti viską", "expire_after": "", "expired": "Nebegalioja", @@ -585,6 +606,8 @@ "favorite_or_unfavorite_photo": "Įtraukti prie arba pašalinti iš mėgstamiausių", "favorites": "Mėgstamiausi", "feature_photo_updated": "", + "features": "Funkcijos", + "features_setting_description": "Valdyti aplikacijos funkcijas", "file_name": "Failo pavadinimas", "file_name_or_extension": "Failo pavadinimas arba plėtinys", "filename": "", @@ -592,6 +615,7 @@ "filter_people": "Filtruoti žmones", "fix_incorrect_match": "", "folders": "Aplankai", + "folders_feature_description": "Peržiūrėkite failų sistemoje esančias nuotraukas ir vaizdo įrašus aplankų rodinyje", "forward": "", "general": "", "get_help": "Gauti pagalbos", @@ -619,7 +643,7 @@ "in_archive": "Archyve", "include_archived": "Įtraukti archyvuotus", "include_shared_albums": "Įtraukti bendrinamus albumus", - "include_shared_partner_assets": "", + "include_shared_partner_assets": "Įtraukti partnerio pasidalintus elementus", "individual_share": "", "info": "Informacija", "interval": { @@ -647,8 +671,8 @@ "library_options": "Bibliotekos pasirinktys", "light": "", "link_options": "Nuorodų parinktys", - "link_to_oauth": "", - "linked_oauth_account": "", + "link_to_oauth": "Susieti su OAuth", + "linked_oauth_account": "Susieta OAuth paskyra", "list": "Sąrašas", "loading": "Kraunama", "loading_search_results_failed": "Nepavyko užkrauti paieškos rezultatų", @@ -664,13 +688,13 @@ "loop_videos": "Kartoti vaizdo įrašus", "loop_videos_description": "", "make": "Gamintojas", - "manage_shared_links": "Bendrai naudojamų nuorodų tvarkymas", + "manage_shared_links": "Bendrinimo nuorodų tvarkymas", "manage_sharing_with_partners": "Valdyti dalijimąsi su partneriais", "manage_the_app_settings": "Valdyti programos nustatymus", "manage_your_account": "Valdyti savo paskyrą", "manage_your_api_keys": "Valdyti savo API raktus", "manage_your_devices": "Valdyti prijungtus įrenginius", - "manage_your_oauth_connection": "", + "manage_your_oauth_connection": "Tvarkyti OAuth prisijungimą", "map": "Žemėlapis", "map_marker_with_image": "", "map_settings": "Žemėlapio nustatymai", @@ -710,7 +734,7 @@ "no_albums_message": "Sukurkite albumą nuotraukoms ir vaizdo įrašams tvarkyti", "no_albums_with_name_yet": "Atrodo, kad dar neturite albumų su šiuo pavadinimu.", "no_albums_yet": "Atrodo, kad dar neturite albumų.", - "no_archived_assets_message": "", + "no_archived_assets_message": "Suarchyvuokite nuotraukas ir vaizdo įrašus, kad jie nebūtų rodomi nuotraukų rodinyje", "no_assets_message": "SPUSTELĖKITE NORĖDAMI ĮKELTI PIRMĄJĄ NUOTRAUKĄ", "no_duplicates_found": "Dublikatų nerasta.", "no_exif_info_available": "", @@ -728,9 +752,10 @@ "notification_toggle_setting_description": "Įjungti el. pašto pranešimus", "notifications": "Pranešimai", "notifications_setting_description": "Tvarkyti pranešimus", - "oauth": "", + "oauth": "OAuth", "official_immich_resources": "Oficialūs Immich ištekliai", "offline": "Neprisijungęs", + "offline_paths": "Nepasiekiami adresai", "ok": "Ok", "oldest_first": "Seniausias pirmas", "onboarding_welcome_user": "Sveiki atvykę, {user}", @@ -744,7 +769,7 @@ "other": "", "other_devices": "Kiti įrenginiai", "other_variables": "Kiti kintamieji", - "owned": "", + "owned": "Nuosavi", "owner": "Savininkas", "partner": "Partneris", "partner_can_access": "{partner} gali naudotis", @@ -764,12 +789,13 @@ "path": "Kelias", "pattern": "", "pause": "Sustabdyti", - "pause_memories": "", + "pause_memories": "Pristabdyti atsiminimus", "paused": "Sustabdyta", "pending": "Laukiama", "people": "Asmenys", "people_edits_count": "{count, plural, one {Redaguotas # asmuo} few {Redaguoti # asmenys} other {Redaguota # asmenų}}", - "people_sidebar_description": "", + "people_feature_description": "Peržiūrėkite nuotraukas ir vaizdo įrašus sugrupuotus pagal asmenis", + "people_sidebar_description": "Rodyti asmenų rodinio nuorodą šoninėje juostoje", "permanent_deletion_warning": "", "permanent_deletion_warning_setting_description": "", "permanently_delete": "Ištrinti visam laikui", @@ -784,7 +810,7 @@ "place": "Vieta", "places": "Vietos", "play": "", - "play_memories": "", + "play_memories": "Leisti atsiminimus", "play_motion_photo": "", "play_or_pause_video": "", "port": "", @@ -794,6 +820,7 @@ "previous_memory": "", "previous_or_next_photo": "", "primary": "", + "profile_image_of_user": "{user} profilio nuotrauka", "profile_picture_set": "Profilio nuotrauka nustatyta.", "public_album": "Viešas albumas", "public_share": "", @@ -830,18 +857,27 @@ "purchase_settings_server_activated": "Serverio produkto raktas yra tvarkomas administratoriaus", "rating": "Įvertinimas žvaigždutėmis", "rating_count": "{count, plural, one {# įvertinimas} few {# įvertinimai} other {# įvertinimų}}", + "rating_description": "Rodyti EXIF įvertinimus informacijos skydelyje", "reaction_options": "", "read_changelog": "", "recent": "", "recent_searches": "", "refresh": "Atnaujinti", + "refresh_encoded_videos": "Perkrauti apdorotus vaizdo įrašus", + "refresh_faces": "Perkrauti veidus", + "refresh_metadata": "Perkrauti metaduomenis", + "refresh_thumbnails": "Perkrauti miniatiūras", "refreshed": "Atnaujinta", - "refreshes_every_file": "", + "refreshes_every_file": "Iš naujo perskaito visus esamus ir naujai pridėtus failus", + "refreshing_encoded_video": "Perkraunamas apdorotas vaizdo įrašas", + "refreshing_faces": "Perkraunami veidai", + "refreshing_metadata": "Perkraunami metaduomenys", "remove": "Pašalinti", + "remove_assets_shared_link_confirmation": "Ar tikrai norite pašalinti {count, plural, one {# elementą} few {# elementus} other {# elementų}} iš šios bendrinimo nuorodos?", "remove_deleted_assets": "", "remove_from_album": "Pašalinti iš albumo", "remove_from_favorites": "Pašalinti iš mėgstamiausių", - "remove_from_shared_link": "", + "remove_from_shared_link": "Pašalinti iš bendrinimo nuorodos", "remove_user": "Pašalinti naudotoją", "removed_api_key": "Pašalintas API Raktas: {name}", "removed_from_archive": "Pašalinta iš archyvo", @@ -850,18 +886,19 @@ "removed_tagged_assets": "Žyma pašalinta iš {count, plural, one {# elemento} other {# elementų}}", "rename": "Pervadinti", "repair": "Pataisyti", - "repair_no_results_message": "", - "replace_with_upload": "", + "repair_no_results_message": "Nesekami ir trūkstami failai bus rodomi čia", + "replace_with_upload": "Pakeisti naujai įkeltu failu", "require_password": "Reikalauti slaptažodžio", "reset": "Atstatyti", "reset_password": "", "reset_people_visibility": "", - "resolved_all_duplicates": "Išspręsti visi dublikatai", + "resolve_duplicates": "Sutvarkyti dublikatus", + "resolved_all_duplicates": "Sutvarkyti visi dublikatai", "restore": "Atkurti", "restore_all": "Atkurti visus", "restore_user": "Atkurti naudotoją", "retry_upload": "", - "review_duplicates": "", + "review_duplicates": "Peržiūrėti dublikatus", "role": "", "save": "Išsaugoti", "saved_api_key": "Išsaugotas API raktas", @@ -898,9 +935,11 @@ "select_avatar_color": "Pasirinkti avataro spalvą", "select_face": "Pasirinkti veidą", "select_featured_photo": "Pasirinkti rodomą nuotrauką", + "select_keep_all": "Visus pažymėti \"Palikti\"", "select_library_owner": "Pasirinkti bibliotekos savininką", "select_new_face": "", "select_photos": "", + "select_trash_all": "Visus pažymėti \"Išmesti\"", "selected": "Pasirinkta", "selected_count": "{count, plural, one {# pasirinktas} few {# pasirinkti} other {# pasirinktų}}", "send_message": "Siųsti žinutę", @@ -911,28 +950,29 @@ "server_version": "Serverio versija", "set": "Nustatyti", "set_as_album_cover": "", - "set_as_profile_picture": "", + "set_as_profile_picture": "Nustatyti kaip profilio nuotrauką", "set_date_of_birth": "Nustatyti gimimo datą", "set_profile_picture": "Nustatyti profilio nuotrauką", - "set_slideshow_to_fullscreen": "", + "set_slideshow_to_fullscreen": "Nustatyti skaidrių peržiūrą per visą ekraną", "settings": "Nustatymai", "settings_saved": "", "share": "Dalintis", - "shared": "", + "shared": "Bendrinami", "shared_by": "", "shared_by_you": "", - "shared_links": "", + "shared_link_options": "Bendrinimo nuorodos parametrai", + "shared_links": "Bendrinimo nuorodos", "shared_photos_and_videos_count": "{assetCount, plural, one {# bendrinama nuotrauka ir vaizdo įrašas} few {# bendrinamos nuotraukos ir vaizdo įrašai} other {# bendrinamų nuotraukų ir vaizdo įrašų}}", "shared_with_partner": "Pasidalinta su {partner}", "sharing": "Dalijimasis", "sharing_enter_password": "Norėdami peržiūrėti šį puslapį, įveskite slaptažodį.", - "sharing_sidebar_description": "", + "sharing_sidebar_description": "Rodyti bendrinimo rodinio nuorodą šoninėje juostoje", "show_album_options": "Rodyti albumo parinktis", "show_file_location": "Rodyti rinkmenos vietą", "show_gallery": "Rodyti galeriją", "show_hidden_people": "", - "show_in_timeline": "", - "show_in_timeline_setting_description": "", + "show_in_timeline": "Rodyti laiko skalėje", + "show_in_timeline_setting_description": "Rodyti šio naudotojo nuotraukas ir vaizdo įrašus mano laiko skalėje", "show_keyboard_shortcuts": "", "show_metadata": "Rodyti metaduomenis", "show_or_hide_info": "Rodyti arba slėpti informaciją", @@ -940,15 +980,18 @@ "show_person_options": "", "show_progress_bar": "", "show_search_options": "Rodyti paieškos parinktis", + "show_slideshow_transition": "Rodyti perėjimą tarp skaidrių", "show_supporter_badge": "Rėmėjo ženklelis", "show_supporter_badge_description": "Rodyti rėmėjo ženklelį", "shuffle": "", + "sidebar": "Šoninė juosta", + "sidebar_display_description": "Rodyti rodinio nuorodą šoninėje juostoje", "sign_out": "Atsijungti", "sign_up": "Užsiregistruoti", "size": "Dydis", "skip_to_content": "Pereiti prie turinio", - "slideshow": "Skaidrės", - "slideshow_settings": "", + "slideshow": "Skaidrių peržiūra", + "slideshow_settings": "Skaidrių peržiūros nustatymai", "sort_albums_by": "", "sort_created": "Sukūrimo data", "sort_modified": "Keitimo data", @@ -978,6 +1021,7 @@ "sync": "Sinchronizuoti", "tag": "Žyma", "tag_created": "Sukurta žyma: {tag}", + "tag_feature_description": "Peržiūrėkite nuotraukas ir vaizdo įrašus sugrupuotus pagal sužymėtas temas", "tag_not_found_question": "Nerandate žymos? Sukurti naują žymą.", "tag_updated": "Atnaujinta žyma: {tag}", "tagged_assets": "Žyma pridėta prie {count, plural, one {# elemento} other {# elementų}}", @@ -986,18 +1030,20 @@ "theme": "Tema", "theme_selection": "", "theme_selection_description": "", - "time_based_memories": "", + "time_based_memories": "Atsiminimai pagal laiką", + "timeline": "Laiko skalė", "timezone": "Laiko juosta", "to_archive": "Archyvuoti", "to_change_password": "Pakeisti slaptažodį", "to_favorite": "Įtraukti prie mėgstamiausių", + "to_trash": "Išmesti", "toggle_settings": "", "toggle_theme": "", "total_usage": "", "trash": "Šiukšliadėžė", - "trash_all": "Ištrinti visus", - "trash_count": "Šiukšliadėžė {count, number}", - "trash_no_results_message": "", + "trash_all": "Perkelti visus į šiukšliadėžę", + "trash_count": "Perkelti {count, number} į šiukšliadėžę", + "trash_no_results_message": "Į šiukšliadėžę perkeltos nuotraukos ir vaizdo įrašai bus rodomi čia.", "trashed_items_will_be_permanently_deleted_after": "Į šiukšliadėžę perkelti elementai bus visam laikui ištrinti po {days, plural, one {# dienos} other {# dienų}}.", "type": "Tipas", "unarchive": "Išarchyvuoti", @@ -1006,22 +1052,26 @@ "unhide_person": "", "unknown": "", "unknown_year": "Nežinomi metai", - "unlink_oauth": "", - "unlinked_oauth_account": "", + "unlink_oauth": "Atsieti OAuth", + "unlinked_oauth_account": "Atsieta OAuth paskyra", "unnamed_album_delete_confirmation": "Ar tikrai norite ištrinti šį albumą?", "unsaved_change": "Neišsaugoti pakeitimai", "unselect_all": "", "unselect_all_duplicates": "Atžymėti visus dublikatus", "unstack": "Išgrupuoti", "unstacked_assets_count": "{count, plural, one {Išgrupuotas # elementas} few {Išgrupuoti # elementai} other {Išgrupuota # elementų}}", + "untracked_files": "Nesekami failai", + "untracked_files_decription": "Šie failai aplikacijos nesekami. Jie galėjo atsirasti dėl nepavykusio perkėlimo, nutraukto įkėlimo ar palikti per klaidą", "up_next": "", "updated_password": "Slaptažodis atnaujintas", "upload": "Įkelti", "upload_concurrency": "", + "upload_errors": "Įkėlimas įvyko su {count, plural, one {# klaida} few {# klaidomis} other {# klaidų}}, norėdami pamatyti naujai įkeltus elementus perkraukite puslapį.", "upload_progress": "Liko {remaining, number} - Apdorota {processed, number}/{total, number}", "upload_status_duplicates": "Dublikatai", "upload_status_errors": "Klaidos", "upload_status_uploaded": "Įkelta", + "upload_success": "Įkėlimas pavyko, norėdami pamatyti naujai įkeltus elementus perkraukite puslapį.", "url": "URL", "usage": "", "user": "Naudotojas", @@ -1031,7 +1081,7 @@ "user_usage_stats_description": "Žiūrėti paskyros naudojimo statistiką", "username": "Naudotojo vardas", "users": "Naudotojai", - "utilities": "Priemonės", + "utilities": "Įrankiai", "validate": "Validuoti", "variables": "Kintamieji", "version": "Versija", @@ -1042,11 +1092,12 @@ "video_hover_setting_description": "Atkurti vaizdo įrašo miniatiūrą, kai pelė užvedama ant elemento. Net ir išjungus, atkūrimą galima pradėti užvedus pelės žymeklį ant atkūrimo piktogramos.", "videos": "Video", "videos_count": "{count, plural, one {# vaizdo įrašas} few {# vaizdo įrašai} other {# vaizdo įrašų}}", - "view": "Rodyti", - "view_album": "Rodyti albumą", + "view": "Žiūrėti", + "view_album": "Žiūrėti albumą", "view_all": "Peržiūrėti viską", "view_all_users": "Peržiūrėti visus naudotojus", - "view_links": "Rodyti nuorodas", + "view_in_timeline": "Žiūrėti laiko skalėje", + "view_links": "Žiūrėti nuorodas", "view_next_asset": "", "view_previous_asset": "", "view_stack": "Peržiūrėti grupę", @@ -1055,6 +1106,8 @@ "week": "Savaitė", "welcome_to_immich": "Sveiki atvykę į Immich", "year": "Metai", + "years_ago": "Prieš {years, plural, one {# metus} other {# metų}}", "yes": "Taip", + "you_dont_have_any_shared_links": "Bendrinimo nuorodų neturite", "zoom_image": "Priartinti vaizdą" } diff --git a/i18n/lv.json b/i18n/lv.json index c6cbc9c3b75a7..3d64db2b9fcca 100644 --- a/i18n/lv.json +++ b/i18n/lv.json @@ -35,13 +35,13 @@ "authentication_settings_reenable": "Lai atkārtoti iespējotu, izmantojiet Servera Komandu.", "background_task_job": "Fona Uzdevumi", "check_all": "Pārbaudīt Visu", - "cleared_jobs": "Notīrīti darbi priekš: {job}", + "cleared_jobs": "Notīrīti uzdevumi priekš: {job}", "config_set_by_file": "Konfigurāciju pašlaik iestata konfigurācijas fails", "confirm_delete_library": "Vai tiešām vēlaties dzēst {library} bibliotēku?", "confirm_email_below": "Lai apstiprinātu, zemāk ierakstiet “{email}”", "confirm_reprocess_all_faces": "Vai tiešām vēlaties atkārtoti apstrādāt visas sejas? Tas arī atiestatīs cilvēkus ar vārdiem.", "confirm_user_password_reset": "Vai tiešām vēlaties atiestatīt lietotāja {user} paroli?", - "create_job": "Izveidot darbu", + "create_job": "Izveidot uzdevumu", "cron_expression": "Cron izteiksme", "disable_login": "Atspējot pieteikšanos", "duplicate_detection_job_description": "Palaidiet mašīnmācīšanos uz līdzekļiem, lai noteiktu līdzīgus attēlus. Paļaujas uz Viedo Meklēšanu", @@ -59,10 +59,10 @@ "image_settings": "Attēla Iestatījumi", "image_settings_description": "Ģenerēto attēlu kvalitātes un izšķirtspējas pārvaldība", "image_thumbnail_title": "Sīktēlu iestatījumi", - "job_created": "Darbs izveidots", - "job_settings": "", - "job_settings_description": "", - "job_status": "Darbu statuss", + "job_created": "Uzdevums izveidots", + "job_settings": "Uzdevumu iestatījumi", + "job_settings_description": "Pārvaldīt uzdevumu izpildes vienlaicīgumu", + "job_status": "Uzdevumu statuss", "library_deleted": "Bibliotēka dzēsta", "library_scanning": "", "library_scanning_description": "", @@ -167,6 +167,7 @@ "repair_all": "Salabot visu", "require_password_change_on_login": "Pieprasīt lietotājam mainīt paroli pēc pirmās pieteikšanās", "scanning_library": "Skenē bibliotēku", + "search_jobs": "Meklēt uzdevumus...", "server_external_domain_settings": "", "server_external_domain_settings_description": "", "server_settings": "Servera iestatījumi", @@ -527,7 +528,7 @@ }, "invite_people": "Ielūgt cilvēkus", "invite_to_album": "Uzaicināt albumā", - "jobs": "Darbi", + "jobs": "Uzdevumi", "keep": "Paturēt", "keep_all": "Paturēt visus", "keyboard_shortcuts": "Tastatūras saīsnes", @@ -873,6 +874,8 @@ "validate": "", "variables": "", "version": "Versija", + "version_announcement_message": "Sveiki! Ir pieejama jauna Immich versija. Lūdzu, veltiet laiku, lai izlasītu laidiena piezīmes un pārliecinātos, ka jūsu iestatījumi ir atjaunināti, lai novērstu jebkādu nepareizu konfigurāciju, jo īpaši, ja izmantojat WatchTower vai citu mehānismu, kas automātiski atjaunina jūsu Immich instanci.", + "version_history": "Versiju vēsture", "video": "Videoklips", "video_hover_setting_description": "", "videos": "Videoklipi", diff --git a/i18n/ms.json b/i18n/ms.json index d35b8ce98c234..d03ef614b4a53 100644 --- a/i18n/ms.json +++ b/i18n/ms.json @@ -141,7 +141,7 @@ "map_implications": "Ciri peta bergantung pada perkhidmatan jubin luaran (tiles.immich.cloud)", "map_light_style": "Tema terang", "map_manage_reverse_geocoding_settings": "Urus tetapan Geocoding Songsang", - "map_reverse_geocoding": "Geocoding Terbalik", + "map_reverse_geocoding": "Geokoding Sonsang", "map_reverse_geocoding_enable_description": "Dayakan pengekodan geo terbalik", "map_reverse_geocoding_settings": "Tetapan Pengekodan Geo Terbalik", "map_settings": "Peta", @@ -229,8 +229,63 @@ "server_settings_description": "Urus tetapan pelayan", "server_welcome_message": "Mesej alu-aluan", "server_welcome_message_description": "Mesej yang dipaparkan pada halaman log masuk.", - "sidecar_job": "Metadata kereta sisi" + "sidecar_job": "Metadata kereta sisi", + "slideshow_duration_description": "Bilangan saat untuk memaparkan setiap imej", + "smart_search_job_description": "Jalankan pembelajaran mesin pada aset-aset untuk menyokong carian pintar", + "storage_template_date_time_description": "Cap masa penciptaan aset digunakan untuk maklumat masa dan tarikh", + "storage_template_date_time_sample": "Contoh masa {date}", + "storage_template_enable_description": "Dayakan enjin templat storan", + "storage_template_hash_verification_enabled": "Pengesahan hac didayakan", + "storage_template_hash_verification_enabled_description": "Mendayakan pengesahan hac, jangan lumpuhkan melainkan anda pasti akan implikasinya", + "storage_template_migration": "Penghijrahan templat storan", + "storage_template_migration_description": "Gunakan {template} semasa pada aset-aset yang dimuat naik sebelum ini", + "storage_template_migration_info": "Perubahan templat hanya akan digunakan pada aset baharu. Untuk menggunakan templat secara retroaktif pada aset-aset yang dimuat naik sebelum ini, jalankan {job}.", + "storage_template_migration_job": "Kerja Migrasi Templat Storan", + "storage_template_more_details": "Untuk butiran lanjut tentang ciri ini, rujuk kepada Templat Storan dan implikasi", + "storage_template_settings": "Templat Storan", + "theme_settings_description": "Urus penyesuaian antara muka web Immich", + "thumbnail_generation_job": "Jana Imej Kenit", + "thumbnail_generation_job_description": "Janakan imej kenit yang besar, kecil, dan kabur untuk setiap aset, serta imej kenit untuk setiap orang" }, + "deduplication_criteria_1": "Saiz imej dalam bait", + "deduplication_criteria_2": "Kiraan data EXIF", + "deduplication_info": "Maklumat Pendeduplikasian", + "deduplication_info_description": "Untuk prapilih aset secara automatik dan mengalih keluar pendua secara pukal, kami melihat pada:", + "default_locale": "Tempatan Lalai", + "delete": "Padam", + "delete_album": "Padam album", + "delete_api_key_prompt": "Adakah anda pasti mahu memadam kunci API ini?", + "delete_duplicates_confirmation": "Adakah anda pasti mahu memadam pendua ini secara kekal?", + "delete_key": "Padam kunci", + "delete_library": "Padam Pustaka", + "delete_link": "Padam pautan", + "delete_others": "Padam yang lain", + "delete_shared_link": "Padam pautan yang dikongsi", + "delete_tag": "Padam tag", + "delete_tag_confirmation_prompt": "Adakah anda pasti mahu memadam tag {tagName}?", + "delete_user": "Padam pengguna", + "deleted_shared_link": "Pautan kongsi yang dipadamkan", + "deletes_missing_assets": "Memadamkan aset yang hilang daripada cakera", + "description": "Penerangan", + "details": "Butiran", + "direction": "Arah", + "disabled": "Dilumpuhkan", + "disallow_edits": "Tolak pengeditan", + "discord": "Perselisihan", + "discover": "Terokai", + "dismiss_all_errors": "Tolak semua ralat", + "dismiss_error": "Tolak ralat", + "display_options": "Pilihan paparan", + "display_order": "Tertib paparan", + "display_original_photos": "Paparkan foto asal", + "display_original_photos_setting_description": "Mengutamakan pemaparan foto asal apabila melihat aset daripada imej kecil apabila aset asal serasi web. Ini boleh menyebabkan kelajuan paparan foto yang lebih perlahan.", + "do_not_show_again": "Jangan tunjukkan mesej ini lagi", + "documentation": "Dokumentasi", + "done": "Selesai", + "download": "Muat Turun", + "download_settings": "Muat Turun", + "download_settings_description": "Urus tetapan yang berkaitan dengan muat turun aset", + "downloading": "Memuat turun", "timeline": "Garis masa", "total": "Jumlah", "user_usage_stats": "Statistik penggunaan akaun", diff --git a/i18n/nb_NO.json b/i18n/nb_NO.json index 9bd4340fc8488..f0bc9d4f6de59 100644 --- a/i18n/nb_NO.json +++ b/i18n/nb_NO.json @@ -44,12 +44,14 @@ "cleared_jobs": "Ryddet opp jobber for: {job}", "config_set_by_file": "Konfigurasjonen er for øyeblikket satt av en konfigurasjonsfil", "confirm_delete_library": "Er du sikker på at du vil slette biblioteket {library}?", - "confirm_delete_library_assets": "Er du sikker på at du vil slette dette biblioteket? Dette vil slette alle {count} tilhørende eiendeler fra Immich og kan ikke angres. Filene vil forbli på disken.", + "confirm_delete_library_assets": "Er du sikker på at du vil slette dette biblioteket? Dette vil slette alle {count, plural, one {# contained asset} other {all # contained assets}} tilhørende eiendeler fra Immich og kan ikke angres. Filene vil forbli på disken.", "confirm_email_below": "For å bekrefte, skriv inn \"{email}\" nedenfor", "confirm_reprocess_all_faces": "Er du sikker på at du vil behandle alle ansikter på nytt? Dette vil også fjerne navngitte personer.", "confirm_user_password_reset": "Er du sikker på at du vil tilbakestille passordet til {user}?", "create_job": "Lag jobb", "cron_expression": "Cron uttrykk", + "cron_expression_description": "Still inn skanneintervallet med cron-formatet. For mer informasjon henvises til f.eks. Crontab Guru", + "cron_expression_presets": "Forhåndsinnstillinger for Cron-uttrykk", "disable_login": "Deaktiver innlogging", "duplicate_detection_job_description": "Kjør maskinlæring på filer for å oppdage lignende bilder. Krever bruk av Smart Search", "exclusion_pattern_description": "Ekskluderingsmønstre lar deg ignorere filer og mapper når du skanner biblioteket ditt. Dette er nyttig hvis du har mapper som inneholder filer du ikke vil importere, for eksempel RAW-filer.", @@ -67,12 +69,19 @@ "image_prefer_embedded_preview_setting_description": "Bruk innebygd forhåndsvisning i RAW-bilder som inndata til bildebehandling når tilgjengelig. Dette kan gi mer nøyaktige farger for noen bilder, men kvaliteten er avhengig av kamera og bildet kan ha komprimeringsartefakter.", "image_prefer_wide_gamut": "Foretrekk bredt fargespekter", "image_prefer_wide_gamut_setting_description": "Bruk Display P3 for miniatyrbilder. Dette bevarer glød bedre i bilder med bredt fargerom, men det kan hende bilder ser annerledes ut på gamle enheter med en gammel nettleserversjon. sRBG bilder beholdes som sRGB for å unngå fargeforskyvninger.", + "image_preview_description": "Mellomstort bilde med strippet metadata, brukt når du ser på en enkelt ressurs og for maskinlæring", + "image_preview_quality_description": "Kvalitet på forhåndsvisning fra 1-100. Høyere er bedre, men genererer større filer og kan redusere hastigheten på systemet. Ved for lav verdi kan det påvirke kvaliteten på maskinlæringen.", "image_preview_title": "Forhåndsvisningsinnstillinger", "image_quality": "Kvalitet", "image_resolution": "Oppløsning", + "image_resolution_description": "Høyere oppløsninger kan bevare flere detaljer, men det tar lengre tid å kode, har større filstørrelser og kan redusere appresponsen.", "image_settings": "Bildeinnstilliinger", "image_settings_description": "Administrer kvalitet og oppløsning på genererte bilder", + "image_thumbnail_description": "Små miniatyrbilder med strippet metadata, brukt når du ser på grupper av bilder som hovedtidslinjen", + "image_thumbnail_quality_description": "Miniatyrbildekvalitet fra 1-100. Høyere er bedre, men produserer større filer og kan redusere appens respons.", + "image_thumbnail_title": "Miniatyrbilde oppsett", "job_concurrency": "{job} samtidighet", + "job_created": "Oppgave laget", "job_not_concurrency_safe": "Denne jobben er ikke samtidlighet sikker.", "job_settings": "Jobbinnstillinger", "job_settings_description": "Administrer parallellkjøring for jobber", @@ -129,7 +138,9 @@ "map_enable_description": "Aktiver kartfunksjoner", "map_gps_settings": "Kart & GPS Innstillinger", "map_gps_settings_description": "Administrer innstillinger for kart og GPS (Reversert geokoding)", + "map_implications": "Kartfunksjonen er avhengig av en ekstern bilde tjeneste (tiles.immich.cloud)", "map_light_style": "Lys stil", + "map_manage_reverse_geocoding_settings": "Administrer instillinger for Omvendt Geokoding", "map_reverse_geocoding": "Omvendt geokoding", "map_reverse_geocoding_enable_description": "Aktiver omvendt geokoding", "map_reverse_geocoding_settings": "Innstillinger for omvendt geokoding", @@ -138,6 +149,8 @@ "map_style_description": "URL til et style.json-karttema", "metadata_extraction_job": "Hent metadata", "metadata_extraction_job_description": "Hent metadatainformasjon fra hver fil, for eksempel GPS-posisjon og oppløsning", + "metadata_faces_import_setting": "Aktiver ansikts importering", + "metadata_faces_import_setting_description": "Importer ansikt fra bilde EXIF data og tillegsfiler", "metadata_settings": "Metadatainnstillinger", "metadata_settings_description": "Administrer metadatainnstillinger", "migration_job": "Migrering", @@ -174,7 +187,7 @@ "oauth_issuer_url": "Utgiverens URL", "oauth_mobile_redirect_uri": "Mobil omdirigerings-URI", "oauth_mobile_redirect_uri_override": "Mobil omdirigerings-URI overstyring", - "oauth_mobile_redirect_uri_override_description": "Aktiver når 'app.immich:/' er en ugyldig omdirigerings-URI.", + "oauth_mobile_redirect_uri_override_description": "Aktiver når OAuth-leverandøren ikke tillater en mobil URI, som '{callback}'", "oauth_profile_signing_algorithm": "Profilsigneringsalgoritme", "oauth_profile_signing_algorithm_description": "Algoritme brukt for å signere brukerprofilen.", "oauth_scope": "Omfang", @@ -194,6 +207,7 @@ "password_settings": "Passordinnlogging", "password_settings_description": "Administrer innstillinger for passordinnlogging", "paths_validated_successfully": "Alle filbaner validert uten problemer", + "person_cleanup_job": "Person opprydding", "quota_size_gib": "Kvotestørrelse (GiB)", "refreshing_all_libraries": "Oppdaterer alle biblioteker", "registration": "Administrator registrering", @@ -204,9 +218,13 @@ "require_password_change_on_login": "Krev at brukeren endrer passord ved første pålogging", "reset_settings_to_default": "Tilbakestill innstillinger til standard", "reset_settings_to_recent_saved": "Tilbakestill innstillingene til de nylig lagrede innstillingene", + "scanning_library": "Søk biblioteket", + "search_jobs": "Søk etter jobber...", "send_welcome_email": "Send velkomst-e-post", "server_external_domain_settings": "Eksternt domene", "server_external_domain_settings_description": "Domene for offentlige delingslenker, inkludert http(s)://", + "server_public_users": "Offentlige brukere", + "server_public_users_description": "Alle brukere (navn og epost) blir vist når en bruker blir lagt til et delt album. Når deaktivert, vil brukerne bare bli synlig for administratorer.", "server_settings": "Serverinstillinger", "server_settings_description": "Administrer serverinnstillinger", "server_welcome_message": "Velkomstmelding", @@ -221,7 +239,7 @@ "storage_template_hash_verification_enabled": "Hash verifisering aktivert", "storage_template_hash_verification_enabled_description": "Aktiver hasjverifisering. Ikke deaktiver dette med mindre du er sikker på konsekvensene", "storage_template_migration": "Lagringsmal migrering", - "storage_template_migration_description": "Bruk gjeldende {mal} på tidligere opplastede bilder.", + "storage_template_migration_description": "Bruk gjeldende {template} på tidligere opplastede bilder", "storage_template_migration_info": "Malendringer vil kun gjelde nye ressurser. For å anvende malen på tidligere opplastede ressurser, kjør {job}.", "storage_template_migration_job": "Migreringsjobb for lagringsmal", "storage_template_more_details": "For mer informasjon om denne funksjonen, se lagringsmalen og dens konsekvenser", @@ -231,6 +249,17 @@ "storage_template_settings_description": "Administrer mappestrukturen og filnavnet til opplastede fil", "storage_template_user_label": "{label} er brukerens Lagringsetikett", "system_settings": "Systeminstillinger", + "tag_cleanup_job": "Tag opprydding", + "template_email_available_tags": "Du kan bruke følgende variabler i din mal: {tags}", + "template_email_if_empty": "Hvis malen er tom, vil standard epost bli brut.", + "template_email_invite_album": "Inviter Album Mal", + "template_email_preview": "Forhåndsvis", + "template_email_settings": "Epost mal", + "template_email_settings_description": "Administrer tilpasset mal for varslings maler", + "template_email_update_album": "Oppdater Album Mal", + "template_email_welcome": "Mal for velkomst epost", + "template_settings": "Varslings Mal", + "template_settings_description": "Administrer tilpassede maler for varsling.", "theme_custom_css_settings": "Egendefinert CSS", "theme_custom_css_settings_description": "Cascading Style Sheets gjør det mulig å tilpasse designet av Immich.", "theme_settings": "Tema innstillinger", @@ -260,6 +289,8 @@ "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_description": "Sett kodeks, oppløsning, kvalitet og andre valg for koding av videoer", "transcoding_hardware_acceleration": "Maskinvareakselerasjon", "transcoding_hardware_acceleration_description": "Eksperimentell; mye raskere, men vil ha lavere kvalitet ved samme bithastighet", "transcoding_hardware_decoding": "Maskinvaredekoding", @@ -272,6 +303,8 @@ "transcoding_max_keyframe_interval": "Maksimal referansebilde intervall", "transcoding_max_keyframe_interval_description": "Setter maksimalt antall bilder mellom referansebilder. Lavere verdier reduserer kompresjonseffektiviteten, men forbedrer søketider og kan forbedre kvaliteten i scener med rask bevegelse. 0 setter verdien automatisk.", "transcoding_optimal_description": "Videoer som har høyere oppløsning enn målopppløsningen eller som ikke er i et akseptert format", + "transcoding_policy": "Retningslinjer for omkoding", + "transcoding_policy_description": "Velg når en video vil blir omkodet", "transcoding_preferred_hardware_device": "Foretrukken maskinvareenhet", "transcoding_preferred_hardware_device_description": "Gjelder bare for VAAPI og QSV. Angir DRI-node brukt for maskinvaretranscoding.", "transcoding_preset_preset": "Forhåndsinnstilling (-preset)", @@ -280,10 +313,10 @@ "transcoding_reference_frames_description": "Antall bilder som skal refereres til når en gitt ramme komprimeres. Høyere verdier forbedrer komprimeringseffektiviteten, men senker ned kodingen. 0 setter denne verdien automatisk.", "transcoding_required_description": "Bare videoer som ikke er i et akseptert format", "transcoding_settings": "Innstillinger for videotranskoding", - "transcoding_settings_description": "Administrer oppløsning og kodinginformasjon for videofiler", + "transcoding_settings_description": "Administrer hvilke videoer å omkode og hvordan behandle dem", "transcoding_target_resolution": "Endelig oppløsning", "transcoding_target_resolution_description": "Høyere oppløsninger kan bevare mer detaljer, men tar lengre tid å kode, resulterer i større filstørrelser, og kan redusere appens responsivitet.", - "transcoding_temporal_aq": "Temporal AQ", + "transcoding_temporal_aq": "Midlertidig AQ", "transcoding_temporal_aq_description": "Gjelder kun for NVENC. Øker kvaliteten på scener med høy detaljgrad og lav bevegelse. Kan være inkompatibelt med eldre enheter.", "transcoding_threads": "Tråder", "transcoding_threads_description": "Høyere verdier fører til raskere koding, men gir mindre plass for serveren til å behandle andre oppgaver mens den er aktiv. Verdien bør ikke være mer enn antall CPU-kjerner. Maksimerer utnyttelsen hvis satt til 0.", @@ -302,6 +335,7 @@ "trash_settings_description": "Administrer papirkurv-innstillinger", "untracked_files": "Usporede filer", "untracked_files_description": "Disse filene er ikke sporet av applikasjonen. De kan være resultatet av mislykkede flyttinger, avbrutte opplastninger eller etterlatt på grunn av en feil", + "user_cleanup_job": "Bruker opprydning", "user_delete_delay": "{user}s konto og elementer vil legges i kø for permanent sletting om {delay, plural, one {# dag} other {# dager}}.", "user_delete_delay_settings": "Sletteforsinkelse", "user_delete_delay_settings_description": "Antall dager etter fjerning før en brukerkonto og dens filer permanent slettes. Brukerfjerningsjobben kjører ved midnatt for å sjekke etter brukere som er klare for sletting. Endringer i denne innstillingen vil bli evaluert ved neste utførelse.", @@ -328,10 +362,11 @@ "advanced": "Avansert", "age_months": "Alder {months, plural, one {# måned} other {# måneder}}", "age_year_months": "Alder 1 år, {months, plural, one {# måned} other {# måneder}}", + "age_years": "{years, plural, other {Age #}}", "album_added": "Album lagt til", "album_added_notification_setting_description": "Motta en e-postvarsling når du legges til i et delt album", "album_cover_updated": "Albumomslag oppdatert", - "album_delete_confirmation": "Er du sikker på at du vil slette albumet {album}?\nHvis dette albumet er delt, vil ikke andre brukere ha tilgang til det lenger.", + "album_delete_confirmation": "Er du sikker på at du vil slette albumet {album}?", "album_delete_confirmation_description": "Hvis dette albumet deles, vil andre brukere miste tilgangen til dette.", "album_info_updated": "Albuminformasjon oppdatert", "album_leave": "Forlate album?", @@ -384,16 +419,30 @@ "asset_uploading": "Laster opp...", "assets": "Filer", "assets_added_count": "Lagt til {count, plural, one {# element} other {# elementer}}", - "assets_restore_confirmation": "Er du sikker på at du vil gjenopprette alle slettede eiendeler? Denne handlingen kan ikke angres!", + "assets_added_to_album_count": "Lagt til {count, plural, one {# asset} other {# assets}} i album", + "assets_added_to_name_count": "Lagt til {count, plural, one {# asset} other {# assets}} i {hasName, select, true {{name}} other {new album}}", + "assets_count": "{count, plural, one {# asset} other {# assets}}", + "assets_moved_to_trash_count": "Flyttet {count, plural, one {# asset} other {# assets}} til søppel", + "assets_permanently_deleted_count": "Permanent slettet {count, plural, one {# asset} other {# assets}}", + "assets_removed_count": "Slettet {count, plural, one {# asset} other {# assets}}", + "assets_restore_confirmation": "Er du sikker på at du vil gjenopprette alle slettede eiendeler? Denne handlingen kan ikke angres! Vær oppmerksom på at frakoblede ressurser ikke kan gjenopprettes på denne måten.", + "assets_restored_count": "Gjenopprettet {count, plural, one {# asset} other {# assets}}", + "assets_trashed_count": "Kastet {count, plural, one {# asset} other {# assets}}", + "assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}} er allerede lagt til i albumet", "authorized_devices": "Autoriserte enheter", "back": "Tilbake", + "back_close_deselect": "Tilbake, lukk eller fjern merking", "backward": "Bakover", - "birthdate_saved": "Fødselsdato er lagret vellykket.", + "birthdate_saved": "Fødselsdato er vellykket lagret", "birthdate_set_description": "Fødelsdatoen er brukt for å beregne alderen til denne personen ved tidspunktet til bildet.", "blurred_background": "Uskarp bakgrunn", - "bulk_delete_duplicates_confirmation": "Er du sikker på at du vil slette {count} dupliserte filer? Dette vil beholde største filen fra hver gruppe og vil permanent slette alle andre duplikater. Du kan ikke angre denne handlingen!", - "bulk_keep_duplicates_confirmation": "Er du sikker på at du vil beholde {count} dupliserte filer? Dette vil løse alle dupliserte grupper uten å slette noe.", - "bulk_trash_duplicates_confirmation": "Er du sikker på ønsker å slette {count} dupliserte filer? Dette vil beholde største filen fra hver gruppe, samt slette alle andre duplikater.", + "bugs_and_feature_requests": "Feil og funksjonsforespørsler", + "build": "Bygg", + "build_image": "Lag Bilde", + "bulk_delete_duplicates_confirmation": "Er du sikker på at du vil slette {count, plural, one {# duplicate asset} other {# duplicate assets}} dupliserte filer? Dette vil beholde største filen fra hver gruppe og vil permanent slette alle andre duplikater. Du kan ikke angre denne handlingen!", + "bulk_keep_duplicates_confirmation": "Er du sikker på at du vil beholde {count, plural, one {# duplicate asset} other {# duplicate assets}} dupliserte filer? Dette vil løse alle dupliserte grupper uten å slette noe.", + "bulk_trash_duplicates_confirmation": "Er du sikker på ønsker å slette {count, plural, one {# duplicate asset} other {# duplicate assets}} dupliserte filer? Dette vil beholde største filen fra hver gruppe, samt slette alle andre duplikater.", + "buy": "Kjøp Immich", "camera": "Kamera", "camera_brand": "Kameramerke", "camera_model": "Kameramodell", @@ -420,8 +469,11 @@ "clear_all_recent_searches": "Fjern alle nylige søk", "clear_message": "Fjern melding", "clear_value": "Fjern verdi", + "clockwise": "Med urviseren", "close": "Lukk", + "collapse": "Slå sammen", "collapse_all": "Kollaps alt", + "color": "Farge", "color_theme": "Fargetema", "comment_deleted": "Kommentar slettet", "comment_options": "Kommentaralternativer", @@ -430,6 +482,7 @@ "confirm": "Bekreft", "confirm_admin_password": "Bekreft administratorpassord", "confirm_delete_shared_link": "Er du sikker på at du vil slette denne delte lenken?", + "confirm_keep_this_delete_others": "Alle andre ressurser i denne stabelen vil bli slettet bortsett fra denne ressursen. Er du sikker på at du vil fortsette?", "confirm_password": "Bekreft passord", "contain": "Inneholder", "context": "Kontekst", @@ -455,6 +508,8 @@ "create_new_person": "Opprett ny person", "create_new_person_hint": "Tildel valgte eiendeler til en ny person", "create_new_user": "Opprett ny bruker", + "create_tag": "Lag tag", + "create_tag_description": "Lag en ny tag. For undertag, vennligst fullfør hele banen til taggen, inkludert forovervendt skråstrek.", "create_user": "Opprett Bruker", "created": "Opprettet", "current_device": "Nåværende enhet", @@ -468,6 +523,10 @@ "date_range": "Datoområde", "day": "Dag", "deduplicate_all": "De-dupliser alle", + "deduplication_criteria_1": "Bilde størrelse i bytes", + "deduplication_criteria_2": "Antall av EXIF data", + "deduplication_info": "Dedupliseringsinformasjon", + "deduplication_info_description": "For å automatisk forhåndsvelge eiendeler og fjerne duplikater samtidig, ser vi på:", "default_locale": "Standard språkinnstilling", "default_locale_description": "Formater datoer og tall basert på nettleserens språkinnstilling", "delete": "Slett", @@ -477,14 +536,19 @@ "delete_key": "Slett nøkkel", "delete_library": "Slett bibliotek", "delete_link": "Slett lenke", + "delete_others": "Slett andre", "delete_shared_link": "Slett delt lenke", + "delete_tag": "Slett tag", + "delete_tag_confirmation_prompt": "Er du sikker på at du vil slette {tagName} tag?", "delete_user": "Slett bruker", "deleted_shared_link": "Slettet delt lenke", + "deletes_missing_assets": "Slett eiendeler som mangler fra disk", "description": "Beskrivelse", "details": "Detaljer", "direction": "Retning", "disabled": "Deaktivert", "disallow_edits": "Forby redigering", + "discord": "Discord", "discover": "Oppdag", "dismiss_all_errors": "Avvis alle feil", "dismiss_error": "Avvis feil", @@ -493,14 +557,20 @@ "display_original_photos": "Vis opprinnelige bilder", "display_original_photos_setting_description": "Foretrekk å vise det opprinnelige bildet når du ser på en fil i stedet for miniatyrbilder når den opprinnelige filen er kompatibel med nettet. Dette kan føre til tregere visning av bilder.", "do_not_show_again": "Ikke vis denne meldingen igjen", + "documentation": "Dokumentasjon", "done": "Ferdig", "download": "Last ned", + "download_include_embedded_motion_videos": "Innebygde videoer", + "download_include_embedded_motion_videos_description": "Inkluder innebygde videoer i levende bilder som en egen fil", "download_settings": "Last ned", "download_settings_description": "Administrer innstillinger relatert til nedlasting av filer", "downloading": "Laster ned", + "downloading_asset_filename": "Last ned {filename}", + "drop_files_to_upload": "Slipp filer hvor som helst for å laste opp", "duplicates": "Duplikater", "duplicates_description": "Løs hver gruppe ved å angi hvilke, hvis noen, er duplikater", "duration": "Varighet", + "edit": "Rediger", "edit_album": "Rediger album", "edit_avatar": "Rediger avatar", "edit_date": "Rediger dato", @@ -514,60 +584,117 @@ "edit_location": "Endre lokasjon", "edit_name": "Endre navn", "edit_people": "Rediger personer", + "edit_tag": "Rediger tag", "edit_title": "Rediger Tittel", "edit_user": "Rediger bruker", "edited": "Redigert", "editor": "Redaktør", + "editor_close_without_save_prompt": "Endringene vil ikke bli lagret", + "editor_close_without_save_title": "Lukk redigering?", + "editor_crop_tool_h2_aspect_ratios": "Sideforhold", + "editor_crop_tool_h2_rotation": "Rotasjon", "email": "E-postadresse", "empty_trash": "Tøm papirkurv", - "enable": "", - "enabled": "", + "empty_trash_confirmation": "Er du sikker på at du vil tømme søppelbøtte ? Dette vil slette alle filene i søppelbøtta permanent fra Immich.\nDu kan ikke angre denne handlingen!", + "enable": "Aktivere", + "enabled": "Aktivert", "end_date": "Slutt dato", "error": "Feil", "error_loading_image": "Feil ved lasting av bilde", + "error_title": "Feil - Noe gikk galt", "errors": { + "cannot_navigate_next_asset": "Kan ikke navigere til neste fil", + "cannot_navigate_previous_asset": "Kan ikke navigere til forrige fil", + "cant_apply_changes": "Kan ikke legge til endringene", + "cant_change_activity": "Kan ikke {enabled, select, true {disable} other {enable}} aktivitet", + "cant_change_asset_favorite": "Kan ikke endre favoritt til filen", + "cant_change_metadata_assets_count": "Kan ikke endre metadata for {count, plural, one {# asset} other {# assets}}", + "cant_get_faces": "Kan ikke finne ansikter", + "cant_get_number_of_comments": "Kan ikke hente antall kommentarer", + "cant_search_people": "Kan ikke søke etter mennesker", + "cant_search_places": "Kan ikke søke etter plasser", "cleared_jobs": "Fjernet jobber for: {job}", + "error_adding_assets_to_album": "Feil med å legge til bilder til album", + "error_adding_users_to_album": "Feil, kan ikke legge til brukere til album", + "error_deleting_shared_user": "Feil med å slette delt bruker", + "error_downloading": "Feil med å laste ned {filename}", + "error_hiding_buy_button": "Feil med å skjule kjøp knapp", + "error_removing_assets_from_album": "Feil med å fjerne bilder fra album, sjekk konsoll for mer detaljer", + "error_selecting_all_assets": "Feil med å velge alle bilder", "exclusion_pattern_already_exists": "Dette eksklusjonsmønsteret eksisterer allerede.", "failed_job_command": "Kommandoen {command} mislyktes for jobben: {job}", + "failed_to_create_album": "Feil med å lage album", + "failed_to_create_shared_link": "Feil med å lage delt lenke", + "failed_to_edit_shared_link": "Feilet med å redigere delt lenke", + "failed_to_get_people": "Feilet med å finne mennesker", + "failed_to_keep_this_delete_others": "Feilet med å beholde dette bilde og slette de andre", + "failed_to_load_asset": "Feilet med å laste bilder", + "failed_to_load_assets": "Feilet med å laste bilde", + "failed_to_load_people": "Feilen med å laste mennesker", + "failed_to_remove_product_key": "Feilet med å ta bort produkt nøkkel", + "failed_to_stack_assets": "Feilet med å stable bilder", + "failed_to_unstack_assets": "Feilet med å avstable bilder", "import_path_already_exists": "Denne importstien eksisterer allerede.", + "incorrect_email_or_password": "Feil epost eller passord", "paths_validation_failed": "{paths, plural, one {# sti} other {# sti}} mislyktes validering", + "profile_picture_transparent_pixels": "Profil bilde kan ikke ha gjennomsiktige piksler. Vennligst zoom inn og/eller flytt bilde.", "quota_higher_than_disk_size": "Du har satt en kvote høyere enn diskstørrelsen", "repair_unable_to_check_items": "Kan ikke sjekke {count, select, one {element} other {elementer}}", "unable_to_add_album_users": "Kan ikke legge til brukere i albumet", + "unable_to_add_assets_to_shared_link": "Kan ikke legge til bilder til delt lenke", "unable_to_add_comment": "Kan ikke legge til kommentar", "unable_to_add_exclusion_pattern": "Kan ikke legge til eksklusjonsmønster", "unable_to_add_import_path": "Kan ikke legge til importsti", "unable_to_add_partners": "Kan ikke legge til partnere", + "unable_to_add_remove_archive": "Kan ikke {archived, select, true {remove asset from} other {add asset to}} arkivet", + "unable_to_add_remove_favorites": "Kan ikke {favorite, select, true {add asset to} other {remove asset from}} favoritter", + "unable_to_archive_unarchive": "Kan ikke {archived, select, true {archive} other {unarchive}}", "unable_to_change_album_user_role": "Kan ikke endre brukerens rolle i albumet", "unable_to_change_date": "Kan ikke endre dato", + "unable_to_change_favorite": "Kan ikke endre favoritt for bildet", "unable_to_change_location": "Kan ikke endre plassering", "unable_to_change_password": "Kan ikke endre passord", + "unable_to_change_visibility": "Kan ikke endre synlighet for {count, plural, one {# person} other {# people}}", + "unable_to_complete_oauth_login": "Kunne ikke fullføre OAuth innlogging", + "unable_to_connect": "Kan ikke koble til", + "unable_to_connect_to_server": "Kan ikke koble til server", "unable_to_copy_to_clipboard": "Kan ikke kopiere til utklippstavlen, sørg for at du får tilgang til siden via HTTPS", - "unable_to_create_admin_account": "", + "unable_to_create_admin_account": "Kan ikke opprette administrator bruker", "unable_to_create_api_key": "Kan ikke opprette en ny API-nøkkel", "unable_to_create_library": "Kan ikke opprette bibliotek", "unable_to_create_user": "Kan ikke opprette bruker", "unable_to_delete_album": "Kan ikke slette album", "unable_to_delete_asset": "Kan ikke slette filen", + "unable_to_delete_assets": "Feil med å slette bilde", "unable_to_delete_exclusion_pattern": "Kan ikke slette eksklusjonsmønster", "unable_to_delete_import_path": "Kan ikke slette importsti", "unable_to_delete_shared_link": "Kan ikke slette delt lenke", "unable_to_delete_user": "Kan ikke slette bruker", + "unable_to_download_files": "Kan ikke laste ned filer", "unable_to_edit_exclusion_pattern": "Kan ikke redigere eksklusjonsmønster", "unable_to_edit_import_path": "Kan ikke redigere importsti", "unable_to_empty_trash": "Kan ikke tømme papirkurven", "unable_to_enter_fullscreen": "Kan ikke gå inn i fullskjerm", "unable_to_exit_fullscreen": "Kan ikke gå ut fra fullskjerm", + "unable_to_get_comments_number": "Kan ikke hente antall kommentarer", + "unable_to_get_shared_link": "Kan ikke hente delt lenke", "unable_to_hide_person": "Kan ikke skjule person", + "unable_to_link_motion_video": "Kan ikke lenke bevegelig video", "unable_to_link_oauth_account": "Kan ikke lenke til OAuth-konto", "unable_to_load_album": "Kan ikke laste inn album", "unable_to_load_asset_activity": "Kan ikke laste inn aktivitet for filen", "unable_to_load_items": "Kan ikke laste inn elementer", "unable_to_load_liked_status": "Kan ikke laste inn likt status", + "unable_to_log_out_all_devices": "Kan ikke logge ut fra alle enheter", + "unable_to_log_out_device": "Kan ikke logge ut av enhet", + "unable_to_login_with_oauth": "Kan ikke logge inn med OAuth", "unable_to_play_video": "Kan ikke spille av video", + "unable_to_reassign_assets_existing_person": "Kunne ikke endre bruker på bildene til {name, select, null {an existing person} other {{name}}}", + "unable_to_reassign_assets_new_person": "Kunne ikke tildele bildene til en ny person", "unable_to_refresh_user": "Kan ikke oppdatere bruker", "unable_to_remove_album_users": "Kan ikke fjerne brukere fra album", "unable_to_remove_api_key": "Kan ikke fjerne API-nøkkel", + "unable_to_remove_assets_from_shared_link": "Kunne ikke fjerne bilder fra delt lenke", "unable_to_remove_deleted_assets": "Kan ikke fjerne offlinefiler", "unable_to_remove_library": "Kan ikke fjerne bibliotek", "unable_to_remove_partner": "Kan ikke fjerne partner", @@ -580,35 +707,48 @@ "unable_to_restore_user": "Kan ikke gjenopprette bruker", "unable_to_save_album": "Kan ikke lagre album", "unable_to_save_api_key": "Kan ikke lagre API-nøkkel", + "unable_to_save_date_of_birth": "Kunne ikke lagre bursdag", "unable_to_save_name": "Kan ikke lagre navn", "unable_to_save_profile": "Kan ikke lagre profil", "unable_to_save_settings": "Kan ikke lagre instillinger", "unable_to_scan_libraries": "Kan ikke skanne biblioteker", "unable_to_scan_library": "Kan ikke skanne bibliotek", + "unable_to_set_feature_photo": "Kunne ikke sette funksjonsbilde", "unable_to_set_profile_picture": "Kan ikke sette profilbilde", "unable_to_submit_job": "Kan ikke sende inn jobb", "unable_to_trash_asset": "Kan ikke flytte filen til papirkurven", "unable_to_unlink_account": "Kan ikke fjerne kobling til konto", + "unable_to_unlink_motion_video": "Kunne ikke ta på kobling på bevegelig video", + "unable_to_update_album_cover": "Kunne ikke oppdatere album bilde", + "unable_to_update_album_info": "Kunne ikke oppdatere informasjon i album", "unable_to_update_library": "Kan ikke oppdatere bibliotek", "unable_to_update_location": "Kan ikke oppdatere plassering", "unable_to_update_settings": "Kan ikke oppdatere innstillinger", "unable_to_update_timeline_display_status": "Kan ikke oppdatere visningsstatus for tidslinje", - "unable_to_update_user": "Kan ikke oppdatere bruker" + "unable_to_update_user": "Kan ikke oppdatere bruker", + "unable_to_upload_file": "Kunne ikke laste opp fil" }, + "exif": "EXIF", "exit_slideshow": "Avslutt lysbildefremvisning", "expand_all": "Utvid alle", "expire_after": "Utgå etter", "expired": "Utgått", + "expires_date": "Utløper {date}", "explore": "Utforsk", + "explorer": "Utforsker", "export": "Eksporter", "export_as_json": "Eksporter som JSON", "extension": "Utvidelse", "external": "Ekstern", "external_libraries": "Eksterne Bibliotek", + "face_unassigned": "Ikke tilordnet", + "failed_to_load_assets": "Feilet med å laste fil", "favorite": "Favoritt", "favorite_or_unfavorite_photo": "Merk som favoritt eller fjern som favoritt", "favorites": "Favoritter", "feature_photo_updated": "Fremhevet bilde oppdatert", + "features": "Funksjoner", + "features_setting_description": "Administrerer funksjoner for appen", "file_name": "Filnavn", "file_name_or_extension": "Filnavn eller filtype", "filename": "Filnavn", @@ -616,24 +756,45 @@ "filter_people": "Filtrer personer", "find_them_fast": "Finn dem raskt ved søking av navn", "fix_incorrect_match": "Fiks feilaktig match", + "folders": "Mapper", + "folders_feature_description": "Utforsker mappe visning for bilder og videoer på fil systemet", "forward": "Fremover", "general": "Generelt", "get_help": "Få Hjelp", "getting_started": "Kom i gang", "go_back": "Gå tilbake", + "go_to_folder": "Gå til mappe", "go_to_search": "Gå til søk", "group_albums_by": "Grupper album etter...", + "group_no": "Ingen gruppering", + "group_owner": "Grupper etter eiere", + "group_year": "Grupper etter år", "has_quota": "Har kvote", + "hi_user": "Hei {name} ({email})", + "hide_all_people": "Skjul alle mennesker", "hide_gallery": "Skjul galleri", + "hide_named_person": "Skjul {name}", "hide_password": "Skjul passord", "hide_person": "Skjul person", + "hide_unnamed_people": "Skjul mennesker uten navn", "host": "Vert", "hour": "Time", "image": "Bilde", + "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} tatt på {date}", + "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} tatt med {person1} den {date}", + "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} tatt med {person1} og {person2} den {date}", + "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} tatt med {person1}, {person2}, og {person3} den {date}", + "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tatt med {person1}, {person2}, og {additionalCount, number} andre den {date}", + "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} den {date}", + "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} med {person1} den {date}", + "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} med {person1} og {person2} den {date}", + "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} med {person1}, {person2}, og {person3} den {date}", + "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} med {person1}, {person2}, ok {additionalCount, number} andre den {date}", "immich_logo": "Immich Logo", "immich_web_interface": "Immich webgrensesnitt", "import_from_json": "Importer fra JSON", "import_path": "Import-sti", + "in_albums": "I {count, plural, one {# album} other {# albums}}", "in_archive": "I arkiv", "include_archived": "Inkluder arkiverte", "include_shared_albums": "Inkluder delte album", @@ -648,19 +809,26 @@ }, "invite_people": "Inviter Personer", "invite_to_album": "Inviter til album", + "items_count": "{count, plural, one {# item} other {# items}}", "jobs": "Oppgaver", "keep": "Behold", "keep_all": "Behold alle", + "keep_this_delete_others": "Behold denne, slett de andre", + "kept_this_deleted_others": "Behold denne filen og slett {count, plural, one {# asset} other {# assets}}", "keyboard_shortcuts": "Tastatursnarveier", "language": "Språk", "language_setting_description": "Velg ditt foretrukket språk", "last_seen": "Sist sett", + "latest_version": "Siste versjon", + "latitude": "Breddegrad", "leave": "Forlat", "let_others_respond": "La andre respondere", "level": "Nivå", "library": "Bibliotek", "library_options": "Bibliotekalternativer", "light": "Lys", + "like_deleted": "Som slettede", + "link_motion_video": "Koble bevegelsesvideo", "link_options": "Lenkealternativer", "link_to_oauth": "Lenke til OAuth", "linked_oauth_account": "Lenket til OAuth-konto", @@ -669,10 +837,17 @@ "loading_search_results_failed": "Klarte ikke å laste inn søkeresultater", "log_out": "Logg ut", "log_out_all_devices": "Logg ut fra alle enheter", + "logged_out_all_devices": "Logg ut av alle enheter", + "logged_out_device": "Logg ut enhet", + "login": "Logg inn", "login_has_been_disabled": "Login har blitt deaktivert.", + "logout_all_device_confirmation": "Er du sikker på at du vil logge ut av alle enheter?", + "logout_this_device_confirmation": "Er du sikker på at du vil logge ut av denne enheten?", + "longitude": "Lengdegrad", "look": "Se", "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!", "make": "Merke", "manage_shared_links": "Håndter delte linker", "manage_sharing_with_partners": "Administrer deling med partnere", @@ -682,18 +857,22 @@ "manage_your_devices": "Administrer dine innloggede enheter", "manage_your_oauth_connection": "Administrer tilkoblingen din med OAuth", "map": "Kart", + "map_marker_for_images": "Kart makeringer for bilder tatt i {city}, {country}", "map_marker_with_image": "Kartmarkør med bilde", "map_settings": "Kartinnstillinger", "matches": "Samsvarende", "media_type": "Mediatype", "memories": "Minner", "memories_setting_description": "Administrer hva du ser i minnene dine", + "memory": "Minne", + "memory_lane_title": "Minnefelt {title}", "menu": "Meny", "merge": "Slå sammen", "merge_people": "Slå sammen personer", "merge_people_limit": "Du kan bare slå sammen opp til 5 fjes om gangen", "merge_people_prompt": "Vil du slå sammen disse personene? Denne handlingen kan ikke reverseres.", "merge_people_successfully": "Personene ble vellykket slått sammen", + "merged_people_count": "Sammenslått {count, plural, one {# person} other {# people}}", "minimize": "Minimer", "minute": "Minutt", "missing": "Mangler", @@ -705,15 +884,19 @@ "name": "Navn", "name_or_nickname": "Navn eller kallenavn", "never": "aldri", + "new_album": "Nytt Album", "new_api_key": "Ny API-nøkkel", "new_password": "Nytt passord", "new_person": "Ny person", "new_user_created": "Ny bruker opprettet", + "new_version_available": "NY VERSJON TILGJENGELIG", "newest_first": "Nyeste først", "next": "Neste", "next_memory": "Neste minne", "no": "Nei", "no_albums_message": "Opprett et album for å organisere bildene og videoene dine", + "no_albums_with_name_yet": "Det ser ut som om det ikke finnes noen album med dette navnet enda.", + "no_albums_yet": "Det ser ut som om du ikke har noen album enda.", "no_archived_assets_message": "Arkiver bilder og videoer for å skjule dem fra visningen av bildene dine", "no_assets_message": "KLIKK FOR Å LASTE OPP DITT FØRSTE BILDE", "no_duplicates_found": "Ingen duplikater ble funnet.", @@ -724,6 +907,7 @@ "no_name": "Ingen navn", "no_places": "Ingen steder", "no_results": "Ingen resultater", + "no_results_description": "Prøv et synonym eller mer generelt søkeord", "no_shared_albums_message": "Opprett et album for å dele bilder og videoer med personer i nettverket ditt", "not_in_any_album": "Ikke i noen album", "note_apply_storage_label_to_previously_uploaded assets": "Merk: For å bruke lagringsetiketten på tidligere opplastede filer, kjør", @@ -733,21 +917,32 @@ "notifications": "Notifikasjoner", "notifications_setting_description": "Administrer varsler", "oauth": "OAuth", + "official_immich_resources": "Offisielle Immich Resurser", "offline": "Frakoblet", "offline_paths": "Frakoblede stier", "offline_paths_description": "Disse resultatene kan skyldes manuell sletting av filer som ikke er en del av et eksternt bibliotek.", "ok": "Ok", "oldest_first": "Eldste først", + "onboarding": "Påmønstring", + "onboarding_privacy_description": "Følgene (valgfrie) funksjoner er avhengige av eksterne tjeneste, og kan bli deaktivert når som helst under administrator instillinger.", + "onboarding_theme_description": "Velg et fargetema for din bruker. Du kan endre denne senere under dine instillinger.", + "onboarding_welcome_description": "La oss sette opp denne installasjonen med noen vanlige instillinger.", + "onboarding_welcome_user": "Velkommen, {user}", "online": "Tilkoblet", "only_favorites": "Bare favoritter", + "open_in_map_view": "Åpne i kartvisning", + "open_in_openstreetmap": "Åpne i OpenStreetMap", "open_the_search_filters": "Åpne søkefiltrene", "options": "Valg", + "or": "eller", "organize_your_library": "Organiser biblioteket ditt", + "original": "original", "other": "Annet", "other_devices": "Andre enheter", "other_variables": "Andre variabler", "owned": "Ditt album", "owner": "Eier", + "partner": "Partner", "partner_can_access": "{partner} har tilgang", "partner_can_access_assets": "Alle bildene og videoene dine unntatt de i arkivert og slettet tilstand", "partner_can_access_location": "Stedet der bildene dine ble tatt", @@ -769,12 +964,21 @@ "paused": "Satt på pause", "pending": "Avventer", "people": "Folk", + "people_edits_count": "Endret {count, plural, one {# person} other {# people}}", + "people_feature_description": "Utforsk bilder og videoer gruppert etter mennesker", "people_sidebar_description": "Vis en lenke til Personer i sidepanelet", "permanent_deletion_warning": "Advarsel om permanent sletting", "permanent_deletion_warning_setting_description": "Vis en advarsel ved permanent sletting av filer", "permanently_delete": "Slett permanent", + "permanently_delete_assets_count": "Permanent slett {count, plural, one {asset} other {assets}}", + "permanently_delete_assets_prompt": "Er du sikker på at du vil permanent slette {count, plural, one {this asset?} other {these # assets?}} Dette vil også slette {count, plural, one {it from its} other {them from their}} album.", "permanently_deleted_asset": "Filen har blitt permanent slettet", + "permanently_deleted_assets_count": "Permanent slett {count, plural, one {# asset} other {# assets}}", + "person": "Person", + "person_hidden": "{name}{hidden, select, true { (hidden)} other {}}", + "photo_shared_all_users": "Det ser ut som om du deler bildene med alle brukere eller det er ingen brukere å dele med.", "photos": "Bilder", + "photos_and_videos": "Bilder & Videoer", "photos_count": "{count, plural, one {{count, number} Bilde} other {{count, number} Bilder}}", "photos_from_previous_years": "Bilder fra tidliger år", "pick_a_location": "Velg et sted", @@ -791,8 +995,31 @@ "previous_memory": "Forrige minne", "previous_or_next_photo": "Forrige eller neste bilde", "primary": "Primær", + "privacy": "Privat", + "profile_image_of_user": "Profil bilde av {user}", "profile_picture_set": "Profilbildet er satt.", + "public_album": "Offentlige album", "public_share": "Offentlig deling", + "purchase_account_info": "Støttespiller", + "purchase_activated_subtitle": "Takk for at du støtter Immich og åpen kildekode programvare", + "purchase_activated_time": "Aktiver den {date, date}", + "purchase_activated_title": "Du produktnøkkel har vellyket blitt aktivert", + "purchase_button_activate": "Aktiver", + "purchase_button_buy": "Kjøp", + "purchase_button_buy_immich": "Kjøp Immich", + "purchase_button_never_show_again": "Aldri vis igjen", + "purchase_button_reminder": "Påminn meg om 30 dager", + "purchase_button_remove_key": "Ta bort produktnøkkel", + "purchase_button_select": "Velg", + "purchase_failed_activation": "Feilet med å aktivere! Vennligst sjekk eposten for riktig produktnøkkel!", + "purchase_individual_description_1": "For en person", + "purchase_individual_description_2": "Støttespiller status", + "purchase_individual_title": "Individuell", + "purchase_input_suggestion": "Har du en produktnøkkel? Legg til denne under", + "purchase_license_subtitle": "Kjøp Immich for å støtte den videre utviklingen av systemet", + "purchase_lifetime_description": "Kjøp for livstid", + "purchase_option_title": "KJØPSVALG", + "purchase_panel_info_1": "Å lage Immich tar mye tid og energi, og nå har vi en fulltidsansatt utvikler som jobber med å gjøre produktet så godt vi kan. Vårt oppdrag er for åpen-kildekode programvare og etisk virksomhets praktisk å kunne bli bærekraftig inntekt for utviklere og for å lage privat repekterte økesystem med mulighet for å tilby skytjeneste.", "reaction_options": "Reaksjonsalternativer", "read_changelog": "Les endringslogg", "recent": "Nylig", @@ -874,7 +1101,7 @@ "shared_by_you": "Delt av deg", "shared_from_partner": "Bilder fra {partner}", "shared_links": "Delte linker", - "shared_photos_and_videos_count": "{assetCount} delte bilder og videoer.", + "shared_photos_and_videos_count": "{assetCount, plural, other {# delte bilder og videoer.}}", "shared_with_partner": "Delt med {partner}", "sharing": "Deling", "sharing_sidebar_description": "Vis en lenke til Deling i sidepanelet", diff --git a/i18n/nl.json b/i18n/nl.json index 0627795079a43..62d87bb63e132 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -523,6 +523,10 @@ "date_range": "Datumbereik", "day": "Dag", "deduplicate_all": "Alles dedupliceren", + "deduplication_criteria_1": "Grootte van afbeelding in bytes", + "deduplication_criteria_2": "Aantal EXIF data", + "deduplication_info": "Deduplicatie-info", + "deduplication_info_description": "Om automatisch bezittingen te preselecteren en duplicaten te verwijderen in bulk, kijken we naar:", "default_locale": "Standaard landinstelling", "default_locale_description": "Formatteer datums en getallen op basis van de landinstellingen van je browser", "delete": "Verwijderen", diff --git a/i18n/pl.json b/i18n/pl.json index d118a130d17ac..49de4dc9e8534 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -523,6 +523,10 @@ "date_range": "Zakres dat", "day": "Dzień", "deduplicate_all": "Usuń Zduplikowane", + "deduplication_criteria_1": "Rozmiar obrazu w bajtach", + "deduplication_criteria_2": "Ilość plików EXIF", + "deduplication_info": "Stan duplikatów", + "deduplication_info_description": "Aby zakwalifikować elementy jako duplikaty do masowego usunięcia, sprawdzane jest:", "default_locale": "Domyślny Region", "default_locale_description": "Formatuj daty i liczby na podstawie ustawień Twojej przeglądarki", "delete": "Usuń", @@ -544,27 +548,27 @@ "direction": "Kierunek", "disabled": "Wyłączone", "disallow_edits": "Nie pozwalaj edytować", - "discord": "Discord", + "discord": "Konflikt", "discover": "Odkryj", "dismiss_all_errors": "Odrzuć wszystkie błędy", "dismiss_error": "Odrzuć błąd", "display_options": "Opcje wyświetlania", "display_order": "Kolejność wyświetlania", "display_original_photos": "Wyświetlaj oryginalne zdjęcia", - "display_original_photos_setting_description": "Wyświetlając zdjęcia i filmy, preferuj oryginalny plik zamiast miniatur jeżeli jest działa on w przeglądarce. Może to skutkować wolniejszym ładowaniem zdjęć i filmów.", + "display_original_photos_setting_description": "Wyświetlając zdjęcia i filmy, prezentuj oryginalny plik zamiast miniatur jeżeli działa on w przeglądarce. Może to skutkować wolniejszym ładowaniem zdjęć i filmów.", "do_not_show_again": "Nie pokazuj więcej tej wiadomości", "documentation": "Dokumentacja", "done": "Gotowe", "download": "Pobierz", - "download_include_embedded_motion_videos": "Osadzone filmy", + "download_include_embedded_motion_videos": "Pobierz filmy ruchomych zdjęć", "download_include_embedded_motion_videos_description": "Dołącz filmy osadzone w ruchomych zdjęciach jako oddzielny plik", "download_settings": "Pobieranie", "download_settings_description": "Zarządzaj pobieraniem zasobów", "downloading": "Pobieranie", "downloading_asset_filename": "Pobieranie zasobu {filename}", - "drop_files_to_upload": "Upuść pliki gdziekolwiek, żeby je załadować", + "drop_files_to_upload": "Upuść pliki gdziekolwiek, aby je załadować", "duplicates": "Duplikaty", - "duplicates_description": "Rozstrzygnij każdą grupę, określając, które zasoby, jeśli takie istnieją, są duplikatami", + "duplicates_description": "Rozstrzygnij każdą grupę, określając, które zasoby są duplikatami, jeżeli są duplikatami", "duration": "Czas trwania", "edit": "Edytuj", "edit_album": "Edytuj album", @@ -578,7 +582,7 @@ "edit_key": "Edytuj klucz", "edit_link": "Edytuj link", "edit_location": "Edytuj lokalizację", - "edit_name": "Edytuj imię", + "edit_name": "Edytuj nazwę", "edit_people": "Edytuj osoby", "edit_tag": "Edytuj etykietę", "edit_title": "Edytuj Tytuł", diff --git a/i18n/pt.json b/i18n/pt.json index 0738c647804d4..fa88c287fe8d6 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -522,7 +522,11 @@ "date_of_birth_saved": "Data de nascimento guardada com sucesso", "date_range": "Intervalo de datas", "day": "Dia", - "deduplicate_all": "Limpar todos os itens duplicados", + "deduplicate_all": "Remover todos os duplicados", + "deduplication_criteria_1": "Tamanho da imagem em bytes", + "deduplication_criteria_2": "Quantidade de dados EXIF", + "deduplication_info": "Informações sobre remoção de duplicados", + "deduplication_info_description": "Para selecionar automaticamente itens e remover duplicados em massa, vemos o seguinte:", "default_locale": "Localização Padrão", "default_locale_description": "Formatar datas e números baseados na linguagem do seu navegador", "delete": "Eliminar", diff --git a/i18n/pt_BR.json b/i18n/pt_BR.json index 8e7c6b5273c3f..6ad0be429bcbb 100644 --- a/i18n/pt_BR.json +++ b/i18n/pt_BR.json @@ -523,6 +523,10 @@ "date_range": "Intervalo de datas", "day": "Dia", "deduplicate_all": "Limpar todas Duplicidades", + "deduplication_criteria_1": "Tamanho do arquivo em bytes", + "deduplication_criteria_2": "Quantidade de dados EXIF", + "deduplication_info": "Informações", + "deduplication_info_description": "Ao selecionar os arquivos que serão marcados para remoção por duplicidade, será considerado os parâmetros:", "default_locale": "Localização Padrão", "default_locale_description": "Formatar datas e números baseados na linguagem do seu navegador", "delete": "Excluir", diff --git a/i18n/ru.json b/i18n/ru.json index 2beb59de60cc2..887222cb9c051 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -523,6 +523,10 @@ "date_range": "Диапазон дат", "day": "День", "deduplicate_all": "Убрать все дубликаты", + "deduplication_criteria_1": "Размер изображения в байтах", + "deduplication_criteria_2": "Подсчет данных EXIF", + "deduplication_info": "Информация о дедупликации", + "deduplication_info_description": "Для автоматического предварительного выбора объектов и массового удаления дубликатов мы рассмотрим:", "default_locale": "Дата и время по умолчанию", "default_locale_description": "Использовать формат даты и времени в соответствии с языковым стандартом вашего браузера", "delete": "Удалить", diff --git a/i18n/sk.json b/i18n/sk.json index f3610acd8e159..9af007999b677 100644 --- a/i18n/sk.json +++ b/i18n/sk.json @@ -5,7 +5,7 @@ "acknowledge": "Rozumiem", "action": "Akcia", "actions": "Akcie", - "active": "Aktívny", + "active": "Aktívne", "activity": "Aktivita", "activity_changed": "Aktivita je {enabled, select, true{povolená} other {zakázaná}}", "add": "Pridať", @@ -23,7 +23,7 @@ "add_to": "Pridať do...", "add_to_album": "Pridať do albumu", "add_to_shared_album": "Pridať do zdieľaného albumu", - "add_url": "Pridaj URL", + "add_url": "Pridať URL", "added_to_archive": "Pridané do archívu", "added_to_favorites": "Pridané do obľúbených", "added_to_favorites_count": "Pridané {count, number} do obľúbených", @@ -100,9 +100,9 @@ "library_watching_enable_description": "Sledovať externé knižnice pre zmeny v súboroch", "library_watching_settings": "Sledovanie knižnice (EXPERIMENTÁLNE)", "library_watching_settings_description": "Automaticky sledovať zmenené súbory", - "logging_enable_description": "Povoliť zaznamenávanie", - "logging_level_description": "Ak je povolené, akú úroveň zaznamenávania použiť.", - "logging_settings": "Zaznamenávanie", + "logging_enable_description": "Povoliť logovanie", + "logging_level_description": "Ak je povolené, akú úroveň logovania použiť.", + "logging_settings": "Logovanie", "machine_learning_clip_model": "Model CLIP", "machine_learning_clip_model_description": "Názov modelu CLIP je uvedený tu. Pamätajte, že pri zmene modelu je nutné znovu spustiť úlohu 'Inteligentné vyhľadávanie' pre všetky obrázky.", "machine_learning_duplicate_detection": "Detekcia duplikátov", @@ -148,7 +148,7 @@ "map_settings_description": "Spravovať nastavenia mapy", "map_style_description": "URL na motív style.json", "metadata_extraction_job": "Extrahovať metadáta", - "metadata_extraction_job_description": "Získaj informácie metadátach z každej položky, ako napríklad GPS, tváre a rozlíšenie", + "metadata_extraction_job_description": "Vytiahne metadáta z každej položky, ako napríklad GPS, tváre a rozlíšenie", "metadata_faces_import_setting": "Povoliť import tváre", "metadata_faces_import_setting_description": "Importuj tváre z EXIF dát obrázkov a sidecar súborov", "metadata_settings": "Metadáta", @@ -233,7 +233,7 @@ "sidecar_job_description": "Objavte alebo synchronizujte metadáta Sidecar zo súborového systému", "slideshow_duration_description": "Čas zobrazenia obrázku v sekundách", "smart_search_job_description": "Spustite strojové učenie na médiách na podporu inteligentného vyhľadávania", - "storage_template_date_time_description": "Časová pečiatka vytvorenia médií sa používa pre informácie o dátume a čase", + "storage_template_date_time_description": "Časová pečiatka vytvorenia položky sa používa pre informácie o dátume a čase", "storage_template_date_time_sample": "Čas vzorky {date}", "storage_template_enable_description": "Povoliť nástroj šablóny úložiska", "storage_template_hash_verification_enabled": "Overenie hash povolené", @@ -252,7 +252,7 @@ "tag_cleanup_job": "Premazanie značiek", "template_email_available_tags": "V šablóne môžeš použiť nasledujúce stítky: {tags}", "template_email_if_empty": "Ak nie je zadaná žiadna šablóna, bude použitá predvolená šablóna.", - "template_email_invite_album": "Šablóna pre Pozvánka do albumu", + "template_email_invite_album": "Šablóna Pozvánky do albumu", "template_email_preview": "Ukážka", "template_email_settings": "Emailové šablóny", "template_email_settings_description": "Spravovanie vlastných šablón pre emailové upozornenia", @@ -266,11 +266,11 @@ "theme_settings_description": "Spravovať prispôsobenie webového rozhrania Immich", "these_files_matched_by_checksum": "Tieto súbory zodpovedajú kontrolným súčtom", "thumbnail_generation_job": "Generovať Miniatúry", - "thumbnail_generation_job_description": "Generujte veľké, malé a rozmazané miniatúry pre každé médium, ako aj miniatúry pre každú osobu", + "thumbnail_generation_job_description": "Generuje veľké, malé a rozostrení miniatúry pre každú položku, ako aj miniatúry pre každú osobu", "transcoding_acceleration_api": "API pre akceleráciu", "transcoding_acceleration_api_description": "Rozhranie API, ktoré bude interagovať s vaším zariadením s cieľom urýchliť prekódovanie. Toto nastavenie je „najlepšie úsilie“: pri zlyhaní sa vráti k softvérovému prekódovaniu. VP9 môže alebo nemusí fungovať v závislosti od vášho hardvéru.", - "transcoding_acceleration_nvenc": "NVENC (vyžaduje grafickú kartu NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (vyžaduje 7. generáciu Intel procesora alebo novšie)", + "transcoding_acceleration_nvenc": "NVENC (vyžaduje NVIDIA GPU)", + "transcoding_acceleration_qsv": "Quick Sync (vyžaduje 7. generáciu Intel CPU alebo novšiu)", "transcoding_acceleration_rkmpp": "RKMPP (iba na Rockchip SOC)", "transcoding_acceleration_vaapi": "VAAPI", "transcoding_accepted_audio_codecs": "Akceptované zvukové kodeky", @@ -496,7 +496,7 @@ "copy_link_to_clipboard": "Skopírovať do schránky", "copy_password": "Skopírovať heslo", "copy_to_clipboard": "Skopírovať do schránky", - "country": "Štát", + "country": "Krajina", "cover": "Titulka", "covers": "Dlaždice", "create": "Vytvoriť", @@ -523,6 +523,10 @@ "date_range": "Rozsah dátumu", "day": "Deň", "deduplicate_all": "Deduplikovať všetko", + "deduplication_criteria_1": "Veľkosť obrázku v bajtoch", + "deduplication_criteria_2": "Počet EXIF údajov", + "deduplication_info": "Info o deduplikácii", + "deduplication_info_description": "Na automatický predvýber položiek a hromadné odstránenie duplicít, sa pozeráme do:", "default_locale": "Predvolená Lokalizácia", "default_locale_description": "Formátovanie dátumu a čísel podľa lokalizácie vášho prehliadača", "delete": "Vymazať", @@ -643,6 +647,8 @@ "unable_to_add_import_path": "Nie je možné pridať cestu importu", "unable_to_add_partners": "Nie je možné pridať partnerov", "unable_to_add_remove_archive": "Nie je možné {archived, select, true {odstrániť položku z} other {pridať položku do}} archívu", + "unable_to_add_remove_favorites": "Nepodarilo sa {favorite, select, true {pridať položku do} other {odstrániť položku z}} obľúbených", + "unable_to_archive_unarchive": "Nepodarilo sa {archived, select, true {archivovať} other {odarchivovať}}", "unable_to_change_album_user_role": "Nie je možné zmeniť rolu používateľa pre album", "unable_to_change_date": "Nie je možné zmeniť dátum", "unable_to_change_favorite": "Nie je možné zmeniť obľúbené pre položku", @@ -683,6 +689,7 @@ "unable_to_log_out_device": "Nie je možné odhlásiť zariadenie", "unable_to_login_with_oauth": "Nie je možné prihlásiť sa cez OAuth", "unable_to_play_video": "Nie je možné prehrať video", + "unable_to_reassign_assets_existing_person": "Nepodarilo sa priradiť položku k {name, select, null {existujúcej osobe} other {{name}}}", "unable_to_reassign_assets_new_person": "Nie je možné priradiť položky novej osobe", "unable_to_refresh_user": "Nie je možné aktualizovať používateľa", "unable_to_remove_album_users": "Nie je možné odstrániť používateľov z albumu", @@ -778,11 +785,11 @@ "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} nasnímané s {person1} a {person2} dňa {date}", "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} nasnímané s {person1}, {person2} a {person3} dňa {date}", "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} nasnímané s {person1}, {person2} a {additionalCount, number} inými dňa {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} nasnímané v {city}, {country} dňa {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} nasnímané v {city}, {country} s {person1} dňa {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} nasnímané v {city}, {country} s {person1} a {person2} dňa {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} nasnímané v {city}, {country} s {person1}, {person2} a {person3} dňa {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} nasnímané v {city}, {country} s {person1}, {person2} a {additionalCount, number} inými dňa {date}", + "image_alt_text_date_place": "{isVideo, select, true {Video} other {Obrázok}} nasnímané v {city}, {country} dňa {date}", + "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Obrázok}} zo dňa {date} v {city}, {country} s {person1}", + "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Obrázok}} v {city}, {country} s {person1} a {person2} zo dňa {date}", + "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Obrázok}} zo dňa {date} v {city}, {country} s {person1}, {person2} a {person3}", + "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Obrázok}} nasnímaný v {city}, {country} s {person1}, {person2} a {additionalCount, number} inými dňa {date}", "immich_logo": "Logo Immich", "immich_web_interface": "Webové rozhranie Immich", "import_from_json": "Importovať z JSON", @@ -806,7 +813,9 @@ "jobs": "Úlohy", "keep": "Ponechať", "keep_all": "Ponechať všetko", - "keyboard_shortcuts": "", + "keep_this_delete_others": "Ponechať toto, odstrániť ostatné", + "kept_this_deleted_others": "Ponechá túto položku a odstráni {count, plural, one {# položku} other {# položiek}}", + "keyboard_shortcuts": "Klávesové skratky", "language": "Jazyk", "language_setting_description": "Vyberte preferovaný jazyk", "last_seen": "Naposledy videné", @@ -818,163 +827,264 @@ "library": "Knižnica", "library_options": "Možnosti knižnice", "light": "Svetlý", + "like_deleted": "Like odstránený", "link_motion_video": "Pripojiť pohyblivé video", "link_options": "Možnosti odkazu", "link_to_oauth": "Prepojiť s OAuth", "linked_oauth_account": "Pripojený OAuth účet", "list": "Zoznam", "loading": "Načítavanie", - "loading_search_results_failed": "", + "loading_search_results_failed": "Načítanie výsledkov hľadania sa nepodarilo", "log_out": "Odhlásiť sa", "log_out_all_devices": "Odhlásiť všetky zariadenia", "logged_out_all_devices": "Všetky zariadenia odhlásené", "logged_out_device": "Zariadenie odhlásené", "login": "Prihlásenie", "login_has_been_disabled": "Prihlásenie bolo vypnuté.", + "logout_all_device_confirmation": "Ste si istý, že sa chcete odhlásiť zo všetkých zariadení?", + "logout_this_device_confirmation": "Ste si istý, že sa chcete odhlásiť z tohoto zariadenia?", "longitude": "Zemepisná dĺžka", "look": "Zobrazenie", - "loop_videos": "", - "loop_videos_description": "", - "make": "", + "loop_videos": "Opakovať videá", + "loop_videos_description": "Povolí prehrávanie videí v slučke v detailnom zobrazení.", + "main_branch_warning": "Používate vývojársku verziu; silno odporúčame používať vydané verzie!", + "make": "Výrobca", "manage_shared_links": "Spravovať zdieľané odkazy", - "manage_sharing_with_partners": "", - "manage_the_app_settings": "", - "manage_your_account": "", - "manage_your_api_keys": "", - "manage_your_devices": "", - "manage_your_oauth_connection": "", + "manage_sharing_with_partners": "Spravovať zdieľanie s partnermi", + "manage_the_app_settings": "Spravovať nastavenia aplikácie", + "manage_your_account": "Spravovať váš účet", + "manage_your_api_keys": "Spravovať vaše API kľúče", + "manage_your_devices": "Spravovať vaše prihlásené zariadenia", + "manage_your_oauth_connection": "Spravovať vaše OAuth spojenia", "map": "Mapa", - "map_marker_with_image": "", + "map_marker_for_images": "Značka na mape pre obrázky odfotené v {city}, {country}", + "map_marker_with_image": "Mapová značka pre obrázok", "map_settings": "Nastavenia máp", - "media_type": "", - "memories": "", - "memories_setting_description": "", + "matches": "Zhody", + "media_type": "Typ média", + "memories": "Spomienky", + "memories_setting_description": "Spravuje čo vidíte v spomienkach", + "memory": "Pamäť", + "memory_lane_title": "Pás spomienok {title}", "menu": "Menu", - "merge": "", - "merge_people": "", - "merge_people_successfully": "", - "minimize": "", - "minute": "", - "missing": "", - "model": "", + "merge": "Zlúčiť", + "merge_people": "Zlúčiť ľudí", + "merge_people_limit": "Zlúčiť môžete naraz najviac 5 tvárí", + "merge_people_prompt": "Chcete zlúčiť týchto ľudí? Táto akcia sa nedá vrátiť.", + "merge_people_successfully": "Zlúčenie ľudí sa podarilo", + "merged_people_count": "Zlúčení {count, plural, one {# človek} other {# ľudia}}", + "minimize": "Minimalizovať", + "minute": "Minúta", + "missing": "Chýbajúce", + "model": "Model", "month": "Mesiac", - "more": "", - "moved_to_trash": "", - "my_albums": "", + "more": "Viac", + "moved_to_trash": "Presunuté do koša", + "my_albums": "Moje albumy", "name": "Meno", - "name_or_nickname": "", + "name_or_nickname": "Meno alebo prezývka", "never": "nikdy", - "new_api_key": "", + "new_album": "Nový album", + "new_api_key": "Nový API kľúč", "new_password": "Nové heslo", - "new_person": "", - "new_user_created": "", + "new_person": "Nová osoba", + "new_user_created": "Nový používateľ vytvorený", "new_version_available": "JE DOSTUPNÁ NOVÁ VERZIA", - "newest_first": "", + "newest_first": "Najnovšie prvé", "next": "Ďalej", - "next_memory": "", - "no": "", - "no_albums_message": "", + "next_memory": "Ďalšia spomienka", + "no": "Nie", + "no_albums_message": "Vytvorí album na organizovanie fotiek a videí", + "no_albums_with_name_yet": "Vyzerá, že zatiaľ nemáte album s týmto názvom.", + "no_albums_yet": "Vyzerá, že zatiaľ nemáte žiadne albumy.", "no_archived_assets_message": "Archivovať fotografie a videá, aby sa skryli zo zobrazenia Fotografie", - "no_assets_message": "", - "no_exif_info_available": "", - "no_explore_results_message": "", - "no_favorites_message": "", - "no_libraries_message": "", - "no_name": "", - "no_places": "", - "no_results": "", - "no_shared_albums_message": "", - "not_in_any_album": "", + "no_assets_message": "KLIKNITE A NAHRAJTE SVOJU PRVÚ FOTKU", + "no_duplicates_found": "Nenašli sa žiadne duplicity.", + "no_exif_info_available": "Nie sú dostupné exif údaje", + "no_explore_results_message": "Nahrajte viac fotiek na objavovanie vašej zbierky.", + "no_favorites_message": "Pridajte si obľúbené, aby ste rýchlo našli svoje najlepšie obrázky a videá", + "no_libraries_message": "Vytvorí externú knižnicu na prezeranie fotiek a videí", + "no_name": "Bez mena", + "no_places": "Bez miesta", + "no_results": "Žiadne výsledky", + "no_results_description": "Skúste synonymum alebo všeobecnejší výraz", + "no_shared_albums_message": "Vytvorí album na zdieľanie fotiek a videí s ľuďmi vo vašej sieti", + "not_in_any_album": "Nie je v žiadnom albume", "note_apply_storage_label_to_previously_uploaded assets": "Poznámka: Ak chcete použiť Štítok úložiska na predtým nahrané médiá, spustite príkaz", - "notes": "", + "note_unlimited_quota": "Poznámka: Zadajte 0 pre neobmedzenú kvótu", + "notes": "Poznámky", "notification_toggle_setting_description": "Povoliť e-mailové upozornenia", "notifications": "Oznámenia", "notifications_setting_description": "Spravovať upozornenia", "oauth": "OAuth", - "offline": "", - "ok": "", - "oldest_first": "", + "official_immich_resources": "Oficiálne Immich zdroje", + "offline": "Offline", + "offline_paths": "Offline cesty", + "offline_paths_description": "Tieto výsledky môžu byť kvôli ručnému vymazaniu súborov ktoré nie sú súčasťou externej knižnice.", + "ok": "OK", + "oldest_first": "Najstaršie prvé", + "onboarding": "Na palube", + "onboarding_privacy_description": "Nasledujúce (voliteľné) funkcie závisia na externých službách, a kedykoľvek ich môžete vypnúť v admin nastaveniach.", + "onboarding_theme_description": "Vyberte farbu témy pre váš server. Môžete to aj neskôr zmeniť vo vašich nastaveniach.", + "onboarding_welcome_description": "Poďme nastaviť pre váš server niekoľko základných nastavení.", "onboarding_welcome_user": "Vitaj, {user}", - "online": "", - "only_favorites": "", - "open_the_search_filters": "", + "online": "Online", + "only_favorites": "Len obľúbené", + "open_in_map_view": "Otvoriť v mape", + "open_in_openstreetmap": "Otvoriť v OpenStreetMap", + "open_the_search_filters": "Otvoriť vyhľadávacie filtre", "options": "Nastavenia", "or": "alebo", "organize_your_library": "Usporiadajte svoju knižnicu", - "other": "", + "original": "originál", + "other": "Ostatné", "other_devices": "Ďalšie zariadenia", - "other_variables": "", + "other_variables": "Ostatné premenné", "owned": "Vlastnené", "owner": "Vlastník", - "partner_sharing": "", - "partners": "", + "partner": "Partner", + "partner_can_access": "{partner} môže pristupovať", + "partner_can_access_assets": "Všetky vaše fotky a videá, okrem Archivovaných a Odstránených", + "partner_can_access_location": "Miesto kde bola fotka spravená", + "partner_sharing": "Zdieľanie s partnerom", + "partners": "Partneri", "password": "Heslo", - "password_does_not_match": "", - "password_required": "", - "password_reset_success": "", + "password_does_not_match": "Heslá sa nezhodujú", + "password_required": "Heslo je povinné", + "password_reset_success": "Obnovenie hesla úspešné", "past_durations": { - "days": "", - "hours": "", - "years": "" + "days": "{days, plural, one {Posledný deň} other {Posledných # dní }}", + "hours": "{hours, plural, one {Posledná hodina} other {Posledných # hodín}}", + "years": "{years, plural, one {Posledný rok} other {Posledné # roky}}" }, - "path": "", - "pattern": "", - "pause": "", - "pause_memories": "", - "paused": "", - "pending": "", + "path": "Cesta", + "pattern": "Vzor", + "pause": "Pozastaviť", + "pause_memories": "Pozastaviť spomienky", + "paused": "Pozastavené", + "pending": "Čakajúce", "people": "Ľudia", - "people_sidebar_description": "", - "permanent_deletion_warning": "", - "permanent_deletion_warning_setting_description": "", - "permanently_delete": "", - "permanently_deleted_asset": "", + "people_edits_count": "{count, plural, one {Upravená # osoba} other {Upravených # ľudí}}", + "people_feature_description": "Prehliadanie fotiek a videí zoskupených podľa ľudí", + "people_sidebar_description": "Zobrazí odkaz na Ľudí v bočnom paneli", + "permanent_deletion_warning": "Varovanie o trvalom zmazaní", + "permanent_deletion_warning_setting_description": "Zobraziť varovanie pri trvalom zmazaní položky", + "permanently_delete": "Trvalo zmazať", + "permanently_delete_assets_count": "Navždy zmazať {count, plural, one {položku} other {položky}}", + "permanently_delete_assets_prompt": "Naozaj si prajete navždy zmazať {count, plural, one {túto položku?} other {týchto # položiek?}} Vymažú sa aj {count, plural, one {zo svojho albumu} other {zo svojich albumov}}.", + "permanently_deleted_asset": "Navždy odstránená položka", + "permanently_deleted_assets_count": "Navždy {count, plural, one {odstránená # položka} other {odstránené # položky}}", + "person": "Osoba", + "person_hidden": "{name}{hidden, select, true { (skryté)} other {}}", + "photo_shared_all_users": "Vyzerá, že zdieľate svoje fotky so všetkými používateľmi alebo nemáte žiadnych používateľov.", "photos": "Fotografie", "photos_and_videos": "Fotografie & Videa", - "photos_from_previous_years": "", - "pick_a_location": "", + "photos_count": "{count, plural, one {{count, number} Fotka} other {{count, number} Fotiek}}", + "photos_from_previous_years": "Fotky z minulých rokov", + "pick_a_location": "Vyberte miesto", "place": "Miesto", "places": "Miesta", "play": "Prehrať", - "play_memories": "", - "play_motion_photo": "", - "play_or_pause_video": "", - "port": "", - "preset": "", - "preview": "", - "previous": "", - "previous_memory": "", - "previous_or_next_photo": "", - "primary": "", - "profile_picture_set": "", + "play_memories": "Prehrať spomienky", + "play_motion_photo": "Prehrať pohyblivú fotku", + "play_or_pause_video": "Pustí alebo pozastaví video", + "port": "Port", + "preset": "Prednastavenie", + "preview": "Náhľad", + "previous": "Predošlé", + "previous_memory": "Predošlá spomienka", + "previous_or_next_photo": "Predošlá alebo ďalšia fotka", + "primary": "Primárne", + "privacy": "Súkromie", + "profile_image_of_user": "Profilový obrázok používateľa {user}", + "profile_picture_set": "Profilový obrázok nastavený.", "public_album": "Verejný album", - "public_share": "", + "public_share": "Verejné zdieľanie", + "purchase_account_info": "Podporovateľ", + "purchase_activated_subtitle": "Ďakujeme za podporu Immich a softvéru s otvorenými zdrojákmi", "purchase_activated_time": "Aktivované {date, date}", + "purchase_activated_title": "Váš kľúč je úspešne aktivovaný", "purchase_button_activate": "Aktivovať", + "purchase_button_buy": "Kúpiť", + "purchase_button_buy_immich": "Kúpiť Immich", "purchase_button_never_show_again": "Už viac nezobrazovať", + "purchase_button_reminder": "Pripomenúť mi o 30 dní", + "purchase_button_remove_key": "Odobrať kľúč", + "purchase_button_select": "Vybrať", + "purchase_failed_activation": "Aktivácia sa nepodarila! Prosím skontrolujte email či je správny kľúč produktu!", + "purchase_individual_description_1": "Pre jednotlivca", + "purchase_individual_description_2": "Stav podporovateľa", + "purchase_individual_title": "Jednotlivec", + "purchase_input_suggestion": "Máte produktový kľúč? Zadajte ho nižšie", + "purchase_license_subtitle": "Kúpte si Immich a podporte neustály vývoj tejto služby", + "purchase_lifetime_description": "Doživotná platnosť", + "purchase_option_title": "MOŽNOSTI NÁKUPU", + "purchase_panel_info_1": "Vývoj Immich zaberá veľa času a úsilia, a máme zamestnaných fulltime inžinierov, aby ho spravili ako sa najlepšie dá. Naša misia je, aby sa open-source softvér a etické biznis praktiky stali udržateľným zdrojom príjmu pre vývojárov a vytvorili ekosystém rešpektujúci súkromie so skutočnými náhradami voči zneužívajúcim cloudovým službám.", + "purchase_panel_info_2": "Keďže sme zaviazaní nezavádzať paywally, nezískate týmto nákupom žiadne prídavné funkcie. Spoliehame sa na používateľov ako vy na podporu neustáleho vývoja Immich.", "purchase_panel_title": "Podporiť projekt", - "reaction_options": "", - "read_changelog": "", + "purchase_per_server": "Za server", + "purchase_per_user": "Za používateľa", + "purchase_remove_product_key": "Odstrániť produktový kľúč", + "purchase_remove_product_key_prompt": "Naozaj chcete odstrániť produktový kľúč?", + "purchase_remove_server_product_key": "Odstrániť produktový kľúč servera", + "purchase_remove_server_product_key_prompt": "Naozaj chcete odstrániť produktový kľúč servera?", + "purchase_server_description_1": "Pre celý server", + "purchase_server_description_2": "Stav podporovateľa", + "purchase_server_title": "Server", + "purchase_settings_server_activated": "Produktový kľúč servera spravuje admin", + "rating": "Hodnotenie hviezdičkami", + "rating_clear": "Vyčistiť hodnotenie", + "rating_count": "{count, plural, one {# hviezdička} other {# hviezdičky}}", + "rating_description": "Zobrazí EXIF hodnotenie v info paneli", + "reaction_options": "Možnosti reakcie", + "read_changelog": "Prečítať zoznam zmien", + "reassign": "Preradiť", + "reassigned_assets_to_existing_person": "Preradené {count, plural, one {# položka} other {# položky}} k {name, select, null {existujúcej osobe} other {{name}}}", + "reassigned_assets_to_new_person": "Preradené {count, plural, one {# položka} other {# položiek}} novej osobe", + "reassing_hint": "Priradí zvolenú položku k existujúcej osobe", "recent": "Nedávne", - "recent_searches": "", + "recent-albums": "Posledné albumy", + "recent_searches": "Posledné vyhľadávania", "refresh": "Obnoviť", + "refresh_encoded_videos": "Obnoviť enkódované videá", + "refresh_faces": "Obnoviť tváre", "refresh_metadata": "Obnoviť metadáta", "refresh_thumbnails": "Obnoviť miniatúry", "refreshed": "Aktualizované", - "refreshes_every_file": "", + "refreshes_every_file": "Znova prečíta všetky existujúce a nové súbory", + "refreshing_encoded_video": "Obnovovanie enkódovaných videí", + "refreshing_faces": "Obnovovnie tvárí", + "refreshing_metadata": "Obnovovanie metadát", + "regenerating_thumbnails": "Pregenerovanie náhľadov", "remove": "Odstrániť", - "remove_deleted_assets": "", + "remove_assets_album_confirmation": "Naozaj chcete odstrániť {count, plural, one {# položky} other {# položiek}} z albumu?", + "remove_assets_shared_link_confirmation": "Naozaj chcete odstrániť {count, plural, one {# položku} other {# položiek}} z tohoto zdieľaného odkazu?", + "remove_assets_title": "Odstrániť položky?", + "remove_custom_date_range": "Odstrániť vlastný rozsah dátumov", + "remove_deleted_assets": "Odstrániť vymazané položky", "remove_from_album": "Odstrániť z albumu", - "remove_from_favorites": "", - "remove_from_shared_link": "", + "remove_from_favorites": "Odstrániť z obľúbených", + "remove_from_shared_link": "Odstrániť zo zdieľaného odkazu", + "remove_url": "Odstrániť URL", "remove_user": "Odstrániť používateľa", + "removed_api_key": "Odstrániť API kľúč: {name}", + "removed_from_archive": "Odstránené z archívu", + "removed_from_favorites": "Odstránené z obľúbených", + "removed_from_favorites_count": "{count, plural, other {Odstránených #}} z obľúbených", + "removed_tagged_assets": "Odstránená značka z {count, plural, one {# položky} other {# položiek}}", + "rename": "Premenovať", "repair": "Opraviť", - "repair_no_results_message": "", - "replace_with_upload": "", + "repair_no_results_message": "Nesledované a chýbajúce súbory sa zobrazia tu", + "replace_with_upload": "Nahradiť nahraním", + "repository": "Repozitár", "require_password": "Vyžadovať heslo", + "require_user_to_change_password_on_first_login": "Vyžadovať zmenu hesla po prvom prihlásení", "reset": "Resetovať", "reset_password": "Obnoviť heslo", - "reset_people_visibility": "", + "reset_people_visibility": "Resetovať viditeľnosť ľudí", + "reset_to_default": "Resetovať na predvolené", + "resolve_duplicates": "Vyriešiť duplicity", + "resolved_all_duplicates": "Vyriešené všetky duplicity", "restore": "Navrátiť", "restore_all": "Navrátit všetko", "restore_user": "Navrátiť používateľa", @@ -1017,7 +1127,7 @@ "search_your_photos": "Hľadajte svoje fotky", "searching_locales": "Hľadám lokality...", "second": "Sekundy", - "see_all_people": "Vydieť všetky osoby", + "see_all_people": "Pozrieť všetky osoby", "select_album_cover": "Vyberte obal albumu", "select_all": "Vybrať všetko", "select_all_duplicates": "Vybrať všetky duplikáty", @@ -1040,6 +1150,7 @@ "server_version": "Verzia Servera", "set": "Nastaviť", "set_as_album_cover": "Nastaviť ako obal albumu", + "set_as_featured_photo": "Nastaviť ako hlavnú fotku", "set_as_profile_picture": "Nastaviť ako profilový obrázok", "set_date_of_birth": "Nastaviť dátum narodenia", "set_profile_picture": "Nastaviť profilový obrázok", @@ -1062,91 +1173,143 @@ "shift_to_permanent_delete": "stlačte ⇧ pre nemenné zmazanie pložiek", "show_album_options": "Zobraziť možnosti albumu", "show_albums": "Zobraziť albumy", - "show_file_location": "", + "show_all_people": "Zobraziť všetkých ľudí", + "show_and_hide_people": "Zobraziť a skryť ľudí", + "show_file_location": "Zobrazí umiestnenie súboru", "show_gallery": "Zobraziť galériu", - "show_hidden_people": "", + "show_hidden_people": "Zobraziť skrytých ľudí", "show_in_timeline": "Zobraziť na časovej osi", - "show_in_timeline_setting_description": "", + "show_in_timeline_setting_description": "Zobrazí fotky a videá tohoto používateľa na časovej osi", "show_keyboard_shortcuts": "Zobraziť klávesové skratky", "show_metadata": "Zobraziť metadáta", - "show_or_hide_info": "", + "show_or_hide_info": "Zobrazí alebo skryje info", "show_password": "Zobraziť heslo", - "show_person_options": "", - "show_progress_bar": "", + "show_person_options": "Zobrazí možnosti osoby", + "show_progress_bar": "Zobrazí ukazovateľ priebehu", "show_search_options": "Zobraziť možnosti vyhľadávania", - "shuffle": "", + "show_slideshow_transition": "Zobrazí prechody v prezentácii", + "show_supporter_badge": "Odznak podporovateľa", + "show_supporter_badge_description": "Zobraziť odznak podporovateľa", + "shuffle": "Náhodné poradie", + "sidebar": "Bočný panel", + "sidebar_display_description": "Zobrazí odkaz na pohľad v bočnom paneli", "sign_out": "Odhlásiť sa", - "sign_up": "", + "sign_up": "Registrovať", "size": "Veľkosť", - "skip_to_content": "", + "skip_to_content": "Preskočiť na obsah", + "skip_to_folders": "Preskočiť do priečinkov", "skip_to_tags": "Preskočiť ku štítkom", - "slideshow": "", - "slideshow_settings": "", + "slideshow": "Prezentácia", + "slideshow_settings": "Nastavenia prezentácie", "sort_albums_by": "Zoradiť albumy podľa...", "sort_created": "Dátum vytvorenia", "sort_items": "Počet položiek", "sort_modified": "Dátum úpravy", "sort_oldest": "Najstaršia fotografia", + "sort_people_by_similarity": "Zoradiť ľudí podľa podobnosti", "sort_recent": "Najnovšia fotografia", "sort_title": "Názov", "source": "Zdroj", "stack": "Zoskupenie", - "stack_selected_photos": "", - "stacktrace": "", + "stack_duplicates": "Zoskupiť duplicity", + "stack_select_one_photo": "Vyberte jednu hlavnú fotku pre zoskupenie", + "stack_selected_photos": "Zoskupiť vybraté fotky", + "stacked_assets_count": "{count, plural, one {Zoskupená # položka} other {Zoskupených # položiek}}", + "stacktrace": "Výpis zásobníku", + "start": "Štart", "start_date": "Začiatočný dátum", - "state": "", - "status": "", - "stop_motion_photo": "", + "state": "Stav", + "status": "Stav", + "stop_motion_photo": "Stopmotion fotka", "stop_photo_sharing": "Zastaviť zdieľanie vašich fotiek?", + "stop_photo_sharing_description": "{partner} už nebude mať prístup k vašim fotkám.", + "stop_sharing_photos_with_user": "Zastaviť zdieľanie týchto fotiek s týmto používateľom", "storage": "Ukladací priestor", "storage_label": "Štítok úložiska", + "storage_usage": "Využitých {used} z {available}", "submit": "Odoslať", "suggestions": "Návrhy", - "sunrise_on_the_beach": "", - "swap_merge_direction": "", - "sync": "", + "sunrise_on_the_beach": "Východ slnka na pláži", + "support": "Podpora", + "support_and_feedback": "Podpora a spätná väzba", + "support_third_party_description": "Vaša inštalácia Immich bola pripravená treťou stranou. Problémy, ktoré sa vyskytli, môžu byť spôsobené týmto balíčkom, preto sa na nich obráťte v prvom rade cez nasledujúce odkazy.", + "swap_merge_direction": "Vymeniť smer zlúčenia", + "sync": "Synchronizovať", + "tag": "Značka", + "tag_assets": "Pridať značku", + "tag_created": "Vytvorená značka: {tag}", + "tag_feature_description": "Prehliadanie fotiek a videá zoskupených podľa tematických značiek", + "tag_not_found_question": "Neviete nájsť značku? Vytvorte novú značku.", + "tag_updated": "Upravená značka: {tag}", + "tagged_assets": "Značka priradená {count, plural, one {# položke} other {# položkám}}", "tags": "Štítky", - "template": "", + "template": "Šablóna", "theme": "Téma", - "theme_selection": "", - "theme_selection_description": "", - "time_based_memories": "", + "theme_selection": "Výber témy", + "theme_selection_description": "Automaticky nastaví tému na svetlú alebo tmavú podľa systémových preferencií v prehliadači", + "they_will_be_merged_together": "Zlúčia sa dokopy", + "third_party_resources": "Zdroje tretích strán", + "time_based_memories": "Časové spomienky", + "timeline": "Časová os", "timezone": "Časové pásmo", "to_archive": "Archivovať", "to_change_password": "Zmeniť heslo", + "to_favorite": "Obľúbiť", + "to_login": "Prihlásiť", + "to_parent": "Prejsť k nadradenému", "to_trash": "Kôš", - "toggle_settings": "", - "toggle_theme": "", - "total_usage": "", + "toggle_settings": "Prepnúť nastavenie", + "toggle_theme": "Prepnúť tmavú tému", + "total": "Celkom", + "total_usage": "Celkové využitie", "trash": "Kôš", - "trash_all": "", + "trash_all": "Všetko do koša", + "trash_count": "{count, number} do koša", + "trash_delete_asset": "Položky do koša/odstrániť", "trash_no_results_message": "Vymazané fotografie a videá sa zobrazia tu.", - "type": "", + "trashed_items_will_be_permanently_deleted_after": "Položky v koši sa natrvalo vymažú po {days, plural, one {# dni} other {# dňoch}}.", + "type": "Typ", "unarchive": "Odarchivovať", + "unarchived_count": "{count, plural, other {Odarchivovaných #}}", "unfavorite": "Odznačiť ako obľúbené", - "unhide_person": "", - "unknown": "", + "unhide_person": "Odkryť osobu", + "unknown": "Neznáme", "unknown_year": "Neznámy rok", - "unlink_oauth": "", - "unlinked_oauth_account": "", + "unlimited": "Neobmedzené", + "unlink_motion_video": "Odpojiť pohyblivé video", + "unlink_oauth": "Odpojiť OAuth", + "unlinked_oauth_account": "Odpojiť OAuth účet", + "unnamed_album": "Nepomenovaný album", "unnamed_album_delete_confirmation": "Ste si istý, že chcete zmazať tento album?", + "unnamed_share": "Nepomenované zdieľanie", "unsaved_change": "Neuložená zmena", - "unselect_all": "", + "unselect_all": "Zrušiť výber všetkých", + "unselect_all_duplicates": "Zrušiť výber všetkých duplicít", "unstack": "Odskupiť", - "up_next": "", - "updated_password": "", + "unstacked_assets_count": "{count, plural, one {Rozložená # položka} few {Rozložené # položky} other {Rozložených # položiek}}", + "untracked_files": "Nesledované súbory", + "untracked_files_decription": "Tieto súbory nie sú sledované aplikáciou. Dôvodom môže byť zlyhaný presun, prerušené nahrávanie, alebo výsledkom bugu", + "up_next": "To je všetko", + "updated_password": "Heslo zmenené", "upload": "Nahrať", - "upload_concurrency": "", + "upload_concurrency": "Súbežnosť nahrávania", + "upload_errors": "Nahrávanie ukončené s {count, plural, one {# chybou} other {# chybami}}, obnovte stránku aby sa zobrazili nové položky.", + "upload_progress": "Ostáva {remaining, number} - Spracovaných {processed, number}/{total, number}", + "upload_skipped_duplicates": "{count, plural, one {Preskočená # duplicita} few {Preskočené # duplicity} other {Preskočených # duplicít}}", "upload_status_duplicates": "Duplikáty", "upload_status_errors": "Chyby", "upload_status_uploaded": "Nahrané", "upload_success": "Nahrávanie úspešné, pridané súbory sa zobrazia po obnovení stránky.", "url": "Odkaz URL", "usage": "Použitie", + "use_custom_date_range": "Použite radšej vlastný rozsah dátumov", "user": "Používateľ", "user_id": "Používateľské ID", + "user_liked": "Používateľovi {user} sa páči {type, select, photo {táto fotka} video {toto video} asset {táto položka} other {toto}}", + "user_purchase_settings": "Nákup", + "user_purchase_settings_description": "Správa vášho nákupu", "user_role_set": "Nastav {user} ako {role}", - "user_usage_detail": "", + "user_usage_detail": "Podrobnosti o využívaní používateľmi", "user_usage_stats": "Štatistiky využitia účtu", "user_usage_stats_description": "Zobraziť štatistiky využitia účtu", "username": "Používateľské meno", @@ -1156,24 +1319,32 @@ "variables": "Premenné", "version": "Verzia", "version_announcement_closing": "Tvoj kamarát, Alex", + "version_announcement_message": "Ahoj! Nová verzia Immich je dostupná. Prosím prečítajte si poznámky k vydaniu, aby ste sa uistili, že inštalácia bude aktuálna bez problémov, najmä ak používate WatchTower alebo akýkoľvek spôsob automatickej aktualizácie Immich servera.", "version_history": "História verzií", + "version_history_item": "Inštalovaná {version} dňa {date}", "video": "Video", - "video_hover_setting_description": "", + "video_hover_setting": "Prehrávať video náhľad pri nabehnutí myšou", + "video_hover_setting_description": "Prehrá video náhľad keď kurzor myši prejde cez položku. Aj keď je vypnuté, prehrávanie sa môže spustiť nabehnutí cez ikonu Prehrať.", "videos": "Videá", + "videos_count": "{count, plural, one {# Video} few {# Videá} other {# Videí}}", "view": "Zobraziť", "view_album": "Zobraziť Album", "view_all": "Zobraziť všetky", "view_all_users": "Zobraziť všetkých používateľov", "view_in_timeline": "Zobraziť v časovej osi", "view_links": "Zobraziť odkazy", + "view_name": "Zobraziť", "view_next_asset": "Zobraziť nasledujúci súbor", "view_previous_asset": "Zobraziť predchádzajúci súbor", - "waiting": "", + "view_stack": "Zobraziť zoskupenie", + "visibility_changed": "Viditeľnosť zmenená pre {count, plural, one {# osobu} other {# ľudí}}", + "waiting": "Čaká", "warning": "Varovanie", "week": "Týždeň", "welcome": "Vitajte", "welcome_to_immich": "Vitajte v Immich", "year": "Rok", + "years_ago": "pred {years, plural, one {# rokom} other {# rokmi}}", "yes": "Áno", "you_dont_have_any_shared_links": "Nemáte žiadne zdielané linky", "zoom_image": "Priblížiť obrázok" diff --git a/i18n/sl.json b/i18n/sl.json index 6f26af2563c13..5073efcabc069 100644 --- a/i18n/sl.json +++ b/i18n/sl.json @@ -523,6 +523,10 @@ "date_range": "Časovno obdobje", "day": "Dan", "deduplicate_all": "Odstrani vse podvojene", + "deduplication_criteria_1": "Velikost slike v bajtih", + "deduplication_criteria_2": "Število podatkov EXIF", + "deduplication_info": "Informacije o deduplikaciji", + "deduplication_info_description": "Za samodejno vnaprejšnjo izbiro sredstev in množično odstranjevanje dvojnikov si ogledamo:", "default_locale": "Privzeti jezik", "default_locale_description": "Oblikujte datume in številke glede na lokalne nastavitve brskalnika", "delete": "Izbriši", diff --git a/i18n/sr_Cyrl.json b/i18n/sr_Cyrl.json index 6d7ba24a24e29..d9b095467ba60 100644 --- a/i18n/sr_Cyrl.json +++ b/i18n/sr_Cyrl.json @@ -523,6 +523,10 @@ "date_range": "Распон датума", "day": "Дан", "deduplicate_all": "Де-дуплицирај све", + "deduplication_criteria_1": "Величина слике у бајтовима", + "deduplication_criteria_2": "Број EXIF података", + "deduplication_info": "Информације о дедупликацији", + "deduplication_info_description": "Да бисмо аутоматски унапред одабрали датотеке и уклонили дупликате групно, гледамо:", "default_locale": "Подразумевана локација (locale)", "default_locale_description": "Форматирајте датуме и бројеве на основу локализације вашег претраживача", "delete": "Обриши", diff --git a/i18n/sr_Latn.json b/i18n/sr_Latn.json index 13bc7f11779df..42536767683be 100644 --- a/i18n/sr_Latn.json +++ b/i18n/sr_Latn.json @@ -523,6 +523,10 @@ "date_range": "Raspon datuma", "day": "Dan", "deduplicate_all": "De-dupliciraj sve", + "deduplication_criteria_1": "Veličina slike u bajtovima", + "deduplication_criteria_2": "Broj EXIF podataka", + "deduplication_info": "Informacije o deduplikaciji", + "deduplication_info_description": "Da bismo automatski unapred odabrali datoteke i uklonili duplikate grupno, gledamo:", "default_locale": "Podrazumevana lokacija (locale)", "default_locale_description": "Formatirajte datume i brojeve na osnovu lokalizacije vašeg pretraživača", "delete": "Obriši", diff --git a/i18n/sv.json b/i18n/sv.json index 6910fa95894c1..73d9ff51cbc2f 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -523,8 +523,11 @@ "date_range": "Datumintervall", "day": "Dag", "deduplicate_all": "Deduplicera alla", + "deduplication_criteria_1": "Bildstorlek i bytes", + "deduplication_criteria_2": "Räkning av EXIF-data", + "deduplication_info": "Dedupliceringsinformation", "default_locale": "Standardplats", - "default_locale_description": "Formatera datum och siffror baserat på din webbläsares lokalitet", + "default_locale_description": "Formatera datum och siffror baserat på din webbläsares språkversion", "delete": "Radera", "delete_album": "Ta bort album", "delete_api_key_prompt": "Är du säker på att du vill ta bort denna API-nyckel?", @@ -913,6 +916,7 @@ "notifications_setting_description": "Hantera aviseringar", "oauth": "OAuth", "offline": "Frånkopplad", + "offline_paths": "Offlinevägar", "offline_paths_description": "Dessa resultat kan bero på att filer som ej ingår i ett externt bibliotek har tagits bort manuellt.", "ok": "OK", "oldest_first": "Äldst först", @@ -957,7 +961,7 @@ "pending": "Väntande", "people": "Personer", "people_edits_count": "Redigerad {count, plural, one {# person} other {# people}}", - "people_feature_description": "Visar foton och videor grupperade per personer", + "people_feature_description": "Visar foton och videor grupperade efter personer", "people_sidebar_description": "Visa en länk till Personer i sidopanelen", "permanent_deletion_warning": "Varning om permanent radering", "permanent_deletion_warning_setting_description": "Visa en varning när tillgångar raderas permanent", diff --git a/i18n/ta.json b/i18n/ta.json index f3ed200b24c42..c3d13dbdf0bc0 100644 --- a/i18n/ta.json +++ b/i18n/ta.json @@ -7,7 +7,7 @@ "actions": "செயல்கள்", "active": "செயல்பாட்டில்", "activity": "செயல்பாடுகள்", - "activity_changed": "செயல்பாடு {இயக்கப்பட்டது, தேர்ந்தெடு, சரி {இயக்கப்பட்டது} மற்றது {முடக்கப்பட்டது}}", + "activity_changed": "செயல்பாடு {இயக்கப்பட்டது, தேர்ந்தெடு, சரி {enabled} மற்றது {disabled}}", "add": "சேர்", "add_a_description": "விவரம் சேர்", "add_a_location": "இடத்தை சேர்க்கவும்", diff --git a/i18n/uk.json b/i18n/uk.json index dbc14f2e2cb85..773b9b7c73733 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -438,7 +438,7 @@ "blurred_background": "Розмитий фон", "bugs_and_feature_requests": "Помилки та Запити", "build": "Збірка", - "build_image": "Створити зображення", + "build_image": "Версія збірки", "bulk_delete_duplicates_confirmation": "Ви впевнені, що хочете масово видалити {count, plural, one {# дубльований ресурс} few {# дубльовані ресурси} other {# дубльованих ресурсів}}? Це дія залишить найбільший ресурс у кожній групі і остаточно видалить всі інші дублікати. Цю дію неможливо скасувати!", "bulk_keep_duplicates_confirmation": "Ви впевнені, що хочете залишити {count, plural, one {# дубльований ресурс} few {# дубльовані ресурси} other {# дубльованих ресурсів}}? Це дозволить вирішити всі групи дублікатів без видалення чого-небудь.", "bulk_trash_duplicates_confirmation": "Ви впевнені, що хочете викинути в кошик {count, plural, one {# дубльований ресурс} few {# дубльовані ресурси} other {# дубльованих ресурсів}} масово? Це залишить найбільший ресурс у кожній групі і викине в кошик всі інші дублікати.", @@ -523,6 +523,10 @@ "date_range": "Проміжок часу", "day": "День", "deduplicate_all": "Видалити всі дублікати", + "deduplication_criteria_1": "Розмір зображення в байтах", + "deduplication_criteria_2": "Кількість даних EXIF", + "deduplication_info": "Інформація про дедуплікацію", + "deduplication_info_description": "Для автоматичного попереднього вибору файлів і масового видалення дублікатів ми враховуємо:", "default_locale": "Дата і час за замовчуванням", "default_locale_description": "Форматувати дати та числа з урахуванням мови вашого браузера", "delete": "Видалити", diff --git a/i18n/zh_SIMPLIFIED.json b/i18n/zh_SIMPLIFIED.json index 12599f385ad63..12c72a8172834 100644 --- a/i18n/zh_SIMPLIFIED.json +++ b/i18n/zh_SIMPLIFIED.json @@ -523,6 +523,10 @@ "date_range": "日期范围", "day": "日", "deduplicate_all": "删除所有重复项", + "deduplication_criteria_1": "图像大小(字节)", + "deduplication_criteria_2": "EXIF 数据计数", + "deduplication_info": "重复数据删除汇总", + "deduplication_info_description": "要自动预选项目并批量删除重复项,我们会考虑:", "default_locale": "默认地区", "default_locale_description": "根据您的浏览器地区设置日期和数字显示格式", "delete": "删除", From fc99c5f53096f2c19f8fe0921c59a909c88a90cb Mon Sep 17 00:00:00 2001 From: Yonathan Randolph Date: Mon, 13 Jan 2025 19:00:55 -0800 Subject: [PATCH 09/18] chore(server): avoid copying sources in dev (#12794) * chore(server): avoid copying sources in dev Add a dev target to the web and server Dockerfiles, and change docker-compose.dev.yml to use the dev target. The dev target avoids copying files so that the docker image is smaller. * chore: respond to PR: don't add dev target web/Dockerfile is only used by docker-compose.dev.yml so a dev target is redundant. Instead, just remove the copy --------- Co-authored-by: Jason Rasmussen --- server/Dockerfile | 2 +- web/Dockerfile | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/server/Dockerfile b/server/Dockerfile index e4fb6d43521eb..85c3ffae1f242 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -9,7 +9,6 @@ RUN npm ci && \ # they're marked as optional dependencies, so we need to copy them manually after pruning rm -rf node_modules/@img/sharp-libvips* && \ rm -rf node_modules/@img/sharp-linuxmusl-x64 -COPY server . ENV PATH="${PATH}:/usr/src/app/bin" \ IMMICH_ENV=development \ NVIDIA_DRIVER_CAPABILITIES=all \ @@ -19,6 +18,7 @@ ENTRYPOINT ["tini", "--", "/bin/sh"] FROM dev AS prod +COPY server . RUN npm run build RUN npm prune --omit=dev --omit=optional COPY --from=dev /usr/src/app/node_modules/@img ./node_modules/@img diff --git a/web/Dockerfile b/web/Dockerfile index bf6aa5af5c2b7..dfef1d83481d3 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -5,7 +5,6 @@ USER node WORKDIR /usr/src/app COPY --chown=node:node package*.json ./ RUN npm ci -COPY --chown=node:node . . ENV CHOKIDAR_USEPOLLING=true EXPOSE 24678 EXPOSE 3000 From a35af2b24248a242ef8732379ba6fce1410794cb Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 13 Jan 2025 22:22:03 -0600 Subject: [PATCH 10/18] refactor: migrate move repository to kysely (#15327) * refactor: migrate move repository to kysely * fix: tests * fix: tests --- server/src/cores/storage.core.ts | 4 +- server/src/interfaces/move.interface.ts | 10 +++-- server/src/queries/move.repository.sql | 29 ++++++------ server/src/repositories/move.repository.ts | 44 ++++++++++++++----- .../services/storage-template.service.spec.ts | 4 +- 5 files changed, 56 insertions(+), 35 deletions(-) diff --git a/server/src/cores/storage.core.ts b/server/src/cores/storage.core.ts index c49175172d66e..d26829d633550 100644 --- a/server/src/cores/storage.core.ts +++ b/server/src/cores/storage.core.ts @@ -183,7 +183,7 @@ export class StorageCore { return; } - move = await this.moveRepository.update({ id: move.id, oldPath: actualPath, newPath }); + move = await this.moveRepository.update(move.id, { id: move.id, oldPath: actualPath, newPath }); } else { move = await this.moveRepository.create({ entityId, pathType, oldPath, newPath }); } @@ -225,7 +225,7 @@ export class StorageCore { } await this.savePath(pathType, entityId, newPath); - await this.moveRepository.delete(move); + await this.moveRepository.delete(move.id); } private async verifyNewPathContentsMatchesExpected( diff --git a/server/src/interfaces/move.interface.ts b/server/src/interfaces/move.interface.ts index 0e79cfcadc5a8..4356d9df8cc8d 100644 --- a/server/src/interfaces/move.interface.ts +++ b/server/src/interfaces/move.interface.ts @@ -1,3 +1,5 @@ +import { Insertable, Updateable } from 'kysely'; +import { MoveHistory } from 'src/db'; import { MoveEntity } from 'src/entities/move.entity'; import { PathType } from 'src/enum'; @@ -6,8 +8,8 @@ export const IMoveRepository = 'IMoveRepository'; export type MoveCreate = Pick & Partial; export interface IMoveRepository { - create(entity: MoveCreate): Promise; - getByEntity(entityId: string, pathType: PathType): Promise; - update(entity: Partial): Promise; - delete(move: MoveEntity): Promise; + create(entity: Insertable): Promise; + getByEntity(entityId: string, pathType: PathType): Promise; + update(id: string, entity: Updateable): Promise; + delete(id: string): Promise; } diff --git a/server/src/queries/move.repository.sql b/server/src/queries/move.repository.sql index 3ce8c0ccddeb4..e51f2829df49e 100644 --- a/server/src/queries/move.repository.sql +++ b/server/src/queries/move.repository.sql @@ -1,18 +1,17 @@ -- NOTE: This file is auto generated by ./sql-generator -- MoveRepository.getByEntity -SELECT - "MoveEntity"."id" AS "MoveEntity_id", - "MoveEntity"."entityId" AS "MoveEntity_entityId", - "MoveEntity"."pathType" AS "MoveEntity_pathType", - "MoveEntity"."oldPath" AS "MoveEntity_oldPath", - "MoveEntity"."newPath" AS "MoveEntity_newPath" -FROM - "move_history" "MoveEntity" -WHERE - ( - ("MoveEntity"."entityId" = $1) - AND ("MoveEntity"."pathType" = $2) - ) -LIMIT - 1 +select + * +from + "move_history" +where + "entityId" = $1 + and "pathType" = $2 + +-- MoveRepository.delete +delete from "move_history" +where + "id" = $1 +returning + * diff --git a/server/src/repositories/move.repository.ts b/server/src/repositories/move.repository.ts index 16d90040145b8..c0177f3f30f1a 100644 --- a/server/src/repositories/move.repository.ts +++ b/server/src/repositories/move.repository.ts @@ -1,29 +1,49 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { Insertable, Kysely, Updateable } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB, MoveHistory } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; import { MoveEntity } from 'src/entities/move.entity'; import { PathType } from 'src/enum'; -import { IMoveRepository, MoveCreate } from 'src/interfaces/move.interface'; -import { Repository } from 'typeorm'; +import { IMoveRepository } from 'src/interfaces/move.interface'; @Injectable() export class MoveRepository implements IMoveRepository { - constructor(@InjectRepository(MoveEntity) private repository: Repository) {} + constructor(@InjectKysely() private db: Kysely) {} - create(entity: MoveCreate): Promise { - return this.repository.save(entity); + create(entity: Insertable): Promise { + return this.db + .insertInto('move_history') + .values(entity) + .returningAll() + .executeTakeFirstOrThrow() as Promise; } @GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] }) - getByEntity(entityId: string, pathType: PathType): Promise { - return this.repository.findOne({ where: { entityId, pathType } }); + getByEntity(entityId: string, pathType: PathType): Promise { + return this.db + .selectFrom('move_history') + .selectAll() + .where('entityId', '=', entityId) + .where('pathType', '=', pathType) + .executeTakeFirst() as Promise; } - update(entity: Partial): Promise { - return this.repository.save(entity); + update(id: string, entity: Updateable): Promise { + return this.db + .updateTable('move_history') + .set(entity) + .where('id', '=', id) + .returningAll() + .executeTakeFirstOrThrow() as unknown as Promise; } - delete(move: MoveEntity): Promise { - return this.repository.remove(move); + @GenerateSql({ params: [DummyValue.UUID] }) + delete(id: string): Promise { + return this.db + .deleteFrom('move_history') + .where('id', '=', id) + .returningAll() + .executeTakeFirstOrThrow() as unknown as Promise; } } diff --git a/server/src/services/storage-template.service.spec.ts b/server/src/services/storage-template.service.spec.ts index 728e891d05379..46ec4f53e1531 100644 --- a/server/src/services/storage-template.service.spec.ts +++ b/server/src/services/storage-template.service.spec.ts @@ -235,7 +235,7 @@ describe(StorageTemplateService.name, () => { expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id], { exifInfo: true }); expect(storageMock.checkFileExists).toHaveBeenCalledTimes(3); expect(storageMock.rename).toHaveBeenCalledWith(assetStub.image.originalPath, newPath); - expect(moveMock.update).toHaveBeenCalledWith({ + expect(moveMock.update).toHaveBeenCalledWith('123', { id: '123', oldPath: assetStub.image.originalPath, newPath, @@ -277,7 +277,7 @@ describe(StorageTemplateService.name, () => { expect(storageMock.stat).toHaveBeenCalledWith(previousFailedNewPath); expect(storageMock.rename).toHaveBeenCalledWith(previousFailedNewPath, newPath); expect(storageMock.copyFile).not.toHaveBeenCalled(); - expect(moveMock.update).toHaveBeenCalledWith({ + expect(moveMock.update).toHaveBeenCalledWith('123', { id: '123', oldPath: previousFailedNewPath, newPath, From 9e1651ef6662e5fd0cc2ceea0b62bacb45f9724c Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Mon, 13 Jan 2025 23:40:19 -0500 Subject: [PATCH 11/18] fix: bump web dependencies (#15325) --- web/package-lock.json | 972 ++++++++++++++++++++---------------------- web/package.json | 58 +-- 2 files changed, 481 insertions(+), 549 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index 9450b76834318..426df0acd78d6 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -9,17 +9,17 @@ "version": "1.124.2", "license": "GNU Affero General Public License version 3", "dependencies": { - "@formatjs/icu-messageformat-parser": "^2.7.8", + "@formatjs/icu-messageformat-parser": "^2.9.8", "@immich/sdk": "file:../open-api/typescript-sdk", "@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mdi/js": "^7.4.47", - "@photo-sphere-viewer/core": "^5.7.1", - "@photo-sphere-viewer/equirectangular-video-adapter": "^5.7.2", - "@photo-sphere-viewer/video-plugin": "^5.7.2", + "@photo-sphere-viewer/core": "^5.11.5", + "@photo-sphere-viewer/equirectangular-video-adapter": "^5.11.5", + "@photo-sphere-viewer/video-plugin": "^5.11.5", "@zoom-image/svelte": "^0.3.0", "dom-to-image": "^2.6.0", "handlebars": "^4.7.8", - "intl-messageformat": "^10.5.14", + "intl-messageformat": "^10.7.11", "justified-layout": "^4.1.0", "lodash-es": "^4.17.21", "luxon": "^3.4.4", @@ -32,43 +32,43 @@ }, "devDependencies": { "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "^9.8.0", - "@faker-js/faker": "^9.0.0", + "@eslint/js": "^9.18.0", + "@faker-js/faker": "^9.3.0", "@socket.io/component-emitter": "^3.1.0", - "@sveltejs/adapter-static": "^3.0.5", - "@sveltejs/enhanced-img": "^0.4.0", - "@sveltejs/kit": "^2.12.0", - "@sveltejs/vite-plugin-svelte": "^4.0.0", + "@sveltejs/adapter-static": "^3.0.8", + "@sveltejs/enhanced-img": "^0.4.4", + "@sveltejs/kit": "^2.15.2", + "@sveltejs/vite-plugin-svelte": "^4.0.4", "@testing-library/jest-dom": "^6.4.2", - "@testing-library/svelte": "^5.2.4", + "@testing-library/svelte": "^5.2.6", "@testing-library/user-event": "^14.5.2", "@types/dom-to-image": "^2.6.7", "@types/justified-layout": "^4.1.4", "@types/lodash-es": "^4.17.12", "@types/luxon": "^3.4.2", - "@typescript-eslint/eslint-plugin": "^8.15.0", - "@typescript-eslint/parser": "^8.15.0", - "@vitest/coverage-v8": "^2.0.5", + "@typescript-eslint/eslint-plugin": "^8.20.0", + "@typescript-eslint/parser": "^8.20.0", + "@vitest/coverage-v8": "^2.1.8", "autoprefixer": "^10.4.17", - "dotenv": "^16.4.5", - "eslint": "^9.14.0", + "dotenv": "^16.4.7", + "eslint": "^9.18.0", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-svelte": "^2.45.1", + "eslint-plugin-svelte": "^2.46.1", "eslint-plugin-unicorn": "^56.0.1", "factory.ts": "^1.4.1", - "globals": "^15.9.0", - "postcss": "^8.4.35", - "prettier": "^3.2.5", + "globals": "^15.14.0", + "postcss": "^8.5.0", + "prettier": "^3.4.2", "prettier-plugin-organize-imports": "^4.0.0", - "prettier-plugin-sort-json": "^4.0.0", - "prettier-plugin-svelte": "^3.2.6", - "rollup-plugin-visualizer": "^5.12.0", - "svelte": "^5.1.5", - "svelte-check": "^4.0.9", - "tailwindcss": "^3.4.1", + "prettier-plugin-sort-json": "^4.1.1", + "prettier-plugin-svelte": "^3.3.3", + "rollup-plugin-visualizer": "^5.14.0", + "svelte": "^5.17.4", + "svelte-check": "^4.1.4", + "tailwindcss": "^3.4.17", "tslib": "^2.6.2", - "typescript": "^5.5.0", - "vite": "^5.4.4", + "typescript": "^5.7.3", + "vite": "^5.4.11", "vitest": "^2.0.5" } }, @@ -220,10 +220,11 @@ "dev": true }, "node_modules/@emnapi/runtime": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.1.1.tgz", - "integrity": "sha512-3bfqkzuR1KLx57nZfjr2NLnFOobvyS0aTszaEGCGqmYMVDRaGvgIZbjGSV/MHSSmLgQ/b9JFHQ5xm5WRZYd+XQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" @@ -646,13 +647,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", - "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.4", + "@eslint/object-schema": "^2.1.5", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -661,11 +662,14 @@ } }, "node_modules/@eslint/core": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", - "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -739,9 +743,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", - "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", + "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", "dev": true, "license": "MIT", "engines": { @@ -749,9 +753,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -759,12 +763,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", - "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@eslint/core": "^0.10.0", "levn": "^0.4.1" }, "engines": { @@ -772,9 +777,9 @@ } }, "node_modules/@faker-js/faker": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.2.0.tgz", - "integrity": "sha512-ulqQu4KMr1/sTFIYvqSdegHT8NIkt66tFAkugGnHA+1WAfEn6hMzNR+svjXGFRVLnapxvej67Z/LwchFrnLBUg==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.3.0.tgz", + "integrity": "sha512-r0tJ3ZOkMd9xsu3VRfqlFR6cz0V/jFYRswAIpC+m/DIfAUXq7g8N7wTAlhSANySXYGKzGryfDXwtwsY8TxEIDw==", "dev": true, "funding": [ { @@ -789,50 +794,51 @@ } }, "node_modules/@formatjs/ecma402-abstract": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.3.tgz", - "integrity": "sha512-aElGmleuReGnk2wtYOzYFmNWYoiWWmf1pPPCYg0oiIQSJj0mjc4eUfzUXaSOJ4S8WzI/cLqnCTWjqz904FT2OQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.2.tgz", + "integrity": "sha512-6sE5nyvDloULiyOMbOTJEEgWL32w+VHkZQs8S02Lnn8Y/O5aQhjOEXwWzvR7SsBE/exxlSpY2EsWZgqHbtLatg==", "license": "MIT", "dependencies": { - "@formatjs/fast-memoize": "2.2.3", - "@formatjs/intl-localematcher": "0.5.7", + "@formatjs/fast-memoize": "2.2.6", + "@formatjs/intl-localematcher": "0.5.10", + "decimal.js": "10", "tslib": "2" } }, "node_modules/@formatjs/fast-memoize": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.3.tgz", - "integrity": "sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.6.tgz", + "integrity": "sha512-luIXeE2LJbQnnzotY1f2U2m7xuQNj2DA8Vq4ce1BY9ebRZaoPB1+8eZ6nXpLzsxuW5spQxr7LdCg+CApZwkqkw==", "license": "MIT", "dependencies": { "tslib": "2" } }, "node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.9.3.tgz", - "integrity": "sha512-9L99QsH14XjOCIp4TmbT8wxuffJxGK8uLNO1zNhLtcZaVXvv626N0s4A2qgRCKG3dfYWx9psvGlFmvyVBa6u/w==", + "version": "2.9.8", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.9.8.tgz", + "integrity": "sha512-hZlLNI3+Lev8IAXuwehLoN7QTKqbx3XXwFW1jh0AdIA9XJdzn9Uzr+2LLBspPm/PX0+NLIfykj/8IKxQqHUcUQ==", "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "2.2.3", - "@formatjs/icu-skeleton-parser": "1.8.7", + "@formatjs/ecma402-abstract": "2.3.2", + "@formatjs/icu-skeleton-parser": "1.8.12", "tslib": "2" } }, "node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.8.7", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.7.tgz", - "integrity": "sha512-fI+6SmS2g7h3srfAKSWa5dwreU5zNEfon2uFo99OToiLF6yxGE+WikvFSbsvMAYkscucvVmTYNlWlaDPp0n5HA==", + "version": "1.8.12", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.12.tgz", + "integrity": "sha512-QRAY2jC1BomFQHYDMcZtClqHR55EEnB96V7Xbk/UiBodsuFc5kujybzt87+qj1KqmJozFhk6n4KiT1HKwAkcfg==", "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "2.2.3", + "@formatjs/ecma402-abstract": "2.3.2", "tslib": "2" } }, "node_modules/@formatjs/intl-localematcher": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.7.tgz", - "integrity": "sha512-GGFtfHGQVFe/niOZp24Kal5b2i36eE2bNL0xi9Sg/yd0TR8aLjcteApZdHmismP5QQax1cMnZM9yWySUUjJteA==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz", + "integrity": "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==", "license": "MIT", "dependencies": { "tslib": "2" @@ -890,450 +896,380 @@ } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.3.tgz", - "integrity": "sha512-FaNiGX1MrOuJ3hxuNzWgsT/mg5OHG/Izh59WW2mk1UwYHUwtfbhk5QNKYZgxf0pLOhx9ctGiGa2OykD71vOnSw==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "glibc": ">=2.26", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.2" + "@img/sharp-libvips-darwin-arm64": "1.0.4" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.3.tgz", - "integrity": "sha512-2QeSl7QDK9ru//YBT4sQkoq7L0EAJZA3rtV+v9p8xTKl4U1bUqTIaCnoC7Ctx2kCjQgwFXDasOtPTCT8eCTXvw==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "glibc": ">=2.26", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.2" + "@img/sharp-libvips-darwin-x64": "1.0.4" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz", - "integrity": "sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", "cpu": [ "arm64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" ], - "engines": { - "macos": ">=11", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz", - "integrity": "sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", "cpu": [ "x64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" ], - "engines": { - "macos": ">=10.13", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz", - "integrity": "sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", "cpu": [ "arm" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "glibc": ">=2.28", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz", - "integrity": "sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", "cpu": [ "arm64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "glibc": ">=2.26", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz", - "integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", "cpu": [ "s390x" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "glibc": ">=2.28", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz", - "integrity": "sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", "cpu": [ "x64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "glibc": ">=2.26", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz", - "integrity": "sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", "cpu": [ "arm64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "musl": ">=1.2.2", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz", - "integrity": "sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", "cpu": [ "x64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "musl": ">=1.2.2", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.3.tgz", - "integrity": "sha512-Q7Ee3fFSC9P7vUSqVEF0zccJsZ8GiiCJYGWDdhEjdlOeS9/jdkyJ6sUSPj+bL8VuOYFSbofrW0t/86ceVhx32w==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", "cpu": [ "arm" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "glibc": ">=2.28", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.2" + "@img/sharp-libvips-linux-arm": "1.0.5" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.3.tgz", - "integrity": "sha512-Zf+sF1jHZJKA6Gor9hoYG2ljr4wo9cY4twaxgFDvlG0Xz9V7sinsPp8pFd1XtlhTzYo0IhDbl3rK7P6MzHpnYA==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "glibc": ">=2.26", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.2" + "@img/sharp-libvips-linux-arm64": "1.0.4" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.3.tgz", - "integrity": "sha512-vFk441DKRFepjhTEH20oBlFrHcLjPfI8B0pMIxGm3+yilKyYeHEVvrZhYFdqIseSclIqbQ3SnZMwEMWonY5XFA==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", "cpu": [ "s390x" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "glibc": ">=2.28", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.2" + "@img/sharp-libvips-linux-s390x": "1.0.4" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.3.tgz", - "integrity": "sha512-Q4I++herIJxJi+qmbySd072oDPRkCg/SClLEIDh5IL9h1zjhqjv82H0Seupd+q2m0yOfD+/fJnjSoDFtKiHu2g==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "glibc": ">=2.26", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.2" + "@img/sharp-libvips-linux-x64": "1.0.4" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.3.tgz", - "integrity": "sha512-qnDccehRDXadhM9PM5hLvcPRYqyFCBN31kq+ErBSZtZlsAc1U4Z85xf/RXv1qolkdu+ibw64fUDaRdktxTNP9A==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "musl": ">=1.2.2", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.2" + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.3.tgz", - "integrity": "sha512-Jhchim8kHWIU/GZ+9poHMWRcefeaxFIs9EBqf9KtcC14Ojk6qua7ghKiPs0sbeLbLj/2IGBtDcxHyjCdYWkk2w==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "musl": ">=1.2.2", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.2" + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.3.tgz", - "integrity": "sha512-68zivsdJ0koE96stdUfM+gmyaK/NcoSZK5dV5CAjES0FUXS9lchYt8LAB5rTbM7nlWtxaU/2GON0HVN6/ZYJAQ==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", "cpu": [ "wasm32" ], "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.1.0" + "@emnapi/runtime": "^1.2.0" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.3.tgz", - "integrity": "sha512-CyimAduT2whQD8ER4Ux7exKrtfoaUiVr7HG0zZvO0XTFn2idUWljjxv58GxNTkFb8/J9Ub9AqITGkJD6ZginxQ==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", "cpu": [ "ia32" ], "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.3.tgz", - "integrity": "sha512-viT4fUIDKnli3IfOephGnolMzhz5VaTvDRkYqtZxOMIoMQ4MrAziO7pT1nVnOt2FAm7qW5aa+CCc13aEY6Le0g==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" @@ -1638,30 +1574,31 @@ } }, "node_modules/@photo-sphere-viewer/core": { - "version": "5.11.1", - "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/core/-/core-5.11.1.tgz", - "integrity": "sha512-bxWnoQGYjXfmHGee4OSkoYLZmdgqvJWMn7wmpK0V0Vf46Fqu+TJ4Yt8+dY2PgpM89HoKzNr15Dzt6jqOfjkFxQ==", + "version": "5.11.5", + "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/core/-/core-5.11.5.tgz", + "integrity": "sha512-aCo+zsWR0m0qSlNQpkacnQoSfc0An0zujBpQJ5l9LSvZixeC85FTWm9OZs1yaXvE5bM+TsdqfPDojjy9xT8qzQ==", "license": "MIT", "dependencies": { "three": "^0.169.0" } }, "node_modules/@photo-sphere-viewer/equirectangular-video-adapter": { - "version": "5.11.1", - "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/equirectangular-video-adapter/-/equirectangular-video-adapter-5.11.1.tgz", - "integrity": "sha512-fkWuVeArtZSWd0z282/J82YSc+oernQaE/cpo0soVaStaNbS1V35iSnPlaBKw40qX6tucJWYw15QwM8xgPC2IQ==", + "version": "5.11.5", + "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/equirectangular-video-adapter/-/equirectangular-video-adapter-5.11.5.tgz", + "integrity": "sha512-OmB5lYtJCHAbOI06X5KABILsjfLhmWp17uEvS9FQrHWX5cYjsSn+T2flBfgxqrVi0gXFSYmh80yoF3tgWjoc9Q==", "license": "MIT", "peerDependencies": { - "@photo-sphere-viewer/core": "5.11.1" + "@photo-sphere-viewer/core": "5.11.5", + "@photo-sphere-viewer/video-plugin": "5.11.5" } }, "node_modules/@photo-sphere-viewer/video-plugin": { - "version": "5.11.1", - "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/video-plugin/-/video-plugin-5.11.1.tgz", - "integrity": "sha512-02spWwv9bjyI6inNdZsczX/qdMICVV9B8lWX/J4iNBaiUCHqPKmk8CeZbRyC/Uh3OHSusSJHyW0FDEOf6qjjww==", + "version": "5.11.5", + "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/video-plugin/-/video-plugin-5.11.5.tgz", + "integrity": "sha512-Jlbx01y3HGwCVlaXPzeMl/LQ2Vqy9LLO9qxmBGoX/aHAse9QsMatl1N1+EUcDZcC4rZcCsNja9OxRN2r/hfnDA==", "license": "MIT", "peerDependencies": { - "@photo-sphere-viewer/core": "5.11.1" + "@photo-sphere-viewer/core": "5.11.5" } }, "node_modules/@pkgjs/parseargs": { @@ -1980,9 +1917,9 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" }, "node_modules/@sveltejs/adapter-static": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.6.tgz", - "integrity": "sha512-MGJcesnJWj7FxDcB/GbrdYD3q24Uk0PIL4QIX149ku+hlJuj//nxUbb0HxUTpjkecWfHjVveSUnUaQWnPRXlpg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz", + "integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1990,13 +1927,14 @@ } }, "node_modules/@sveltejs/enhanced-img": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sveltejs/enhanced-img/-/enhanced-img-0.4.1.tgz", - "integrity": "sha512-Z0xwQWM7tfdlNYuaFsAsbjEosEZb961yP7hlvZBLlh3+Rv4tI3BboD6bUkmInj+cC66p/5rybgvEtxX5LILSuw==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@sveltejs/enhanced-img/-/enhanced-img-0.4.4.tgz", + "integrity": "sha512-BlBTGfbLUgHa+zSVrsGLOd+noCKWfipoOjoxE26bAAX97v7zh5eiCAp1KEdpkluL05Tl3+nR14gQdPsATyZqoA==", "dev": true, "license": "MIT", "dependencies": { "magic-string": "^0.30.5", + "sharp": "^0.33.5", "svelte-parse-markup": "^0.1.5", "vite-imagetools": "^7.0.1", "zimmerframe": "^1.1.2" @@ -2007,9 +1945,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.13.0.tgz", - "integrity": "sha512-6t6ne00vZx/TjD6s0Jvwt8wRLKBwbSAN1nhlOzcLUSTYX1hTp4eCBaTPB5Yz/lu+tYcvz4YPEEuPv3yfsNp2gw==", + "version": "2.15.2", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.15.2.tgz", + "integrity": "sha512-p208T1kdM6zd8k4YXIUM60pLWQ8dZqehXSiqn4NulXHyHibX53uIAL2xtNL8GjxX2IVPqPRT978MwVYhCKExdQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2040,9 +1978,9 @@ } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-4.0.1.tgz", - "integrity": "sha512-prXoAE/GleD2C4pKgHa9vkdjpzdYwCSw/kmjw6adIyu0vk5YKCfqIztkLg10m+kOYnzZu3bb0NaPTxlWre2a9Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-4.0.4.tgz", + "integrity": "sha512-0ba1RQ/PHen5FGpdSrW7Y3fAMQjrXantECALeOiOdBdzR5+5vPP6HVZRLmZaQL+W8m++o+haIAKq5qT+MiZ7VA==", "dev": true, "license": "MIT", "dependencies": { @@ -2271,10 +2209,11 @@ } }, "node_modules/@testing-library/svelte": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-5.2.4.tgz", - "integrity": "sha512-EFdy73+lULQgMJ1WolAymrxWWrPv9DWyDuDFKKlUip2PA/EXuHptzfYOKWljccFWDKhhGOu3dqNmoc2f/h/Ecg==", + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-5.2.6.tgz", + "integrity": "sha512-1Y8cEg/BtV4J6g9irkY0ksz+ueDFYLiikjTLiqvQPkOUeDzR4gg2zECBf8yrOrCy3e2TAOYMcaysFa0bQMyk1w==", "dev": true, + "license": "MIT", "dependencies": { "@testing-library/dom": "^10.0.0" }, @@ -2430,21 +2369,21 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz", - "integrity": "sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz", + "integrity": "sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/type-utils": "8.15.0", - "@typescript-eslint/utils": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/type-utils": "8.20.0", + "@typescript-eslint/utils": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2455,25 +2394,21 @@ }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.15.0.tgz", - "integrity": "sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz", + "integrity": "sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/typescript-estree": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/typescript-estree": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "debug": "^4.3.4" }, "engines": { @@ -2484,23 +2419,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.15.0.tgz", - "integrity": "sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.20.0.tgz", + "integrity": "sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0" + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2511,16 +2442,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.15.0.tgz", - "integrity": "sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.20.0.tgz", + "integrity": "sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.15.0", - "@typescript-eslint/utils": "8.15.0", + "@typescript-eslint/typescript-estree": "8.20.0", + "@typescript-eslint/utils": "8.20.0", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2530,18 +2461,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.15.0.tgz", - "integrity": "sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.20.0.tgz", + "integrity": "sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==", "dev": true, "license": "MIT", "engines": { @@ -2553,20 +2480,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.15.0.tgz", - "integrity": "sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.20.0.tgz", + "integrity": "sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2575,10 +2502,8 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { @@ -2608,16 +2533,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.15.0.tgz", - "integrity": "sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.20.0.tgz", + "integrity": "sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/typescript-estree": "8.15.0" + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/typescript-estree": "8.20.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2627,22 +2552,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.15.0.tgz", - "integrity": "sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.20.0.tgz", + "integrity": "sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/types": "8.20.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2667,9 +2588,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.5.tgz", - "integrity": "sha512-/RoopB7XGW7UEkUndRXF87A9CwkoZAJW01pj8/3pgmDVsjMH2IKy6H1A38po9tmUlwhSyYs0az82rbKd9Yaynw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.8.tgz", + "integrity": "sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==", "dev": true, "license": "MIT", "dependencies": { @@ -2690,8 +2611,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.5", - "vitest": "2.1.5" + "@vitest/browser": "2.1.8", + "vitest": "2.1.8" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2700,14 +2621,14 @@ } }, "node_modules/@vitest/expect": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.5.tgz", - "integrity": "sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz", + "integrity": "sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.5", - "@vitest/utils": "2.1.5", + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, @@ -2716,13 +2637,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.5.tgz", - "integrity": "sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.8.tgz", + "integrity": "sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.5", + "@vitest/spy": "2.1.8", "estree-walker": "^3.0.3", "magic-string": "^0.30.12" }, @@ -2743,9 +2664,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.5.tgz", - "integrity": "sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", + "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2756,13 +2677,13 @@ } }, "node_modules/@vitest/runner": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.5.tgz", - "integrity": "sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.8.tgz", + "integrity": "sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.5", + "@vitest/utils": "2.1.8", "pathe": "^1.1.2" }, "funding": { @@ -2770,13 +2691,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.5.tgz", - "integrity": "sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.8.tgz", + "integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.5", + "@vitest/pretty-format": "2.1.8", "magic-string": "^0.30.12", "pathe": "^1.1.2" }, @@ -2785,9 +2706,9 @@ } }, "node_modules/@vitest/spy": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.5.tgz", - "integrity": "sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.8.tgz", + "integrity": "sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==", "dev": true, "license": "MIT", "dependencies": { @@ -2798,13 +2719,13 @@ } }, "node_modules/@vitest/utils": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.5.tgz", - "integrity": "sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz", + "integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.5", + "@vitest/pretty-format": "2.1.8", "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, @@ -3346,6 +3267,15 @@ "node": ">=6" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -3577,10 +3507,7 @@ "node_modules/decimal.js": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" }, "node_modules/deep-eql": { "version": "5.0.2", @@ -3674,10 +3601,11 @@ "integrity": "sha512-Dt0QdaHmLpjURjU7Tnu3AgYSF2LuOmksSGsUcE6ItvJoCWTBEmiMXcqBdNSAm9+QbbwD7JMoVsuuKX6ZVQv1qA==" }, "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, @@ -3755,9 +3683,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", "dev": true, "license": "MIT" }, @@ -3868,27 +3796,27 @@ } }, "node_modules/eslint": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.14.0.tgz", - "integrity": "sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", + "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.7.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.14.0", - "@eslint/plugin-kit": "^0.2.0", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.10.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.18.0", + "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.0", + "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", @@ -3907,8 +3835,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" @@ -3949,6 +3876,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, + "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -3957,9 +3885,9 @@ } }, "node_modules/eslint-plugin-svelte": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.46.0.tgz", - "integrity": "sha512-1A7iEMkzmCZ9/Iz+EAfOGYL8IoIG6zeKEq1SmpxGeM5SXmoQq+ZNnCpXFVJpsxPWYx8jIVGMerQMzX20cqUl0g==", + "version": "2.46.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.46.1.tgz", + "integrity": "sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==", "dev": true, "license": "MIT", "dependencies": { @@ -4066,16 +3994,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/@eslint/js": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz", - "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/eslint/node_modules/@humanwhocodes/retry": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", @@ -4278,13 +4196,12 @@ } }, "node_modules/esrap": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz", - "integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.2.tgz", + "integrity": "sha512-FhVlJzvTw7ZLxYZ7RyHwQCFE64dkkpzGNNnphaGCLwjqGk1SQcqzbgdx9FowPCktx6NOSHkzvcZ3vsvdH54YXA==", "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15", - "@types/estree": "^1.0.1" + "@jridgewell/sourcemap-codec": "^1.4.15" } }, "node_modules/esrecurse": { @@ -4313,6 +4230,7 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" } @@ -4689,9 +4607,9 @@ } }, "node_modules/globals": { - "version": "15.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", - "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", "dev": true, "license": "MIT", "engines": { @@ -4943,14 +4861,14 @@ } }, "node_modules/intl-messageformat": { - "version": "10.7.6", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.6.tgz", - "integrity": "sha512-IsMU/hqyy3FJwNJ0hxDfY2heJ7MteSuFvcnCebxRp67di4Fhx1gKKE+qS0bBwUF8yXkX9SsPUhLeX/B6h5SKUA==", + "version": "10.7.11", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.11.tgz", + "integrity": "sha512-IB2N1tmI24k2EFH3PWjU7ivJsnWyLwOWOva0jnXFa29WzB6fb0JZ5EMQGu+XN5lDtjHYFo0/UooP67zBwUg7rQ==", "license": "BSD-3-Clause", "dependencies": { - "@formatjs/ecma402-abstract": "2.2.3", - "@formatjs/fast-memoize": "2.2.3", - "@formatjs/icu-messageformat-parser": "2.9.3", + "@formatjs/ecma402-abstract": "2.3.2", + "@formatjs/fast-memoize": "2.2.6", + "@formatjs/icu-messageformat-parser": "2.9.8", "tslib": "2" } }, @@ -6110,9 +6028,9 @@ } }, "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.0.tgz", + "integrity": "sha512-27VKOqrYfPncKA2NrFOVhP5MGAfHKLYn/Q0mz9cNQyRAKYi3VNHwYU2qKKqPCqgBmeeJ0uAFB56NumXZ5ZReXg==", "dev": true, "funding": [ { @@ -6130,7 +6048,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -6307,9 +6225,9 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "license": "MIT", "bin": { @@ -6340,10 +6258,11 @@ } }, "node_modules/prettier-plugin-sort-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-sort-json/-/prettier-plugin-sort-json-4.0.0.tgz", - "integrity": "sha512-zV5g+bWFD2zAqyQ8gCkwUTC49o9FxslaUdirwivt5GZHcf57hCocavykuyYqbExoEsuBOg8IU36OY7zmVEMOWA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-sort-json/-/prettier-plugin-sort-json-4.1.1.tgz", + "integrity": "sha512-uJ49wCzwJ/foKKV4tIPxqi4jFFvwUzw4oACMRG2dcmDhBKrxBv0L2wSKkAqHCmxKCvj0xcCZS4jO2kSJO/tRJw==", "dev": true, + "license": "MIT", "engines": { "node": ">=18.0.0" }, @@ -6352,9 +6271,9 @@ } }, "node_modules/prettier-plugin-svelte": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.2.8.tgz", - "integrity": "sha512-PAHmmU5cGZdnhW4mWhmvxuG2PVbbHIxUuPOdUKvfE+d4Qt2d29iU5VWrPdsaW5YqVEE0nqhlvN4eoKmVMpIF3Q==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.3.3.tgz", + "integrity": "sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -6714,13 +6633,14 @@ } }, "node_modules/rollup-plugin-visualizer": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz", - "integrity": "sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.14.0.tgz", + "integrity": "sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA==", "dev": true, + "license": "MIT", "dependencies": { "open": "^8.4.0", - "picomatch": "^2.3.1", + "picomatch": "^4.0.2", "source-map": "^0.7.4", "yargs": "^17.5.1" }, @@ -6728,17 +6648,34 @@ "rollup-plugin-visualizer": "dist/bin/cli.js" }, "engines": { - "node": ">=14" + "node": ">=18" }, "peerDependencies": { + "rolldown": "1.x", "rollup": "2.x || 3.x || 4.x" }, "peerDependenciesMeta": { + "rolldown": { + "optional": true + }, "rollup": { "optional": true } } }, + "node_modules/rollup-plugin-visualizer/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/rollup-plugin-visualizer/node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -6863,43 +6800,43 @@ } }, "node_modules/sharp": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.3.tgz", - "integrity": "sha512-vHUeXJU1UvlO/BNwTpT0x/r53WkLUVxrmb5JTgW92fdFCFk0ispLMAeu/jPO2vjkXM1fYUi3K7/qcLF47pwM1A==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", "dev": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", - "semver": "^7.6.0" + "semver": "^7.6.3" }, "engines": { - "libvips": ">=8.15.2", "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.3", - "@img/sharp-darwin-x64": "0.33.3", - "@img/sharp-libvips-darwin-arm64": "1.0.2", - "@img/sharp-libvips-darwin-x64": "1.0.2", - "@img/sharp-libvips-linux-arm": "1.0.2", - "@img/sharp-libvips-linux-arm64": "1.0.2", - "@img/sharp-libvips-linux-s390x": "1.0.2", - "@img/sharp-libvips-linux-x64": "1.0.2", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.2", - "@img/sharp-libvips-linuxmusl-x64": "1.0.2", - "@img/sharp-linux-arm": "0.33.3", - "@img/sharp-linux-arm64": "0.33.3", - "@img/sharp-linux-s390x": "0.33.3", - "@img/sharp-linux-x64": "0.33.3", - "@img/sharp-linuxmusl-arm64": "0.33.3", - "@img/sharp-linuxmusl-x64": "0.33.3", - "@img/sharp-wasm32": "0.33.3", - "@img/sharp-win32-ia32": "0.33.3", - "@img/sharp-win32-x64": "0.33.3" + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" } }, "node_modules/shebang-command": { @@ -7271,9 +7208,9 @@ } }, "node_modules/svelte": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.2.1.tgz", - "integrity": "sha512-WzyA7VUVlDTLPt+m71bLD5BXasavqvAo68DelxWaPo8dNEZ3tmeq3DSJPsWqnG37cG2hfn7HaD3x882qF+7UOw==", + "version": "5.17.4", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.17.4.tgz", + "integrity": "sha512-ne4IhhVBwzpUByjo1ocxQnqRoWsRilc9Ry1j+0uPWhHmg4jS/nnlSwYYfx7Ium8okCZ4hYM89rg0B5G0hQzk+g==", "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", @@ -7283,8 +7220,9 @@ "acorn-typescript": "^1.4.13", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", - "esm-env": "^1.0.0", - "esrap": "^1.2.2", + "clsx": "^2.1.1", + "esm-env": "^1.2.1", + "esrap": "^1.4.2", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", @@ -7295,9 +7233,9 @@ } }, "node_modules/svelte-check": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.0.9.tgz", - "integrity": "sha512-SVNCz2L+9ZELGli7G0n3B3QE5kdf0u27RtKr2ZivWQhcWIXatZxwM4VrQ6AiA2k9zKp2mk5AxkEhdjbpjv7rEw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.1.4.tgz", + "integrity": "sha512-v0j7yLbT29MezzaQJPEDwksybTE2Ups9rUxEXy92T06TiA0cbqcO8wAOwNUVkFW6B0hsYHA+oAX3BS8b/2oHtw==", "dev": true, "license": "MIT", "dependencies": { @@ -7407,9 +7345,9 @@ } }, "node_modules/svelte-gestures": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/svelte-gestures/-/svelte-gestures-5.0.6.tgz", - "integrity": "sha512-kElJnoZrQtlkXE0O/RcKioz9NP0Sxx05j31ohyosNkydo6NOEsZB85mhoaCxOQNjxN+QPumYWfmIUsznYFjihA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/svelte-gestures/-/svelte-gestures-5.0.4.tgz", + "integrity": "sha512-a6cnR46AfFZ8zZyvA38A1wBLBFI7rYuAWQnmv3yYgSdbaJK/U7JG34rSkjMCePRvf4BETJSDfMNngLs5zEAfbw==", "license": "MIT" }, "node_modules/svelte-i18n": { @@ -7921,9 +7859,9 @@ "peer": true }, "node_modules/tailwindcss": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.15.tgz", - "integrity": "sha512-r4MeXnfBmSOuKUWmXe6h2CcyfzJCEk4F0pptO5jlnYSIViUkVmsawj80N5h2lO3gwcmSb4n3PuN+e+GC1Guylw==", + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "dev": true, "license": "MIT", "dependencies": { @@ -7936,7 +7874,7 @@ "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.6", - "lilconfig": "^2.1.0", + "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", @@ -7958,6 +7896,19 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss/node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/tailwindcss/node_modules/postcss-load-config": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", @@ -7994,19 +7945,6 @@ } } }, - "node_modules/tailwindcss/node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", - "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, "node_modules/tailwindcss/node_modules/yaml": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", @@ -8058,13 +7996,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -8217,15 +8148,16 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/ts-interface-checker": { @@ -8258,9 +8190,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -8463,9 +8395,9 @@ } }, "node_modules/vite-node": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.5.tgz", - "integrity": "sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.8.tgz", + "integrity": "sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==", "dev": true, "license": "MIT", "dependencies": { @@ -8505,19 +8437,19 @@ } }, "node_modules/vitest": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.5.tgz", - "integrity": "sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.8.tgz", + "integrity": "sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "2.1.5", - "@vitest/mocker": "2.1.5", - "@vitest/pretty-format": "^2.1.5", - "@vitest/runner": "2.1.5", - "@vitest/snapshot": "2.1.5", - "@vitest/spy": "2.1.5", - "@vitest/utils": "2.1.5", + "@vitest/expect": "2.1.8", + "@vitest/mocker": "2.1.8", + "@vitest/pretty-format": "^2.1.8", + "@vitest/runner": "2.1.8", + "@vitest/snapshot": "2.1.8", + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", "chai": "^5.1.2", "debug": "^4.3.7", "expect-type": "^1.1.0", @@ -8529,7 +8461,7 @@ "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.1.5", + "vite-node": "2.1.8", "why-is-node-running": "^2.3.0" }, "bin": { @@ -8544,8 +8476,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.5", - "@vitest/ui": "2.1.5", + "@vitest/browser": "2.1.8", + "@vitest/ui": "2.1.8", "happy-dom": "*", "jsdom": "*" }, diff --git a/web/package.json b/web/package.json index 7ceae0a0ae220..b843f6c13af8d 100644 --- a/web/package.json +++ b/web/package.json @@ -24,58 +24,58 @@ }, "devDependencies": { "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "^9.8.0", - "@faker-js/faker": "^9.0.0", + "@eslint/js": "^9.18.0", + "@faker-js/faker": "^9.3.0", "@socket.io/component-emitter": "^3.1.0", - "@sveltejs/adapter-static": "^3.0.5", - "@sveltejs/enhanced-img": "^0.4.0", - "@sveltejs/kit": "^2.12.0", - "@sveltejs/vite-plugin-svelte": "^4.0.0", + "@sveltejs/adapter-static": "^3.0.8", + "@sveltejs/enhanced-img": "^0.4.4", + "@sveltejs/kit": "^2.15.2", + "@sveltejs/vite-plugin-svelte": "^4.0.4", "@testing-library/jest-dom": "^6.4.2", - "@testing-library/svelte": "^5.2.4", + "@testing-library/svelte": "^5.2.6", "@testing-library/user-event": "^14.5.2", "@types/dom-to-image": "^2.6.7", "@types/justified-layout": "^4.1.4", "@types/lodash-es": "^4.17.12", "@types/luxon": "^3.4.2", - "@typescript-eslint/eslint-plugin": "^8.15.0", - "@typescript-eslint/parser": "^8.15.0", - "@vitest/coverage-v8": "^2.0.5", + "@typescript-eslint/eslint-plugin": "^8.20.0", + "@typescript-eslint/parser": "^8.20.0", + "@vitest/coverage-v8": "^2.1.8", "autoprefixer": "^10.4.17", - "dotenv": "^16.4.5", - "eslint": "^9.14.0", + "dotenv": "^16.4.7", + "eslint": "^9.18.0", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-svelte": "^2.45.1", + "eslint-plugin-svelte": "^2.46.1", "eslint-plugin-unicorn": "^56.0.1", "factory.ts": "^1.4.1", - "globals": "^15.9.0", - "postcss": "^8.4.35", - "prettier": "^3.2.5", + "globals": "^15.14.0", + "postcss": "^8.5.0", + "prettier": "^3.4.2", "prettier-plugin-organize-imports": "^4.0.0", - "prettier-plugin-sort-json": "^4.0.0", - "prettier-plugin-svelte": "^3.2.6", - "rollup-plugin-visualizer": "^5.12.0", - "svelte": "^5.1.5", - "svelte-check": "^4.0.9", - "tailwindcss": "^3.4.1", + "prettier-plugin-sort-json": "^4.1.1", + "prettier-plugin-svelte": "^3.3.3", + "rollup-plugin-visualizer": "^5.14.0", + "svelte": "^5.17.4", + "svelte-check": "^4.1.4", + "tailwindcss": "^3.4.17", "tslib": "^2.6.2", - "typescript": "^5.5.0", - "vite": "^5.4.4", + "typescript": "^5.7.3", + "vite": "^5.4.11", "vitest": "^2.0.5" }, "type": "module", "dependencies": { - "@formatjs/icu-messageformat-parser": "^2.7.8", + "@formatjs/icu-messageformat-parser": "^2.9.8", "@immich/sdk": "file:../open-api/typescript-sdk", "@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mdi/js": "^7.4.47", - "@photo-sphere-viewer/core": "^5.7.1", - "@photo-sphere-viewer/equirectangular-video-adapter": "^5.7.2", - "@photo-sphere-viewer/video-plugin": "^5.7.2", + "@photo-sphere-viewer/core": "^5.11.5", + "@photo-sphere-viewer/equirectangular-video-adapter": "^5.11.5", + "@photo-sphere-viewer/video-plugin": "^5.11.5", "@zoom-image/svelte": "^0.3.0", "dom-to-image": "^2.6.0", "handlebars": "^4.7.8", - "intl-messageformat": "^10.5.14", + "intl-messageformat": "^10.7.11", "justified-layout": "^4.1.0", "lodash-es": "^4.17.21", "luxon": "^3.4.4", From f70ee3f3502899e68a797ab2104b7a56911ed81a Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Tue, 14 Jan 2025 09:14:28 -0500 Subject: [PATCH 12/18] refactor: auth pages (#15328) --- web/eslint.config.mjs | 1 + web/src/app.html | 6 + .../forms/admin-registration-form.svelte | 78 -------- .../forms/change-password-form.svelte | 64 ------- .../lib/components/forms/login-form.svelte | 177 ----------------- .../AuthPageLayout.svelte} | 2 +- web/src/routes/+layout.svelte | 24 +-- .../routes/auth/change-password/+page.svelte | 47 ++++- web/src/routes/auth/login/+page.svelte | 179 +++++++++++++++++- web/src/routes/auth/register/+page.svelte | 76 +++++++- 10 files changed, 293 insertions(+), 361 deletions(-) delete mode 100644 web/src/lib/components/forms/admin-registration-form.svelte delete mode 100644 web/src/lib/components/forms/change-password-form.svelte delete mode 100644 web/src/lib/components/forms/login-form.svelte rename web/src/lib/components/{shared-components/fullscreen-container.svelte => layouts/AuthPageLayout.svelte} (93%) diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index f3cf9d7f10b72..fc5e35ce6db92 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -81,6 +81,7 @@ export default [ 'unicorn/prevent-abbreviations': 'off', 'unicorn/no-nested-ternary': 'off', 'unicorn/consistent-function-scoping': 'off', + 'unicorn/filename-case': 'off', 'unicorn/prefer-top-level-await': 'off', 'unicorn/import-style': 'off', 'svelte/button-has-type': 'error', diff --git a/web/src/app.html b/web/src/app.html index 6fd02dc9f811b..c0ac3cfe6c2e3 100644 --- a/web/src/app.html +++ b/web/src/app.html @@ -101,6 +101,12 @@ + +
diff --git a/web/src/lib/components/forms/admin-registration-form.svelte b/web/src/lib/components/forms/admin-registration-form.svelte deleted file mode 100644 index b4ecd56283195..0000000000000 --- a/web/src/lib/components/forms/admin-registration-form.svelte +++ /dev/null @@ -1,78 +0,0 @@ - - -
-
- - -
- -
- - -
- -
- - -
- -
- - -
- - {#if errorMessage} -

{errorMessage}

- {/if} - -
- -
- diff --git a/web/src/lib/components/forms/change-password-form.svelte b/web/src/lib/components/forms/change-password-form.svelte deleted file mode 100644 index 6f16781d9a037..0000000000000 --- a/web/src/lib/components/forms/change-password-form.svelte +++ /dev/null @@ -1,64 +0,0 @@ - - -
-
- - -
- -
- - -
- - {#if errorMessage} -

{errorMessage}

- {/if} - -
- -
-
diff --git a/web/src/lib/components/forms/login-form.svelte b/web/src/lib/components/forms/login-form.svelte deleted file mode 100644 index 6c1dcecba3dd5..0000000000000 --- a/web/src/lib/components/forms/login-form.svelte +++ /dev/null @@ -1,177 +0,0 @@ - - -{#if !oauthLoading && $featureFlags.passwordLogin} -
- {#if errorMessage} -

- {errorMessage} -

- {/if} - -
- - -
- -
- - -
- -
- -
-
-{/if} - -{#if $featureFlags.oauth} - {#if $featureFlags.passwordLogin} -
-
- - {$t('or')} - -
- {/if} -
- {#if oauthError} -

{oauthError}

- {/if} - -
-{/if} - -{#if !$featureFlags.passwordLogin && !$featureFlags.oauth} -

{$t('login_has_been_disabled')}

-{/if} diff --git a/web/src/lib/components/shared-components/fullscreen-container.svelte b/web/src/lib/components/layouts/AuthPageLayout.svelte similarity index 93% rename from web/src/lib/components/shared-components/fullscreen-container.svelte rename to web/src/lib/components/layouts/AuthPageLayout.svelte index 64ee41a2255cc..c470f809a6bff 100644 --- a/web/src/lib/components/shared-components/fullscreen-container.svelte +++ b/web/src/lib/components/layouts/AuthPageLayout.svelte @@ -1,6 +1,6 @@ - + {#snippet message()}

{$t('hi_user', { values: { name: $user.name, email: $user.email } })} @@ -31,5 +44,23 @@

{/snippet} - -
+
+
+ + +
+ +
+ + +
+ + {#if errorMessage} +

{errorMessage}

+ {/if} + +
+ +
+
+ diff --git a/web/src/routes/auth/login/+page.svelte b/web/src/routes/auth/login/+page.svelte index 0ab506f5e3ca6..63346a6abf69f 100644 --- a/web/src/routes/auth/login/+page.svelte +++ b/web/src/routes/auth/login/+page.svelte @@ -1,9 +1,17 @@ {#if $featureFlags.loaded} - + {#snippet message()}

@@ -22,10 +111,82 @@

{/snippet} - await goto(AppRoute.PHOTOS, { invalidateAll: true })} - onFirstLogin={async () => await goto(AppRoute.AUTH_CHANGE_PASSWORD)} - onOnboarding={async () => await goto(AppRoute.AUTH_ONBOARDING)} - /> -
+ {#if !oauthLoading && $featureFlags.passwordLogin} +
+ {#if errorMessage} +

+ {errorMessage} +

+ {/if} + +
+ + +
+ +
+ + +
+ +
+ +
+
+ {/if} + + {#if $featureFlags.oauth} + {#if $featureFlags.passwordLogin} +
+
+ + {$t('or')} + +
+ {/if} +
+ {#if oauthError} +

{oauthError}

+ {/if} + +
+ {/if} + + {#if !$featureFlags.passwordLogin && !$featureFlags.oauth} +

{$t('login_has_been_disabled')}

+ {/if} + {/if} diff --git a/web/src/routes/auth/register/+page.svelte b/web/src/routes/auth/register/+page.svelte index 2e55ba7435ecd..43e28d5964687 100644 --- a/web/src/routes/auth/register/+page.svelte +++ b/web/src/routes/auth/register/+page.svelte @@ -1,22 +1,86 @@ - + {#snippet message()}

{$t('admin.registration_description')}

{/snippet} - -
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + {#if errorMessage} +

{errorMessage}

+ {/if} + +
+ +
+
+ From 4279cd6e1e98685aa55e49758e8adc3c97c0ecf2 Mon Sep 17 00:00:00 2001 From: Mattia Natali Date: Tue, 14 Jan 2025 15:24:58 +0100 Subject: [PATCH 13/18] feat(web): Slideshow is enabled everywhere. It no longer needs assetStore. (#15077) Slideshow no longer needs assetStore. It is enabled everywhere Co-authored-by: Alex --- .../asset-viewer/asset-viewer.svelte | 52 ++++++++----------- .../components/photos-page/asset-grid.svelte | 14 ++++- .../gallery-viewer/gallery-viewer.svelte | 51 +++++++++++++++--- .../duplicates-compare-control.svelte | 39 +++++++++++--- web/src/lib/stores/asset-viewing.store.ts | 3 +- .../[[assetId=id]]/+page.svelte | 15 ++++++ 6 files changed, 128 insertions(+), 46 deletions(-) diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 7a2f97bb655ee..ea5d6e92759cb 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -8,7 +8,6 @@ import { updateNumberOfComments } from '$lib/stores/activity.store'; import { closeEditorCofirm } from '$lib/stores/asset-editor.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; - import type { AssetStore } from '$lib/stores/assets.store'; import { isShowDetail } from '$lib/stores/preferences.store'; import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import { user } from '$lib/stores/user.store'; @@ -49,8 +48,9 @@ import VideoViewer from './video-wrapper-viewer.svelte'; import ImagePanoramaViewer from './image-panorama-viewer.svelte'; + type HasAsset = boolean; + interface Props { - assetStore?: AssetStore | null; asset: AssetResponseDto; preloadAssets?: AssetResponseDto[]; showNavigation?: boolean; @@ -61,13 +61,13 @@ onAction?: OnAction | undefined; reactions?: ActivityResponseDto[]; onClose: (dto: { asset: AssetResponseDto }) => void; - onNext: () => void; - onPrevious: () => void; + onNext: () => Promise; + onPrevious: () => Promise; + onRandom: () => Promise; copyImage?: () => Promise; } let { - assetStore = null, asset = $bindable(), preloadAssets = $bindable([]), showNavigation = true, @@ -80,6 +80,7 @@ onClose, onNext, onPrevious, + onRandom, copyImage = $bindable(), }: Props = $props(); @@ -271,22 +272,6 @@ }); }; - const navigateAssetRandom = async () => { - if (!assetStore) { - return; - } - - const asset = await assetStore.getRandomAsset(); - if (!asset) { - return; - } - - slideshowHistory.queue(asset); - - setAsset(asset); - $restartSlideshowProgress = true; - }; - const navigateAsset = async (order?: 'previous' | 'next', e?: Event) => { if (!order) { if ($slideshowState === SlideshowState.PlaySlideshow) { @@ -296,23 +281,30 @@ } } + e?.stopPropagation(); + + let hasNext = false; + if ($slideshowState === SlideshowState.PlaySlideshow && $slideshowNavigation === SlideshowNavigation.Shuffle) { - return (order === 'previous' ? slideshowHistory.previous() : slideshowHistory.next()) || navigateAssetRandom(); + hasNext = order === 'previous' ? slideshowHistory.previous() : slideshowHistory.next(); + if (!hasNext) { + const asset = await onRandom(); + if (asset) { + slideshowHistory.queue(asset); + hasNext = true; + } + } + } else { + hasNext = order === 'previous' ? await onPrevious() : await onNext(); } - if ($slideshowState === SlideshowState.PlaySlideshow && assetStore) { - const hasNext = - order === 'previous' ? await assetStore.getPreviousAsset(asset) : await assetStore.getNextAsset(asset); + if ($slideshowState === SlideshowState.PlaySlideshow) { if (hasNext) { $restartSlideshowProgress = true; } else { await handleStopSlideshow(); } } - - e?.stopPropagation(); - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - order === 'previous' ? onPrevious() : onNext(); }; // const showEditorHandler = () => { @@ -435,7 +427,7 @@ {person} {stack} showDetailButton={enableDetailPanel} - showSlideshow={!!assetStore} + showSlideshow={true} onZoomImage={zoomToggle} onCopyImage={copyImage} onAction={handleAction} diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index 55f935c8ddff7..fd98f7e6a3a10 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -527,6 +527,18 @@ return !!nextAsset; }; + const handleRandom = async () => { + const randomAsset = await $assetStore.getRandomAsset(); + + if (randomAsset) { + const preloadAsset = await $assetStore.getNextAsset(randomAsset); + assetViewingStore.setAsset(randomAsset, preloadAsset ? [preloadAsset] : []); + await navigate({ targetRoute: 'current', assetId: randomAsset.id }); + } + + return randomAsset; + }; + const handleClose = async ({ asset }: { asset: AssetResponseDto }) => { assetViewingStore.showAssetViewer(false); showSkeleton = true; @@ -911,7 +923,6 @@ {#await import('../asset-viewer/asset-viewer.svelte') then { default: AssetViewer }} {/await} diff --git a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte index 65c6c20e7b75b..4c3c35aecab44 100644 --- a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte +++ b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte @@ -34,6 +34,7 @@ isShowDeleteConfirmation?: boolean; onPrevious?: (() => Promise) | undefined; onNext?: (() => Promise) | undefined; + onRandom?: (() => Promise) | undefined; } let { @@ -47,6 +48,7 @@ isShowDeleteConfirmation = $bindable(false), onPrevious = undefined, onNext = undefined, + onRandom = undefined, }: Props = $props(); let { isViewing: isViewerOpen, asset: viewingAsset, setAsset } = assetViewingStore; @@ -202,35 +204,71 @@ })(), ); - const handleNext = async () => { + const handleNext = async (): Promise => { try { let asset: AssetResponseDto | undefined; if (onNext) { asset = await onNext(); } else { - currentViewAssetIndex = Math.min(currentViewAssetIndex + 1, assets.length - 1); - asset = assets[currentViewAssetIndex]; + currentViewAssetIndex = currentViewAssetIndex + 1; + asset = currentViewAssetIndex < assets.length ? assets[currentViewAssetIndex] : undefined; + } + + if (!asset) { + return false; + } + + await navigateToAsset(asset); + return true; + } catch (error) { + handleError(error, $t('errors.cannot_navigate_next_asset')); + return false; + } + }; + + const handleRandom = async (): Promise => { + try { + let asset: AssetResponseDto | undefined; + if (onRandom) { + asset = await onRandom(); + } else { + if (assets.length > 0) { + const randomIndex = Math.floor(Math.random() * assets.length); + asset = assets[randomIndex]; + } + } + + if (!asset) { + return null; } await navigateToAsset(asset); + return asset; } catch (error) { handleError(error, $t('errors.cannot_navigate_next_asset')); + return null; } }; - const handlePrevious = async () => { + const handlePrevious = async (): Promise => { try { let asset: AssetResponseDto | undefined; if (onPrevious) { asset = await onPrevious(); } else { - currentViewAssetIndex = Math.max(currentViewAssetIndex - 1, 0); - asset = assets[currentViewAssetIndex]; + currentViewAssetIndex = currentViewAssetIndex - 1; + asset = currentViewAssetIndex >= 0 ? assets[currentViewAssetIndex] : undefined; + } + + if (!asset) { + return false; } await navigateToAsset(asset); + return true; } catch (error) { handleError(error, $t('errors.cannot_navigate_previous_asset')); + return false; } }; @@ -372,6 +410,7 @@ onAction={handleAction} onPrevious={handlePrevious} onNext={handleNext} + onRandom={handleRandom} onClose={() => { assetViewingStore.showAssetViewer(false); handlePromiseError(navigate({ targetRoute: 'current', assetId: null })); diff --git a/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte b/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte index 11a5c67fcfb99..e6b995434901e 100644 --- a/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte +++ b/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte @@ -42,6 +42,34 @@ assetViewingStore.showAssetViewer(false); }); + const onNext = () => { + const index = getAssetIndex($viewingAsset.id) + 1; + if (index >= assets.length) { + return Promise.resolve(false); + } + setAsset(assets[index]); + return Promise.resolve(true); + }; + + const onPrevious = () => { + const index = getAssetIndex($viewingAsset.id) - 1; + if (index < 0) { + return Promise.resolve(false); + } + setAsset(assets[index]); + return Promise.resolve(true); + }; + + const onRandom = () => { + if (assets.length <= 0) { + return Promise.resolve(null); + } + const index = Math.floor(Math.random() * assets.length); + const asset = assets[index]; + setAsset(asset); + return Promise.resolve(asset); + }; + const onSelectAsset = (asset: AssetResponseDto) => { if (selectedAssetIds.has(asset.id)) { selectedAssetIds.delete(asset.id); @@ -153,14 +181,9 @@ 1} - onNext={() => { - const index = getAssetIndex($viewingAsset.id) + 1; - setAsset(assets[index % assets.length]); - }} - onPrevious={() => { - const index = getAssetIndex($viewingAsset.id) - 1 + assets.length; - setAsset(assets[index % assets.length]); - }} + {onNext} + {onPrevious} + {onRandom} onClose={() => { assetViewingStore.showAssetViewer(false); handlePromiseError(navigate({ targetRoute: 'current', assetId: null })); diff --git a/web/src/lib/stores/asset-viewing.store.ts b/web/src/lib/stores/asset-viewing.store.ts index 2e6e44511d36b..689556b52242a 100644 --- a/web/src/lib/stores/asset-viewing.store.ts +++ b/web/src/lib/stores/asset-viewing.store.ts @@ -15,9 +15,10 @@ function createAssetViewingStore() { viewState.set(true); }; - const setAssetId = async (id: string) => { + const setAssetId = async (id: string): Promise => { const asset = await getAssetInfo({ id, key: getKey() }); setAsset(asset); + return asset; }; const showAssetViewer = (show: boolean) => { diff --git a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte index 613ae4d66bed7..2239a21cd5699 100644 --- a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -107,14 +107,28 @@ if (viewingAssetCursor < viewingAssets.length - 1) { await setAssetId(viewingAssets[++viewingAssetCursor]); await navigate({ targetRoute: 'current', assetId: $viewingAsset.id }); + return true; } + return false; } async function navigatePrevious() { if (viewingAssetCursor > 0) { await setAssetId(viewingAssets[--viewingAssetCursor]); await navigate({ targetRoute: 'current', assetId: $viewingAsset.id }); + return true; } + return false; + } + + async function navigateRandom() { + if (viewingAssets.length <= 0) { + return null; + } + const index = Math.floor(Math.random() * viewingAssets.length); + const asset = await setAssetId(viewingAssets[index]); + await navigate({ targetRoute: 'current', assetId: $viewingAsset.id }); + return asset; } @@ -132,6 +146,7 @@ showNavigation={viewingAssets.length > 1} onNext={navigateNext} onPrevious={navigatePrevious} + onRandom={navigateRandom} onClose={() => { assetViewingStore.showAssetViewer(false); handlePromiseError(navigate({ targetRoute: 'current', assetId: null })); From 19e2504583595d4c5e6ce847a89e4dacdf762ad2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 10:19:01 -0500 Subject: [PATCH 14/18] fix(deps): update machine-learning (#15336) --- machine-learning/Dockerfile | 4 ++-- machine-learning/poetry.lock | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/machine-learning/Dockerfile b/machine-learning/Dockerfile index 705e4827ff1e0..925e027ff64b9 100644 --- a/machine-learning/Dockerfile +++ b/machine-learning/Dockerfile @@ -1,6 +1,6 @@ ARG DEVICE=cpu -FROM python:3.11-bookworm@sha256:b337e1fd27dbacda505219f713789bf82766694095876769ea10c2d34b4f470b AS builder-cpu +FROM python:3.11-bookworm@sha256:f997d3f71b7dcff3f937703c02861437f2b41a94e1ddbd1b5fa357ee99f5cce4 AS builder-cpu FROM builder-cpu AS builder-openvino @@ -34,7 +34,7 @@ RUN python3 -m venv /opt/venv COPY poetry.lock pyproject.toml ./ RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev -FROM python:3.11-slim-bookworm@sha256:873952659a04188d2a62d5f7e30fd673d2559432a847a8ad5fcaf9cbd085e9ed AS prod-cpu +FROM python:3.11-slim-bookworm@sha256:de909939264a469f834cf89e4dd2ed6aca2ef0844f47ba0bf7f2b4661f5111a5 AS prod-cpu FROM prod-cpu AS prod-openvino diff --git a/machine-learning/poetry.lock b/machine-learning/poetry.lock index 33a4354c3065e..ebfd075c7c7d3 100644 --- a/machine-learning/poetry.lock +++ b/machine-learning/poetry.lock @@ -2498,13 +2498,13 @@ files = [ [[package]] name = "pydantic" -version = "2.10.4" +version = "2.10.5" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, - {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, + {file = "pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53"}, + {file = "pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff"}, ] [package.dependencies] From 3e11b90851819374342d493d6c575f0c14381463 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 13:20:12 -0500 Subject: [PATCH 15/18] chore(deps): update node.js to v22.13.0 (#15337) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/Dockerfile | 2 +- server/Dockerfile | 2 +- web/Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/Dockerfile b/cli/Dockerfile index 31dd8576e2eaf..da2f17cc39014 100644 --- a/cli/Dockerfile +++ b/cli/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22.12.0-alpine3.20@sha256:96cc8323e25c8cc6ddcb8b965e135cfd57846e8003ec0d7bcec16c5fd5f6d39f AS core +FROM node:22.13.0-alpine3.20@sha256:db8dcb90326a0116375414e9a7c068a6b87a4422b7da37b5c6cd026f7c7835d3 AS core WORKDIR /usr/src/open-api/typescript-sdk COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./ diff --git a/server/Dockerfile b/server/Dockerfile index 85c3ffae1f242..7a54578770bc3 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -25,7 +25,7 @@ COPY --from=dev /usr/src/app/node_modules/@img ./node_modules/@img COPY --from=dev /usr/src/app/node_modules/exiftool-vendored.pl ./node_modules/exiftool-vendored.pl # web build -FROM node:22.12.0-alpine3.20@sha256:96cc8323e25c8cc6ddcb8b965e135cfd57846e8003ec0d7bcec16c5fd5f6d39f AS web +FROM node:22.13.0-alpine3.20@sha256:db8dcb90326a0116375414e9a7c068a6b87a4422b7da37b5c6cd026f7c7835d3 AS web WORKDIR /usr/src/open-api/typescript-sdk COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./ diff --git a/web/Dockerfile b/web/Dockerfile index dfef1d83481d3..0b510004262b5 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22.12.0-alpine3.20@sha256:96cc8323e25c8cc6ddcb8b965e135cfd57846e8003ec0d7bcec16c5fd5f6d39f +FROM node:22.13.0-alpine3.20@sha256:db8dcb90326a0116375414e9a7c068a6b87a4422b7da37b5c6cd026f7c7835d3 RUN apk add --no-cache tini USER node From 073fccb517afb13335e9428e2f9082d2c9137b48 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 18:33:27 +0000 Subject: [PATCH 16/18] chore(deps): update python:3.11-slim-bookworm docker digest to 6ed5bff (#15346) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- machine-learning/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machine-learning/Dockerfile b/machine-learning/Dockerfile index 925e027ff64b9..7b0f97c1cf8b7 100644 --- a/machine-learning/Dockerfile +++ b/machine-learning/Dockerfile @@ -34,7 +34,7 @@ RUN python3 -m venv /opt/venv COPY poetry.lock pyproject.toml ./ RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev -FROM python:3.11-slim-bookworm@sha256:de909939264a469f834cf89e4dd2ed6aca2ef0844f47ba0bf7f2b4661f5111a5 AS prod-cpu +FROM python:3.11-slim-bookworm@sha256:6ed5bff4d7d377e2a27d9285553b8c21cfccc4f00881de1b24c9bc8d90016e82 AS prod-cpu FROM prod-cpu AS prod-openvino From b9000d8770109bbd145040e13de07c3f5e16927f Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Tue, 14 Jan 2025 14:53:33 -0500 Subject: [PATCH 17/18] feat(web): immich-ui components (#14263) * feat: add immich-ui to auth pages * fix: welcome icon * styling * fix: mobile padding --------- Co-authored-by: Alex Tran --- docker/docker-compose.dev.yml | 1 + docs/docs/developer/setup.md | 11 + web/package-lock.json | 317 +++++++++++------- web/package.json | 1 + web/src/app.css | 24 ++ .../components/layouts/AuthPageLayout.svelte | 39 +-- web/src/routes/+layout.svelte | 10 + web/src/routes/+page.svelte | 11 +- .../routes/auth/change-password/+page.svelte | 50 ++- web/src/routes/auth/login/+page.svelte | 80 ++--- web/src/routes/auth/register/+page.svelte | 47 ++- web/tailwind.config.js | 15 +- web/vite.config.js | 1 + 13 files changed, 345 insertions(+), 262 deletions(-) diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index fc1e2602daf1c..4dc41e143e003 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -71,6 +71,7 @@ services: - ../web:/usr/src/app - ../i18n:/usr/src/i18n - ../open-api/:/usr/src/open-api/ + # - ../../ui:/usr/ui - /usr/src/app/node_modules ulimits: nofile: diff --git a/docs/docs/developer/setup.md b/docs/docs/developer/setup.md index 9dbaf157b5d49..f341c3e9cbd3d 100644 --- a/docs/docs/developer/setup.md +++ b/docs/docs/developer/setup.md @@ -63,6 +63,17 @@ If you only want to do web development connected to an existing, remote backend, IMMICH_SERVER_URL=https://demo.immich.app/ npm run dev ``` +#### `@immich/ui` + +To see local changes to `@immich/ui` in Immich, do the following: + +1. Install `@immich/ui` as a sibling to `immich/`, for example `/home/user/immich` and `/home/user/ui` +1. Build the `@immich/ui` project via `npm run build` +1. Uncomment the corresponding volume in web service of the `docker/docker-compose.dev.yaml` file (`../../ui:/usr/ui`) +1. Uncomment the corresponding alias in the `web/vite.config.js` file (`'@immich/ui': path.resolve(\_\_dirname, '../../ui')`) +1. Start up the stack via `make dev` +1. After making changes in `@immich/ui`, rebuild it (`npm run build`) + ### Mobile app The mobile app `(/mobile)` will required Flutter toolchain 3.13.x to be installed on your system. diff --git a/web/package-lock.json b/web/package-lock.json index 426df0acd78d6..14d0928731bb7 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@formatjs/icu-messageformat-parser": "^2.9.8", "@immich/sdk": "file:../open-api/typescript-sdk", + "@immich/ui": "^0.11.0", "@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mdi/js": "^7.4.47", "@photo-sphere-viewer/core": "^5.11.5", @@ -104,7 +105,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, "engines": { "node": ">=10" }, @@ -793,6 +793,31 @@ "npm": ">=9.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, "node_modules/@formatjs/ecma402-abstract": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.2.tgz", @@ -1279,11 +1304,34 @@ "resolved": "../open-api/typescript-sdk", "link": true }, + "node_modules/@immich/ui": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.11.0.tgz", + "integrity": "sha512-zRQFHCVt6BstNkGuVt27rLUAurOpZ0djfaZYDeqHuc8H97XXXk+hsbXzvADlVa9xAPHetUM3JuusPseJ+Hr23g==", + "license": "GNU Affero General Public License version 3", + "dependencies": { + "@mdi/js": "^7.4.47", + "bits-ui": "^1.0.0-next.46", + "tailwind-merge": "^2.5.4", + "tailwind-variants": "^0.3.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/@internationalized/date": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.6.0.tgz", + "integrity": "sha512-+z6ti+CcJnRlLHok/emGEsWQhe7kfSmEW+/6qCzvKY67YPh7YOBfvc7+/+NXq+zJlbArg30tYpqLjNgcAYv2YQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -1300,7 +1348,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, "engines": { "node": ">=12" }, @@ -1312,7 +1359,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "engines": { "node": ">=12" }, @@ -1323,14 +1369,12 @@ "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -1347,7 +1391,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -1362,7 +1405,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -1542,7 +1584,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -1555,7 +1596,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -1564,7 +1604,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1605,7 +1644,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "optional": true, "engines": { "node": ">=14" @@ -2017,6 +2055,15 @@ "vite": "^5.0.0" } }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@testing-library/dom": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.2.0.tgz", @@ -2826,7 +2873,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -2846,14 +2892,12 @@ "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -2865,8 +2909,7 @@ "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" }, "node_modules/argparse": { "version": "2.0.1", @@ -2967,18 +3010,40 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, "engines": { "node": ">=8" } }, + "node_modules/bits-ui": { + "version": "1.0.0-next.77", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-1.0.0-next.77.tgz", + "integrity": "sha512-IV0AyVEvsRkXv4s/fl4iea5E9W2b9EBf98s9mRMKMc1xHxM9MmtM2r6MZMqftHQ/c+gHTIt3A9EKuTlh7uay8w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.4", + "@floating-ui/dom": "^1.6.7", + "@internationalized/date": "^3.5.6", + "esm-env": "^1.1.2", + "runed": "^0.22.0", + "svelte-toolbelt": "^0.7.0" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/huntabyte" + }, + "peerDependencies": { + "svelte": "^5.11.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2993,7 +3058,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -3093,7 +3157,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, "engines": { "node": ">= 6" } @@ -3164,7 +3227,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -3189,7 +3251,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -3350,7 +3411,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, "engines": { "node": ">= 6" } @@ -3388,7 +3448,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -3416,7 +3475,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, "bin": { "cssesc": "bin/cssesc" }, @@ -3580,14 +3638,12 @@ "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, "node_modules/dom-accessibility-api": { "version": "0.5.16", @@ -3621,8 +3677,7 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/electron-to-chromium": { "version": "1.5.74", @@ -3634,8 +3689,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/engine.io-client": { "version": "6.5.4", @@ -4146,9 +4200,9 @@ } }, "node_modules/esm-env": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.1.tgz", - "integrity": "sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", "license": "MIT" }, "node_modules/esniff": { @@ -4306,7 +4360,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -4323,7 +4376,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -4347,7 +4399,6 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -4374,7 +4425,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -4424,7 +4474,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", - "dev": true, "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -4469,7 +4518,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -4482,8 +4530,7 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "node_modules/geojson-vt": { "version": "3.2.1", @@ -4527,7 +4574,6 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -4548,7 +4594,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -4560,7 +4605,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4570,7 +4614,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -4666,7 +4709,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -4852,6 +4894,12 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -4882,7 +4930,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -4909,7 +4956,6 @@ "version": "2.13.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", - "dev": true, "dependencies": { "has": "^1.0.3" }, @@ -4944,7 +4990,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4953,7 +4998,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -4962,7 +5006,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -4974,7 +5017,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -5113,7 +5155,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -5128,7 +5169,6 @@ "version": "1.21.6", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", - "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -5303,8 +5343,7 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/locate-character": { "version": "3.0.0", @@ -5546,7 +5585,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -5555,7 +5593,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -5623,7 +5660,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -5660,7 +5696,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -5671,7 +5706,6 @@ "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", - "dev": true, "funding": [ { "type": "github", @@ -5734,7 +5768,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5760,7 +5793,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5769,7 +5801,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, "engines": { "node": ">= 6" } @@ -5850,8 +5881,7 @@ "node_modules/package-json-from-dist": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", - "dev": true + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" }, "node_modules/parent-module": { "version": "1.0.1", @@ -5910,7 +5940,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -5918,14 +5947,12 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -5940,8 +5967,7 @@ "node_modules/path-scurry/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" }, "node_modules/pathe": { "version": "1.1.2", @@ -5976,14 +6002,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -5995,7 +6019,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6004,7 +6027,6 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, "engines": { "node": ">= 6" } @@ -6031,7 +6053,6 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.0.tgz", "integrity": "sha512-27VKOqrYfPncKA2NrFOVhP5MGAfHKLYn/Q0mz9cNQyRAKYi3VNHwYU2qKKqPCqgBmeeJ0uAFB56NumXZ5ZReXg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -6060,7 +6081,6 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", @@ -6077,7 +6097,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, "dependencies": { "camelcase-css": "^2.0.1" }, @@ -6125,7 +6144,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -6194,7 +6212,6 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -6207,8 +6224,7 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/potpack": { "version": "2.0.0", @@ -6341,7 +6357,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -6372,7 +6387,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, "dependencies": { "pify": "^2.3.0" } @@ -6483,7 +6497,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -6561,7 +6574,6 @@ "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", @@ -6587,7 +6599,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -6697,7 +6708,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -6716,6 +6726,21 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/runed": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.22.0.tgz", + "integrity": "sha512-ZWVXWhOr0P5xdNgtviz6D1ivLUDWKLCbeC5SUEJ3zBkqLReVqWHenFxMNFeFaiC5bfxhFxyxzyzB+98uYFtwdA==", + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", @@ -6843,7 +6868,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -6855,7 +6879,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -6870,7 +6893,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "engines": { "node": ">=14" }, @@ -6979,7 +7001,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -7078,7 +7099,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7093,7 +7113,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7107,7 +7126,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -7120,7 +7138,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -7152,11 +7169,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -7199,7 +7224,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -7841,6 +7865,41 @@ "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0-next.1" } }, + "node_modules/svelte-toolbelt": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.7.0.tgz", + "integrity": "sha512-i/Tv4NwAWWqJnK5H0F8y/ubDnogDYlwwyzKhrspTUFzrFuGnYshqd2g4/R43ds841wmaFiSW/HsdsdWhPOlrAA==", + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "runed": "^0.20.0", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/svelte-toolbelt/node_modules/runed": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.20.0.tgz", + "integrity": "sha512-YqPxaUdWL5nUXuSF+/v8a+NkVN8TGyEGbQwTA25fLY35MR/2bvZ1c6sCbudoo1kT4CAJPh4kUkcgGVxW127WKw==", + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, "node_modules/svelte/node_modules/aria-query": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", @@ -7858,11 +7917,36 @@ "optional": true, "peer": true }, + "node_modules/tailwind-merge": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", + "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwind-variants": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.3.0.tgz", + "integrity": "sha512-ho2k5kn+LB1fT5XdNS3Clb96zieWxbStE9wNLK7D0AV64kdZMaYzAKo0fWl6fXLPY99ffF9oBJnIj5escEl/8A==", + "license": "MIT", + "dependencies": { + "tailwind-merge": "^2.5.4" + }, + "engines": { + "node": ">=16.x", + "pnpm": ">=7.x" + }, + "peerDependencies": { + "tailwindcss": "*" + } + }, "node_modules/tailwindcss": { "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", - "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -7900,7 +7984,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, "license": "MIT", "engines": { "node": ">=14" @@ -7913,7 +7996,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -7949,7 +8031,6 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", - "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -8000,7 +8081,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, "dependencies": { "any-promise": "^1.0.0" } @@ -8009,7 +8089,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -8098,7 +8177,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -8163,8 +8241,7 @@ "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, "node_modules/tslib": { "version": "2.8.1", @@ -8308,8 +8385,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/validate-npm-package-license": { "version": "3.0.4", @@ -8581,7 +8657,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -8635,7 +8710,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -8652,7 +8726,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -8667,7 +8740,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -8678,8 +8750,7 @@ "node_modules/wrap-ansi-cjs/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", diff --git a/web/package.json b/web/package.json index b843f6c13af8d..fc936efdc4007 100644 --- a/web/package.json +++ b/web/package.json @@ -67,6 +67,7 @@ "dependencies": { "@formatjs/icu-messageformat-parser": "^2.9.8", "@immich/sdk": "file:../open-api/typescript-sdk", + "@immich/ui": "^0.11.0", "@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mdi/js": "^7.4.47", "@photo-sphere-viewer/core": "^5.11.5", diff --git a/web/src/app.css b/web/src/app.css index d1af865bcadfa..00cefc5ce60de 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -22,6 +22,30 @@ --immich-dark-success: 56 142 60; --immich-dark-warning: 245 124 0; } + + :root { + /* light */ + --immich-ui-primary: 66 80 175; + --immich-ui-dark: 0 0 0; + --immich-ui-light: 255 255 255; + --immich-ui-success: 34 197 94; + --immich-ui-danger: 180 0 0; + --immich-ui-warning: 255 170 0; + --immich-ui-info: 14 165 233; + --immich-ui-default-border: 209 213 219; + } + + .dark { + /* dark */ + --immich-ui-primary: 172 203 250; + --immich-ui-light: 0 0 0; + --immich-ui-dark: 229 231 235; + /* --immich-success: 56 142 60; */ + --immich-ui-danger: 239 68 68; + --immich-ui-warning: 255 170 0; + --immich-ui-info: 14 165 233; + --immich-ui-default-border: 55 65 81; + } } @font-face { diff --git a/web/src/lib/components/layouts/AuthPageLayout.svelte b/web/src/lib/components/layouts/AuthPageLayout.svelte index c470f809a6bff..3a61a1671cc2d 100644 --- a/web/src/lib/components/layouts/AuthPageLayout.svelte +++ b/web/src/lib/components/layouts/AuthPageLayout.svelte @@ -1,36 +1,25 @@ -
-
-
- -

- {title} -

-
- - {#if showMessage} -
- {@render message?.()} -
- {/if} - - {@render children?.()} -
+
+ + + + + {title} + + + + {@render children?.()} + +
diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index fa1351ab20d98..2706ead46e094 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -19,12 +19,22 @@ import { isAssetViewerRoute, isSharedLinkRoute } from '$lib/utils/navigation'; import { onDestroy, onMount, type Snippet } from 'svelte'; import { run } from 'svelte/legacy'; + import { setTranslations } from '@immich/ui'; import '../app.css'; + import { t } from 'svelte-i18n'; interface Props { children?: Snippet; } + $effect(() => { + setTranslations({ + close: $t('close'), + showPassword: $t('show_password'), + hidePassword: $t('hide_password'), + }); + }); + let { children }: Props = $props(); let showNavigationLoadingBar = $state(false); diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index 68a5deb0f9157..b3ac52bd7c0df 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -1,17 +1,16 @@
-
+
- +
-

{$t('welcome_to_immich')}

-
diff --git a/web/src/routes/auth/change-password/+page.svelte b/web/src/routes/auth/change-password/+page.svelte index ea340ff600728..6b911184752db 100644 --- a/web/src/routes/auth/change-password/+page.svelte +++ b/web/src/routes/auth/change-password/+page.svelte @@ -1,11 +1,10 @@ - {#snippet message()} -

- {$t('hi_user', { values: { name: $user.name, email: $user.email } })} -
-
- {$t('change_password_description')} -

- {/snippet} +
+ + + {$t('hi_user', { values: { name: $user.name, email: $user.email } })} + {$t('change_password_description')} + + +
-
-
- - -
+ + + + + -
- - -
+ + + {#if errorMessage} + {errorMessage} + {/if} + - {#if errorMessage} -

{errorMessage}

- {/if} - -
- -
+
+ +
+
diff --git a/web/src/routes/auth/login/+page.svelte b/web/src/routes/auth/login/+page.svelte index 63346a6abf69f..f52face78eb31 100644 --- a/web/src/routes/auth/login/+page.svelte +++ b/web/src/routes/auth/login/+page.svelte @@ -1,17 +1,14 @@ {#if $featureFlags.loaded} - - {#snippet message()} -

+ + {#if $serverConfig.loginPageMessage} + {@html $serverConfig.loginPageMessage} -

- {/snippet} + + {/if} {#if !oauthLoading && $featureFlags.passwordLogin} -
+ {#if errorMessage} -

- {errorMessage} -

+ {/if} -
- - -
+ + + -
- - -
+ + + -
- -
+ {/if} {#if $featureFlags.oauth} {#if $featureFlags.passwordLogin} -
+

- {$t('or')} + {$t('or').toUpperCase()}
{/if} -
+
{#if oauthError} -

{oauthError}

+ {/if}
{/if} {#if !$featureFlags.passwordLogin && !$featureFlags.oauth} -

{$t('login_has_been_disabled')}

+ {/if} {/if} diff --git a/web/src/routes/auth/register/+page.svelte b/web/src/routes/auth/register/+page.svelte index 43e28d5964687..50551358ea698 100644 --- a/web/src/routes/auth/register/+page.svelte +++ b/web/src/routes/auth/register/+page.svelte @@ -1,12 +1,11 @@ - {#snippet message()} -

- {$t('admin.registration_description')} -

- {/snippet} +
+ + {$t('admin.registration_description')} + +
-
-
- - -
+ + + + -
- - -
+ + + -
- - -
+ + + -
- - -
+ + + {#if errorMessage} -

{errorMessage}

+ {/if}
- +
diff --git a/web/tailwind.config.js b/web/tailwind.config.js index eb1ea78fae76f..12bfd7c604da4 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -2,7 +2,7 @@ import plugin from 'tailwindcss/plugin'; /** @type {import('tailwindcss').Config} */ export default { - content: ['./src/**/*.{html,js,svelte,ts}'], + content: ['./src/**/*.{html,js,svelte,ts}', './node_modules/@immich/ui/dist/**/*.{svelte,js}'], darkMode: 'class', theme: { extend: { @@ -24,7 +24,20 @@ export default { 'immich-dark-error': 'rgb(var(--immich-dark-error) / )', 'immich-dark-success': 'rgb(var(--immich-dark-success) / )', 'immich-dark-warning': 'rgb(var(--immich-dark-warning) / )', + + primary: 'rgb(var(--immich-ui-primary) / )', + light: 'rgb(var(--immich-ui-light) / )', + dark: 'rgb(var(--immich-ui-dark) / )', + success: 'rgb(var(--immich-ui-success) / )', + danger: 'rgb(var(--immich-ui-danger) / )', + warning: 'rgb(var(--immich-ui-warning) / )', + info: 'rgb(var(--immich-ui-info) / )', + subtle: 'rgb(var(--immich-gray) / )', }, + borderColor: ({ theme }) => ({ + ...theme('colors'), + DEFAULT: 'rgb(var(--immich-ui-default-border) / )', + }), fontFamily: { 'immich-mono': ['Overpass Mono', 'monospace'], }, diff --git a/web/vite.config.js b/web/vite.config.js index 266312e137dbb..5d134beab081b 100644 --- a/web/vite.config.js +++ b/web/vite.config.js @@ -19,6 +19,7 @@ export default defineConfig({ 'xmlhttprequest-ssl': './node_modules/engine.io-client/lib/xmlhttprequest.js', // eslint-disable-next-line unicorn/prefer-module '@test-data': path.resolve(__dirname, './src/test-data'), + // '@immich/ui': path.resolve(__dirname, '../../ui'), }, }, server: { From 5d2e421800c47d02d97736ad44abe89c89f430f9 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Tue, 14 Jan 2025 15:01:21 -0500 Subject: [PATCH 18/18] chore: add renovate config for immich-ui (#15349) --- renovate.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/renovate.json b/renovate.json index dd3ca1ad59e3e..2634eaef4d119 100644 --- a/renovate.json +++ b/renovate.json @@ -6,6 +6,10 @@ ], "minimumReleaseAge": "5 days", "packageRules": [ + { + "groupName": "@immich/ui", + "matchPackageNames": ["@immich/ui"] + }, { "matchFileNames": [ "cli/**",