Compare commits

...

189 Commits

Author SHA1 Message Date
Yaros f1ffdfe223 chore: add todos & warning 2026-04-30 19:48:57 +02:00
Yaros 44a77892f4 Merge branch 'main' into fix/map-sidepanel-queries 2026-04-28 19:58:44 +02:00
Yaros 31fb7f6aa8 Merge branch 'main' into fix/map-sidepanel-queries 2026-04-28 19:56:15 +02:00
Timon 96b6165bd3 refactor(server)!: move correlationId to X-Correlation-ID response header (#28139) 2026-04-28 13:07:39 -04:00
Mees Frensel 2624f3884f fix(web): large files: better handling of asset deletions (#28117) 2026-04-28 18:18:39 +02:00
Timon f9b7ce9407 fix(web): convert shared link expiry to UTC before serialising (#28135) 2026-04-28 16:10:08 +00:00
Timon 013ea37a0d refactor!: change number to integer types (#27912)
* refactor!: change number to integer types

* fix oversight
2026-04-28 11:25:03 -04:00
Mees Frensel b2b4385271 chore(web): refactor people panel (#28136) 2026-04-28 11:22:22 -04:00
Mees Frensel 081c75bb21 fix(web): refresh memories hourly (#28114) 2026-04-28 11:18:51 -04:00
renovate[bot] da337578fb fix(deps): update typescript-projects (#28132)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-04-28 13:18:29 +02:00
renovate[bot] acf4109171 chore(deps): update dependency exiftool-vendored to v35.18.0 (#28133)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-28 12:46:54 +02:00
renovate[bot] 66601a1fdc chore(deps): update dependency terragrunt to v1.0.2 (#28125)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-28 12:00:44 +02:00
renovate[bot] 02ff077367 chore(deps): update prom/prometheus docker digest to e425440 (#28120)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-28 12:00:11 +02:00
renovate[bot] 94bb6c1a5e chore(deps): update dependency @immich/ui to v0.76.2 (#28121)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-27 21:54:12 -05:00
Daniel Dietzler fe9e5afcf4 fix: do not emit AlbumInvite event for owner (#28110) 2026-04-27 17:59:46 +00:00
Yosi Taguri 5e89efba64 fix(ml): handle empty/corrupt images in face detection (#27391)
* fix(ml): handle empty/corrupt images in face detection

When a corrupt or degenerate image with zero-dimension (0 width or 0 height)
reaches the face detection pipeline, insightface's RetinaFace.detect() calls
cv2.resize() with a target size of 0, triggering an OpenCV assertion failure:

  error: (-215:Assertion failed) inv_scale_x > 0 in function 'resize'

This crashes the ML worker and returns a 500 error to the server.

Add an early return in FaceDetector._predict() that checks for zero-dimension
images after decoding and returns empty detection results instead of passing
them to the insightface model.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ml): move empty image validation to request level

Per review feedback, validate image dimensions in the predict endpoint
(returning 400) rather than in each model's _predict method. This
catches all zero-dimension images before they reach any model task.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ml): resolve mypy strict type error in predict endpoint

Use intermediate `decoded` variable so mypy knows `.width` and `.height`
are accessed on `Image`, not on `Image | str`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 11:14:34 -04:00
Peter Ombodi 5a457d72c9 fix(mobile): delete assets on trash empty, Android (#26070)
* fix(mobile): improve trash sync flow
- trash local assets on remote delete events
- unify remote trash handling and support assetDelete cleanup by remote asset id
- update sync stream tests

* fix(mobile): revert pubspec.lock

* refactor(mobile): remove helper
remove unused columns from results

* refactor(mobile): use remoteIds in getAssetsFromBackupAlbums and remove getAssetsFromBackupAlbumsByRemoteIds
refactor tests

---------

Co-authored-by: Peter Ombodi <peter.ombodi@gmail.com>
2026-04-27 18:46:49 +05:30
Min Idzelis 45ccdb37fb refactor(web): replace asset-viewer listener based face hover with overlay elements (#27400) 2026-04-27 12:08:34 +02:00
Savely Krasovsky 9263e2f2e1 feat(ml): update Intel graphics compiler and compute runtime (#28076)
feat(ml): update Intel graphics compiler and compute runtime to latest versions
2026-04-25 08:49:57 -04:00
Aaron Liu a3ee615c5b chore(ml): update huggingfacehub and pillow (#27552) 2026-04-24 19:44:01 -04:00
Yaros 39cfad7136 feat(mobile): action bottom sheet on map timeline (#27515) 2026-04-24 09:30:10 -05:00
renovate[bot] 350056dd1a fix(deps): update dependency uuid to v14 [security] (#28046)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-23 11:24:33 +02:00
Alex f0835d06f8 chore: migrate to FUTO Apple's account (#28020)
* chore: migrate to FUTO Apple's account

* chore: migrate to FUTO Apple's account

* chore: match widget and share extension

* chore: update app share group

* reuse group.app.immich.share
2026-04-22 11:53:20 -05:00
Alex 03b70cf029 fix: jump to timeline on new auto_router update (#28022) 2026-04-22 10:21:48 -05:00
Daniel Dietzler 4bfb8b36c2 chore!: migrate album owner to album_user (#27467)
Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-04-22 16:52:23 +02:00
renovate[bot] dfacde5af8 fix(deps): update typescript-projects (#28025)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-04-22 16:49:28 +02:00
Junghwan 317afe9e3b fix(web): normalize underscore locale codes in dynamic language selection (#27900)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-04-22 13:28:33 +00:00
Jason Rasmussen 1fb5f13237 fix: oauth prompt (#28021) 2026-04-22 09:19:28 -04:00
Luis Nachtigall 793a7054fb fix(mobile): thumbnail transition to asset viewer (#27850) 2026-04-21 15:54:40 -05:00
Luis Nachtigall 3a874dd441 fix(mobile): enable autoplay for motion photos in video viewer (#27961) 2026-04-21 15:53:21 -05:00
Luis Nachtigall 3dc7dc93d8 fix(mobile): clear local data on forced logout (#27957) 2026-04-21 15:52:00 -05:00
Yaros 70397dc5a6 fix(mobile): zero exposure (#28017) 2026-04-21 15:47:27 -05:00
Jason Rasmussen a16d233a0c chore(web): sort imports (#27922)
* feat: sort imports

* fix: something?
2026-04-21 14:51:38 -04:00
Daniel Dietzler bb0872afef chore: upgrade sql-tools (#27885) 2026-04-21 17:42:27 +00:00
Freddie Floydd b9ca68f6e4 chore(web): rename components to PascalCase (#28013)
chore: rename components to PascalCase
2026-04-21 12:29:42 -04:00
Daniel Dietzler 837305da7e chore: un-skip tests (#28012) 2026-04-21 12:08:23 -04:00
Daniel Dietzler e20fb44142 fix: web navigation/animation regression (#28011) 2026-04-21 14:51:37 +00:00
renovate[bot] c2786978cd fix(deps): update typescript-projects (#28008)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-04-21 15:29:34 +02:00
renovate[bot] 312bb91a4f chore(deps): update github-actions (#28005)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-21 13:35:55 +02:00
renovate[bot] c1934b904c chore(deps): update dependency opentofu to v1.11.6 (#27999)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-21 13:31:16 +02:00
renovate[bot] 47752d158a fix(deps): update dependency @mapbox/mapbox-gl-rtl-text to v0.4.0 (#28007)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-21 13:25:30 +02:00
renovate[bot] 6267322b9c chore(deps): update dependency exiftool-vendored to v35.17.0 (#28004)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-21 13:08:12 +02:00
renovate[bot] 93c3cd49f3 chore(deps): update node.js to v24.15.0 (#28006)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-21 09:47:32 +00:00
renovate[bot] f52825ab08 chore(deps): update prom/prometheus docker digest to 5550dc6 (#27998)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-21 11:23:24 +02:00
renovate[bot] d74dc74f92 chore(deps): update dependency terragrunt to v1.0.1 (#28002)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-21 11:23:07 +02:00
Luis Nachtigall 539a39ae49 refactor(mobile): Migrate durationInSeconds to durationMs (#26615) 2026-04-20 23:28:11 -04:00
Daniel Dietzler f68cd424a7 chore: tags styling (#27984) 2026-04-20 22:06:43 -05:00
Alex 20c0cc7e73 fix: show neon light (#27994) 2026-04-20 20:12:54 -04:00
Aki Hakune be1b9a5f67 feat(server): add MPO file type support (#27963)
* feat(server): add MPO file type support

* fix(server): document description error
2026-04-20 17:45:53 -04:00
shenlong d9011c0829 refactor: test organisation and service test (#27991)
* refactor: test organisation

# Conflicts:
#	mobile/test/unit/utils/editor_test.dart

* regroup hash_service_test

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-04-20 17:45:20 -04:00
shenlong f909648bce chore: pump flutter to 3.41.7 (#27990)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-04-20 14:51:27 +00:00
Min Idzelis c78b1d8ab4 fix(web): prevent interaction with detail panel behind person side panel (#27309) 2026-04-20 15:26:06 +02:00
Jason Rasmussen 94a34436a3 chore: remove unused packages & code (#27925) 2026-04-20 08:39:46 -04:00
shenlong 0eef15a3ab chore(mobile): minor dependency updates (#27949)
* chore: minor dependency updates

* regenerate pod and remove unused imports

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-04-19 11:56:39 -05:00
shenlong 6982896549 feat: android periodic work manager task (#23563)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-04-19 11:55:07 -05:00
Matthew Momjian 2c812a2561 fix(docs): helmet file affected containers (#27939)
fix helmet file
2026-04-18 12:19:39 -04:00
Mert 0b1188e42e chore(server): separate ffmpeg arguments (#27937)
separate arguments
2026-04-18 15:33:44 +00:00
Freddie Floydd be20cd2bf9 chore(web): bump svelte-check version to silence big warning stack trace (#27935)
chore: bump svelte-check version to silence big warning log
2026-04-18 14:42:47 +00:00
LJspice b8591cb591 feat(server): add OIDC logout URL override option (#27389)
* feat(server): add OIDC logout URL override option
- Added toggle and field consistent with existing mobile redirect URI override.
- Existing auto-discovery remains default.
- Update tests and docs for new feature.

* fix(server): changes from review for OIDC logout URL override
- Rename 'logoutUri' to 'endSessionEndpoint'
- Remove toggle, just use override if provided
- Moved field in settings UI
2026-04-18 04:18:21 +00:00
Freddie Floydd 384d3a0984 fix(web): fix stale album page load (#27825)
* invalidate album data on album update to fix stale page load

* invalidate album data on album update to fix stale page load

* factor out callback, make async and await invalidate

* chore: formatting

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2026-04-17 21:24:33 -04:00
Freddie Floydd 03af669856 refactor(web): co-locate single-use components in /routes (#27921)
* co-locate single use components to /routes

* revert accidentally changed paths

* fix mangled path

* fmt

* fix accidentally moved multi-use components
2026-04-17 21:21:36 -04:00
renovate[bot] b0e4850d76 chore(deps): update dependency flutter to v3.41.6 (#27915)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-18 05:14:44 +05:30
Freddie Floydd 36ebcaf00c fix(web): compute hashes for uploads in chunks (#27878)
* add @noble/hashes as a dep for web

* hash files in chunks

* drop old reference to crypto in test code

* use web worker for file hashing
2026-04-17 19:08:46 -04:00
shenlong 7a86f2b7b9 chore: remove stale mobile/.isar submodule entry (#27913)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-04-18 04:29:13 +05:30
sparsh985 55f2b3b6a0 feat(server): add configurable OAuth prompt parameter (#26755)
* feat(server): add configurable OAuth prompt parameter

Add a `prompt` field to the OAuth system config, allowing admins to
configure the OIDC `prompt` parameter (e.g. `select_account`, `login`,
`consent`). Defaults to empty string (no prompt sent), preserving
backward compatibility.

This is useful for providers like Google where users want to be prompted
to select an account when multiple accounts are signed in.

Discussed in #20762

* chore: regenerate OpenAPI spec and clients for OAuth prompt field

* Adding e2e test cases

* feat: web setting

* feat: docs

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2026-04-17 21:20:07 +00:00
shenlong fd5e8d6521 chore: pump auto_route (#27876)
* chore: pump auto_route

* make build

* chore: use drift from pubdev (#27877)

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-04-17 20:28:36 +00:00
Freddie Floydd 6798d5df32 fix(server): require at least one field to be set when updating memory (#27842)
* add zod util to require one field is set in some schemas. appy to update memory endpoint

* add test
2026-04-17 20:18:48 +00:00
Min Idzelis 9d33853544 fix(web): respect abort signal after timeline bucket fetches (#27563)
Change-Id: I4bf7c7458883b50bd21484b1029d62526a6a6964
2026-04-17 16:18:14 -04:00
bo0tzz a46e46452c fix: run profile picture through thumbnail pipeline (#27890)
* fix: run profile picture through thumbnail pipeline

* fix: format
2026-04-17 16:15:59 -04:00
santanoce dbf30b77bf feat(server): added backchannel logout api endpoint (#26235)
* feat(server): added backchannel logout api endpoint

* test(server): fixed e2e tests

* fix(server): fixed suggested changes by reviewer

* feat(server): created function invalidateOAuth

* fix(server): fixed session.repository.sql

* test(server): added unit tests for backchannelLogout function

* test(server): added e2e tests for oidc backchnnel logout

* docs(server): added documentation on backchannel logout url

* docs(server): fixed typo

* feat(server): minor improvements of the oidc backchannel logout

* test(server): fixed tests after merge with main

* fix(server): fixed e2e test file

* refactor(server): tiny refactor of validateLogoutToken

* chore: cleanup

* fix: tests

* fix: make jwks extractable

---------

Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-04-17 18:45:33 +00:00
bo0tzz 8afca348ff fix: sanitize filenames before adding to zip (#27893)
* fix: sanitize filenames before adding to zip

* fix: lints

* chore: drop split()
2026-04-17 13:05:53 -04:00
shenlong 2070f775d6 refactor: remove riverpod generator (#27874)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-04-17 20:01:44 +05:30
bo0tzz a456a05052 chore: make filesystem backup docs even more explicit (#27013) 2026-04-17 15:42:40 +02:00
Sergey Katsubo b7eff33f90 chore(web): refactor date section of asset viewer (#24514)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-04-17 12:56:39 +00:00
Mees Frensel 18c0228f1b fix(web): remove json title from duplicate tool thumbnail (#27886) 2026-04-17 08:37:04 -04:00
Jason Rasmussen 2f8be45fe0 chore!: remove /api/server/theme endpoint (#27880)
chore: remove server/theme endpoint
2026-04-17 08:30:03 -04:00
Daniel Dietzler 41968fdcac feat: cache shared link (#27889) 2026-04-17 08:17:39 -04:00
Yaros 79c392ceba fix(docs): instructions on how to use local immich ui (#27813) 2026-04-17 12:29:14 +02:00
Mees Frensel 8fbeb64c59 fix(web): use event for zooming out after opening face editor (#27789) 2026-04-17 12:14:24 +02:00
Mees Frensel 7d181f0686 fix!: set duration to null when not present (#26982) 2026-04-17 11:57:10 +02:00
shenlong 2172dde7dc chore: remove immich lint (#27873)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-04-16 15:55:40 -05:00
Luis Nachtigall fce220b1d7 chore(mobile): update special format column detection (#27867) 2026-04-16 14:01:59 -04:00
shenlong 2a47c35eb7 chore: pump flutter to 3.41.6 (#27834)
* chore: pump flutter to 3.41.6

* more isar cleanup

* ignore experimental use for TableMigration

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-04-16 22:26:46 +05:30
Jason Rasmussen 6aadb7b5bd feat: dynamic languages (#27869)
Co-authored-by: xantin <github@xantin.be>
2026-04-16 12:37:37 -04:00
renovate[bot] 88bce52042 fix(deps): update dependency jose to v6 (#27862)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-04-16 18:36:07 +02:00
Timothy Dobras d046f16860 fix(oauth): normalize email claim to lowercase and trim before account lookup and registration (#26841)
* fix(oauth): normalize email claim to lowercase before account lookup and registration

* test(auth): add test for OAuth email case normalization

* chore: clean up

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2026-04-16 15:41:42 +00:00
renovate[bot] 88815a0345 chore(deps): update base-image to v202604141125 (major) (#27858)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 15:40:40 +00:00
Jason Rasmussen 57212f29bf chore: bump (#27866) 2026-04-16 11:32:33 -04:00
renovate[bot] 95fa8fbdab chore(deps): update machine-learning (#26970)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 11:32:08 -04:00
renovate[bot] 687b7cad6f chore(deps): update dependency terragrunt to v1 (#27860)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 10:36:15 -04:00
Steven Massaro ac2ebcee37 chore: improve randomness of /search/random endpoint (#27531) 2026-04-16 14:36:05 +00:00
bo0tzz 3356e81c85 fix!: do not allow insecure oauth requests by default (#27844)
* fix!: do not allow insecure oauth requests by default

* fix: format

* fix: make open-api

* fix: tests

* nit: casing

* chore: migration to allow insecure if current oauth uses http
2026-04-16 10:11:58 -04:00
renovate[bot] 9c642bd6fc chore(deps): update github-actions (#27857)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 15:29:48 +02:00
bo0tzz 9da0cb3cf4 chore: link to PR template in auto-close message (#27756) 2026-04-16 09:14:36 -04:00
renovate[bot] 4ff6cca4da fix(deps): update dependency pillow to >=12.2,<12.3 [security] (#27773)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 09:13:39 -04:00
renovate[bot] 2b7ae4981f chore(deps): update dependency pytest to v9.0.3 [security] (#27777)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 09:12:49 -04:00
renovate[bot] e63df4121a chore(deps): update dependency @types/node to ^24.12.2 (#27856)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 13:12:38 +00:00
renovate[bot] 03b4ab2935 fix(deps): update dependency simple-icons to v16 (#27855)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 13:07:35 +00:00
bo0tzz facd3bd331 fix: oauth issuerUrl validation (#27848) 2026-04-16 09:06:55 -04:00
renovate[bot] 20ddf2e7d2 fix(deps): update dependency nestjs-cls to v6 (#27852)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 13:45:42 +02:00
renovate[bot] 7f0025b3fc chore(deps): update dependency @types/nodemailer to v8 (#27851)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 13:28:23 +02:00
Mees Frensel 60f4dedb29 chore(web): small fixes for location picker modal (#27822) 2026-04-16 11:49:24 +02:00
Min Idzelis d5d2ebd9bf fix(web): close edit faces panel on Escape key press (#27519)
Move `showEditFaces` state to `assetViewerManager` so the edit faces
panel open/close state is globally accessible. Add Escape key handling
to `PersonSidePanel` that closes the assign-face sub-panel first, then
the edit faces panel. Guard the asset viewer's global Escape-to-close
action so it doesn't fire while either face panel is open.

Change-Id: I0c947fe0758aef0eac473f4cc72f6a3b6a6a6964
2026-04-15 20:22:20 -05:00
Yaros 37abbeba52 fix(mobile): readonly redirect when not logged in (#27728) 2026-04-15 20:20:08 -05:00
Min Idzelis 50557002b7 fix(web): stale adaptive image when original overlays preview (#27621)
AdaptiveImage stacks quality layers (thumbnail → preview → original) as they load. Without compositor layer promotion on the container, the browser could render a stale frame when the original-quality layer was overlaid on top of the preview-quality layer.

Add `will-change: transform` as a class on AdaptiveImage's root element so it gets a dedicated compositor layer from first paint. This also subsumes the imperative `style.willChange = 'transform'` that zoomImageAction was applying to the same element (the zoom target from photo-viewer is the AdaptiveImage root), so drop that now-redundant code.

Change-Id: Icd866a2bb5a5fce299c36404547fa0546a6a6964
2026-04-15 20:19:18 -05:00
Min Idzelis 4aa31d38bf fix(web): svelte regression - cancel video preview fetch when bind:this is cleared early (#27713)
fix(web): cancel video preview fetch when bind:this is cleared early

In Svelte 5.53.9, `bind:this` is now cleared earlier in the unmount
sequence ("better bind:this cleanup timing"). The video thumbnail's
$effect was relying on the old order to read the bound `player` element
and clear its `src` to abort the in-flight `/api/assets/{id}/video/playback`
range request — but the bind is now `undefined` by the time the effect
runs, so the cleanup is silently skipped. The detached <video> keeps
its src, and Firefox does not abort an in-flight media fetch when the
element is detached/GC'd. Long-lived 206 range requests then saturate
Firefox's 6-connection HTTP/1.1 per-host limit and freeze the timeline
(see #27585).

Capture the player reference inside the effect and tear down via the
effect cleanup return — Svelte runs the prior cleanup (with the captured
ref) before `bind:this` is cleared. Use the canonical
`pause() / removeAttribute('src') / load()` sequence which actually aborts
the fetch in Firefox, even on a detached element.

Fixes #27585

Change-Id: I4d9fba859955f5c54f603c345e61d4206a6a6964
2026-04-15 20:18:59 -05:00
Min Idzelis 3d8df74b43 refactor(web): turn thumbhash action into Thumbhash component (#27741)
refactor(web): extract thumbhash canvas into Thumbhash component

Change-Id: If78955bed48b6e690df398e5e2ae61fb6a6a6964
2026-04-15 20:18:49 -05:00
renovate[bot] 2ff9f95527 chore(deps): update dependency python-multipart to v0.0.26 [security] (#27838)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-15 19:06:27 -04:00
Jason Rasmussen a69eecf3bc chore!: remove without assets (#27835)
* chore!: remove without assets

* fix: linting and e2e

---------

Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-04-15 18:34:22 -04:00
Jason Rasmussen 4ffa26c969 feat: auth logout page (#27831)
* feat: auth logout page

* feat: skip login if already logged in
2026-04-15 16:33:52 -04:00
Jason Rasmussen ac06514db5 feat: album map markers endpoint (#27830) 2026-04-15 15:58:34 -04:00
Jason Rasmussen 792cb9148b chore!: rename API key schemas (#27828)
chore!: rename API schemas
2026-04-15 15:58:26 -04:00
Daniel Dietzler 8ee5d3039a chore!: remove deviceId and deviceAssetId (#27818)
chore: remove deviceId and deviceAssetId
2026-04-15 15:00:33 -04:00
Jason Rasmussen d410131312 chore!: remove old timeline sync endpoints (#27804) 2026-04-15 13:58:48 -04:00
bo0tzz 5334a6254a fix: make web build stage deterministic (#27823) 2026-04-15 19:31:23 +02:00
shenlong 79fccdbee0 refactor: yeet old timeline (#27666)
* refactor: yank old timeline

# Conflicts:
#	mobile/lib/presentation/pages/editing/drift_edit.page.dart
#	mobile/lib/providers/websocket.provider.dart
#	mobile/lib/routing/router.dart

* more cleanup

* remove native code

* chore: bump sqlite-data version

* remove old background tasks from BGTaskSchedulerPermittedIdentifiers

* rebase

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-04-15 23:00:27 +05:30
Brandon Wees 6dd6053222 feat: mobile editing (#25397)
* feat: mobile editing

fix: openapi patch

this sucks :pepehands:

chore: migrate some changes from the filtering PR

chore: color tweak

fix: hide edit button on server versions

chore: translation

* chore: code review changes

chore: code review

* sealed class

* const constant

* enum

* concurrent queries

* chore: major cleanup to use riverpod provider

* fix: aspect ratio selection

* chore: typesafe websocket event parsing

* fix: wrong disable state for save button

* chore: remove isCancelled shim

* chore: cleanup postframe callback usage

* chore: clean import

---------

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
2026-04-15 09:26:40 -05:00
Mees Frensel 8454cb2631 chore: exit open-api script on errors (#27815) 2026-04-15 10:09:51 -04:00
Daniel Dietzler 603fc7401f fix: redirect original (#27759) 2026-04-15 15:43:09 +02:00
Jason Rasmussen ed70e0febf chore: stop using legacy partner create endpoint (#27806) 2026-04-15 16:16:43 +05:30
Jason Rasmussen 5f5e3344d5 chore!: remove unused token response param (#27805) 2026-04-15 00:58:00 -04:00
Brandon Wees 6da2d3d587 chore!: remove getRandom api endpoint (#27780)
* chore!: remove getRandom api endpoint

* chore: sync openapi

* fix: test

* chore: more cleanup
2026-04-14 21:32:12 -04:00
Jason Rasmussen 41d2d84b21 chore!: remove deprecated env variables (#27802) 2026-04-14 21:30:31 -04:00
Jason Rasmussen 6ba17bb86f refactor!: remove my shared link dto (#27023)
refactor!: remove deprecated shared link apis
2026-04-14 20:58:02 -04:00
Jason Rasmussen e1a84d3ab6 refactor!: remove replace asset (#27022) 2026-04-14 20:21:05 -04:00
Timon 7d8f843be6 refactor!: migrate class-validator to zod (#26597) 2026-04-14 23:39:03 +02:00
OdinOxin 3753b7a4d1 feat: sort users alphabetically when adding to album (#27731) 2026-04-14 21:21:22 +02:00
Jonathan Jogenfors 84a1fb27ca feat(web): lazy load library and server statistics (#26406)
* feat: add offline library statistics

* fix comments

* feat: add offline library statistics

* fix comments

* fix Daniel's comments

* fix Daniels comment 2
2026-04-14 12:54:09 -04:00
Yaros 81780b0cc0 fix(web): add partner photo to album from multiselect (#27767)
* fix(web): add partner photo to album

* chore: fix formatting

* fix: run-job assets

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2026-04-14 16:48:39 +00:00
Min Idzelis 5e81a5a054 feat(web): remove delay from Skeleton (#27580)
Change-Id: I95a37f1af832c005a8f009d6f07df8ac6a6a6964
2026-04-14 12:47:37 -04:00
Miguel Raposo e4e2f586b5 fix(server): render storage template date/time tokens in UTC (#24350) (#26917) 2026-04-14 18:45:14 +02:00
OdinOxin a001adf14a feat: filter users on share (#27732)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-04-14 16:43:45 +00:00
Daniel Dietzler 136814540a fix: asset multi select download shortcut (#27784) 2026-04-14 12:29:55 -04:00
Jason Rasmussen fed5cc1ae1 feat: upgrade immich/ui (#27792) 2026-04-14 16:18:12 +00:00
Yaros 641ab51b80 fix(web): selection clearing on preview (#27702)
* fix(web): selection clearing on preview

* chore: remove unnecessary checks
2026-04-14 10:06:32 -05:00
Yaros 3b47ca1c37 fix(mobile): add keys for person tiles in search (#27689)
* fix(mobile): key for person tiles in search

* chore: add key to avatar

* chore: use simple personId

* chore: rename key in person page
2026-04-14 10:05:09 -05:00
Jason Rasmussen 8fb2c7755d feat: commands (#27546) 2026-04-14 09:34:46 -04:00
Jason Rasmussen 1ba0989e15 refactor: auth manager (#27638) 2026-04-14 08:49:24 -04:00
renovate[bot] daed3f0966 chore(deps): update dependency @sveltejs/kit to v2.57.1 [security] (#27762)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-14 11:50:25 +02:00
renovate[bot] 46d612ad8c chore(deps): update dependency nodemailer to v8.0.5 [security] (#27623)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-14 11:49:26 +02:00
renovate[bot] 513dead2c2 chore(deps): update dependency @nestjs/core to v11.1.18 [security] (#27544)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-14 09:49:03 +00:00
renovate[bot] ca006c1569 fix(deps): update typescript-projects (#27573)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-04-14 11:41:09 +02:00
renovate[bot] 4e8e8304fd fix(deps): update react-email monorepo (major) (#27572)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-14 11:40:58 +02:00
Nicolas-micuda-becker d377d2e145 fix(web): center images in RTL layouts (#27678) (#27753) 2026-04-13 13:29:35 -05:00
shenlong 9c9feddf7d refactor: folder page to use new models (#27657)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-04-13 12:19:44 -05:00
Andreas Heinz bfcf34d8b5 feat(web): persist state of file path information in details panel (#27770)
feat(enhancement): persist state of file path info in details panel
2026-04-13 12:18:34 -05:00
github-actions 95e57a24cb chore: version v2.7.5 2026-04-13 14:27:31 +00:00
Weblate (bot) eada662981 chore(web): update translations (#27589)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ar/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/da/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de_CH/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/eo/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/id/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ko/
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/nl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/th/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/uk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hant/
Translation: Immich/immich

Co-authored-by: Aliyss Snow <mangoworksbeta@gmail.com>
Co-authored-by: Bannawat Thongbai <kaji.kanlapat99@gmail.com>
Co-authored-by: Carlo Beltrame <weblate@pendantmusic.ch>
Co-authored-by: Dawnsink <dai@cosmopeace.com>
Co-authored-by: Edmundas <edmius@gmail.com>
Co-authored-by: Happy <59247878+happy2452354@users.noreply.github.com>
Co-authored-by: Jeppe Nellemann <jepnel@proton.me>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Link Notig <TestMailProtonWhyNot@protonmail.com>
Co-authored-by: Mees Frensel <meesfrensel@gmail.com>
Co-authored-by: Mārtiņš Bruņenieks <martinsb@gmail.com>
Co-authored-by: Osama <laptooxz@proton.me>
Co-authored-by: PPNplus <ppnplus@protonmail.com>
Co-authored-by: Tim Morley <weblate.3919org@timsk.org>
Co-authored-by: UDP <udp@users.noreply.hosted.weblate.org>
Co-authored-by: User 123456789 <user123456789@users.noreply.hosted.weblate.org>
Co-authored-by: Volodymyr Sakharov <savolodya@gmail.com>
Co-authored-by: Vykintas Vyšniauskas <vykintasv@gmail.com>
Co-authored-by: WellsTsai <dan50907@gmail.com>
Co-authored-by: brainheart95 <josephdm4d@gmail.com>
Co-authored-by: dvbthien <dvbthien@users.noreply.hosted.weblate.org>
Co-authored-by: 이찬웅 <lcw7527@gmail.com>
2026-04-13 14:25:01 +00:00
Zack Pollard 352f6ecc28 fix(server): add rate limit and deduplication to version check (#27747) 2026-04-13 12:35:46 +00:00
Yaros 6cd33de1bb fix(server): assets not shown if partner timeline disabled 2026-04-12 13:57:35 +02:00
Yaros ac3eea80d2 fix(server/web): shared albums in map sidebar 2026-04-12 13:36:49 +02:00
github-actions bee49cef02 chore: version v2.7.4 2026-04-10 16:32:26 +00:00
shenlong 6d0c6a4008 chore: pump cronet version (#27685)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2026-04-10 16:29:05 +00:00
Luis Nachtigall 8a975e5ea9 refactor(mobile): cleanup iOS image loading pipeline (#27672)
* refactor: replace DispatchQueue + DispatchSemaphore with OperationQueue for image processing

* implement RequestRegistry and UnfairLock for managing cancellable requests

* implement requests registry for local and remote image processing

* remove Cancellable protocol and cancel method from request registry

* refactor: introduce ImageRequest base class with unified cancellation and finish helpers

* refactor: add get method to RequestRegistry and streamline request removal in image processing

* add guard to cancel to prevent double onCancel calls

* fix duplicate code merge issue

* refactor(ios): enhance finish method to return callback status

* remove unfitting methods form ImageRequest.swift and fix memory issue

* revert bad merge

* refactor(ios): resolve cancellation issues

* refactor(ios): streamline image request completion handling

* add return statements

* refactor(ios): simplify image request cancellation and registry handling

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2026-04-10 10:56:35 -05:00
Luis Nachtigall d39e7da10d fix(mobile): fix flutter cache eviction on thumbnails (#27663)
* fix: add markFinished parameter to loadRequest and loadCodecRequest methods

* update loadRequest and loadCodecRequest methods to use isFinal

* Apply suggestions from code review

Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>

* remove redundant check

* fix: ensure isFinished is set correctly during cache eviction

* formatting

---------

Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>
2026-04-10 10:28:55 -05:00
Daniel Dietzler bc400d68ac chore: move .tsbuildinfo file to dist folder (#27682) 2026-04-10 16:02:25 +02:00
renovate[bot] d7f038ec60 chore(deps): update dependency eslint-plugin-unicorn to v64 (#27575)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-04-10 10:23:42 +00:00
Mees Frensel 26957f37ce fix(server): hide original filename when not showing metadata (#27581) 2026-04-10 12:07:18 +02:00
github-actions 3254d31cd2 chore: version v2.7.3 2026-04-09 17:51:40 +00:00
Jason Rasmussen 7b269d1638 fix: ssr open graph tags (#27639)
fix: SSR open graph tags
2026-04-09 12:16:41 -04:00
Luis Nachtigall b5bed02300 fix(mobile): get provider refs before async gaps in backup page (#27597)
* fix(mobile): get provider refs before async gaps in backup page

* fix(mobile): use previously created provider refs in start backup function
2026-04-08 20:55:53 -05:00
Zack Pollard 5553910236 fix(web): don't cache empty search results for people search (#27632) 2026-04-09 02:33:04 +01:00
Zack Pollard 8d67c1f820 fix(server): people search not showing for 3 or less characters (#27629)
Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>
2026-04-09 01:56:07 +01:00
Matthew Momjian ed0ec30917 fix(docs): updated docker deprecation link (#27633)
new link
2026-04-08 20:33:11 -04:00
Luis Nachtigall 2b0f6c9202 fix(mobile): improve image load cancellation handling (#27624)
fix(image): improve image load cancellation handling
2026-04-08 17:23:42 -04:00
André Erasmus 55ab8c65b6 fix(server): avoid false restore failures on large database imports (#27420)
* fix(server): increase restore health check timeout and reject with Error

* chore: clean up

---------

Co-authored-by: André Erasmus <25480506+NoBadDays@users.noreply.github.com>
Co-authored-by: Jason Rasmussen <jason@rasm.me>
2026-04-08 16:03:41 -04:00
Cullen Jennings 781d568f29 fix(docs): typo 'Start rating' to 'Star rating' (#27606) 2026-04-08 18:25:45 +00:00
Zack Pollard 6a361dae72 fix(server): use randomized cron for version check scheduling (#27626)
Also removes unnecessary rate limit
2026-04-08 19:15:38 +01:00
renovate[bot] 64766c8c06 chore(deps): update github-actions (#27560)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-08 17:32:54 +02:00
github-actions 6a63e814a5 chore: version v2.7.2 2026-04-07 20:58:38 +00:00
Jason Rasmussen 6441c3b77c fix: server build (#27599) 2026-04-07 20:53:04 +00:00
github-actions b03a649e74 chore: version v2.7.1 2026-04-07 20:22:28 +00:00
Mert 2903b2653b fix(server): library import batch size (#27595)
* lower batch size

* update test
2026-04-07 15:58:03 -04:00
Mert 9ba9a22c40 fix(ml): downgrade numpy (#27591)
downgrade numpy
2026-04-07 15:57:42 -04:00
bo0tzz f1882c2926 fix: csp quotes (#27592) 2026-04-07 15:54:30 -04:00
Daniel Dietzler 4278789083 chore: git ignore tsBuildInfo (#27594) 2026-04-07 15:53:10 -04:00
renovate[bot] 921c8a8de3 chore(deps): update dependency typescript to v6 (#27577)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-04-07 17:15:55 +02:00
github-actions afec61addc chore: version v2.7.0 2026-04-07 15:08:18 +00:00
Weblate (bot) a1a03efbcd chore(web): update translations (#27483)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ar/
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/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/fi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/gl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ko/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/lt/
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/nn/
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/ru/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/th/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/tr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hans/
Translation: Immich/immich

Co-authored-by: Dawnsink <dai@cosmopeace.com>
Co-authored-by: DevServs <bonov@mail.ru>
Co-authored-by: Francesco Fiorentino <gallgricela+trotter@gmail.com>
Co-authored-by: Frank Paul Silye <frankps@gmail.com>
Co-authored-by: Gianni De Wachter <gianni.dewachter@gmail.com>
Co-authored-by: HackingAll <hacking.all.YT@gmail.com>
Co-authored-by: Haru Ijima <haruijimakun@gmail.com>
Co-authored-by: Hurricane_32 <rodrigorimo@hotmail.com>
Co-authored-by: Jarek Iwanus <jiwanus@proton.me>
Co-authored-by: Junghyuk Kwon <kwon@junghy.uk>
Co-authored-by: Krastyo Krastev <roshavi4ak@gmail.com>
Co-authored-by: Luis Peregrina <luis.a.peregrina@gmail.com>
Co-authored-by: MarcSerraPeralta <marcserraperalta@gmail.com>
Co-authored-by: Matjaž T. <matjaz@moj-svet.si>
Co-authored-by: Petri Hämäläinen <petri.hamalainen@mailbox.org>
Co-authored-by: Simen Haugen <simen00@gmail.com>
Co-authored-by: Sylvain Pichon <service@spichon.fr>
Co-authored-by: TA <tobi@warsnich.de>
Co-authored-by: TV Box <realceday.tvbox@gmail.com>
Co-authored-by: Veerasak Kritsanapraphan <veerasak.kritsanapraphan@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: chamdim <chamdim@protonmail.com>
Co-authored-by: miksuk28 <mikhail@sukhanik.no>
Co-authored-by: muziqaz <muziqaz@users.noreply.hosted.weblate.org>
Co-authored-by: nanai <ivagamerytmc@gmail.com>
Co-authored-by: naxxerd <top.gear2951@dsme.no>
Co-authored-by: ray ra <verdonsky22@gmail.com>
Co-authored-by: 张建涛 <app521@gmail.com>
Co-authored-by: 안세훈 <on9686@gmail.com>
2026-04-07 15:05:52 +00:00
Dominik Szymański 1d0e5cf18d fix: allow bots to access /s/ urls (#27579)
#27548 Add Allow directive for custom share links social media preview
2026-04-07 09:22:53 -05:00
Min Idzelis de9ec95db1 fix(web): handle unhandled promise rejection in CancellableTask (#27553)
When a concurrent caller awaits `this.complete` inside `execute()` and
`cancel()` is called, the promise rejects with `undefined` outside of any
try/catch, causing "Uncaught (in promise) undefined" console spam during
rapid timeline scrolling.

- Wrap the `await this.complete` path in try/catch, returning 'CANCELED'
- Guard the `finally` block to only null `cancelToken` if it still belongs
  to this call, preventing a race condition with `cancel()` to `init()`

Change-Id: I65764dd664eb408433fc6e5fc2be4df56a6a6964
2026-04-07 09:22:29 -05:00
renovate[bot] 7f784952eb chore(deps): update dependency rollup-plugin-visualizer to v7 (#27576)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-07 11:01:23 +00:00
renovate[bot] 3d6c7ba353 chore(deps): update dependency @types/supertest to v7 (#27574)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-07 10:57:45 +00:00
renovate[bot] 3be97db118 fix(deps): update react monorepo to v19 (major) (#27571)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-07 12:53:33 +02:00
renovate[bot] 8f3a99ffbc chore(deps): update dependency eslint-plugin-compat to v7 (#27570)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-07 10:52:25 +00:00
renovate[bot] e6d114af10 chore(deps): update dependency terragrunt to v0.99.5 (#27567)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-07 12:37:29 +02:00
renovate[bot] 4e28811f09 chore(deps): update prom/prometheus docker digest to dda13e2 (#27566)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-07 11:49:05 +02:00
renovate[bot] 4987032e62 chore(deps): update docker.io/valkey/valkey:9 docker digest to 3b55fba (#27559)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-07 11:48:06 +02:00
renovate[bot] 572bad8ede chore(deps): update dependency vite to v8.0.5 [security] (#27543)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-06 18:20:10 +00:00
1656 changed files with 53481 additions and 79111 deletions
+6
View File
@@ -6,6 +6,12 @@ mobile/openapi/**/*.dart linguist-generated=true
mobile/lib/**/*.g.dart -diff -merge mobile/lib/**/*.g.dart -diff -merge
mobile/lib/**/*.g.dart linguist-generated=true mobile/lib/**/*.g.dart linguist-generated=true
mobile/android/**/*.g.kt -diff -merge
mobile/android/**/*.g.kt linguist-generated=true
mobile/ios/**/*.g.swift -diff -merge
mobile/ios/**/*.g.swift linguist-generated=true
mobile/lib/**/*.drift.dart -diff -merge mobile/lib/**/*.drift.dart -diff -merge
mobile/lib/**/*.drift.dart linguist-generated=true mobile/lib/**/*.drift.dart linguist-generated=true
+1 -1
View File
@@ -1 +1 @@
24.14.1 24.15.0
+1 -1
View File
@@ -51,7 +51,7 @@ jobs:
run: | run: |
gh api graphql \ gh api graphql \
-f prId="$NODE_ID" \ -f prId="$NODE_ID" \
-f body="This PR has been automatically closed as the description doesn't follow our template. After you edit it to match the template, the PR will automatically be reopened." \ -f body="This PR has been automatically closed as the description doesn't follow [our template](https://github.com/immich-app/immich/blob/main/.github/pull_request_template.md). After you edit it to match the template, the PR will automatically be reopened." \
-f query=' -f query='
mutation CommentAndClosePR($prId: ID!, $body: String!) { mutation CommentAndClosePR($prId: ID!, $body: String!) {
addComment(input: { addComment(input: {
+6 -6
View File
@@ -103,7 +103,7 @@ jobs:
- name: Restore Gradle Cache - name: Restore Gradle Cache
id: cache-gradle-restore id: cache-gradle-restore
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@@ -121,7 +121,7 @@ jobs:
cache: true cache: true
- name: Setup Android SDK - name: Setup Android SDK
uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3.2.2 uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
with: with:
packages: '' packages: ''
@@ -153,14 +153,14 @@ jobs:
fi fi
- name: Publish Android Artifact - name: Publish Android Artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: release-apk-signed name: release-apk-signed
path: mobile/build/app/outputs/flutter-apk/*.apk path: mobile/build/app/outputs/flutter-apk/*.apk
- name: Save Gradle Cache - name: Save Gradle Cache
id: cache-gradle-save id: cache-gradle-save
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
if: github.ref == 'refs/heads/main' if: github.ref == 'refs/heads/main'
with: with:
path: | path: |
@@ -210,7 +210,7 @@ jobs:
working-directory: ./mobile working-directory: ./mobile
- name: Setup Ruby - name: Setup Ruby
uses: ruby/setup-ruby@c515ec17f69368147deb311832da000dd229d338 # v1.297.0 uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0
with: with:
ruby-version: '3.3' ruby-version: '3.3'
bundler-cache: true bundler-cache: true
@@ -291,7 +291,7 @@ jobs:
security delete-keychain build.keychain || true security delete-keychain build.keychain || true
- name: Upload IPA artifact - name: Upload IPA artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: ios-release-ipa name: ios-release-ipa
path: mobile/ios/Runner.ipa path: mobile/ios/Runner.ipa
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Check for breaking API changes - name: Check for breaking API changes
uses: oasdiff/oasdiff-action/breaking@1f38ea5ea0b4a2e4e49901c3bcdf4386a05e9ea1 # v0.0.37 uses: oasdiff/oasdiff-action/breaking@f8cb9308b42121e793f835bd14c0b8090420430c # v0.0.39
with: with:
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
revision: open-api/immich-openapi-specs.json revision: open-api/immich-openapi-specs.json
+2 -2
View File
@@ -89,7 +89,7 @@ jobs:
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
if: ${{ !github.event.pull_request.head.repo.fork }} if: ${{ !github.event.pull_request.head.repo.fork }}
with: with:
registry: ghcr.io registry: ghcr.io
@@ -115,7 +115,7 @@ jobs:
type=raw,value=latest,enable=${{ github.event_name == 'release' }} type=raw,value=latest,enable=${{ github.event_name == 'release' }}
- name: Build and push image - name: Build and push image
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with: with:
file: cli/Dockerfile file: cli/Dockerfile
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
needs: [get_body, should_run] needs: [get_body, should_run]
if: ${{ needs.should_run.outputs.should_run == 'true' }} if: ${{ needs.should_run.outputs.should_run == 'true' }}
container: container:
image: ghcr.io/immich-app/mdq:main@sha256:df7188ba88abb0800d73cc97d3633280f0c0c3d4c441d678225067bf154150fb image: ghcr.io/immich-app/mdq:main@sha256:557cca601891b8b7d78b940071d35aaf7aaeb9b327d19b22cf282118edbc5272
outputs: outputs:
checked: ${{ steps.get_checkbox.outputs.checked }} checked: ${{ steps.get_checkbox.outputs.checked }}
steps: steps:
+3 -3
View File
@@ -57,7 +57,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -70,7 +70,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
# ️ Command-line programs to run using the OS shell. # ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -83,6 +83,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh # ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with: with:
category: '/language:${{matrix.language}}' category: '/language:${{matrix.language}}'
+2 -2
View File
@@ -60,7 +60,7 @@ jobs:
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn'] suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
steps: steps:
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@@ -90,7 +90,7 @@ jobs:
suffix: [''] suffix: ['']
steps: steps:
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
+1 -1
View File
@@ -86,7 +86,7 @@ jobs:
run: pnpm build run: pnpm build
- name: Upload build output - name: Upload build output
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: docs-build-output name: docs-build-output
path: docs/build/ path: docs/build/
+5 -5
View File
@@ -29,7 +29,7 @@ jobs:
run: echo 'The triggering workflow did not succeed' && exit 1 run: echo 'The triggering workflow did not succeed' && exit 1
- name: Get artifact - name: Get artifact
id: get-artifact id: get-artifact
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with: with:
github-token: ${{ steps.token.outputs.token }} github-token: ${{ steps.token.outputs.token }}
script: | script: |
@@ -48,7 +48,7 @@ jobs:
return { found: true, id: matchArtifact.id }; return { found: true, id: matchArtifact.id };
- name: Determine deploy parameters - name: Determine deploy parameters
id: parameters id: parameters
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env: env:
HEAD_SHA: ${{ github.event.workflow_run.head_sha }} HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
with: with:
@@ -135,7 +135,7 @@ jobs:
- name: Load parameters - name: Load parameters
id: parameters id: parameters
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env: env:
PARAM_JSON: ${{ needs.checks.outputs.parameters }} PARAM_JSON: ${{ needs.checks.outputs.parameters }}
with: with:
@@ -147,7 +147,7 @@ jobs:
core.setOutput("shouldDeploy", parameters.shouldDeploy); core.setOutput("shouldDeploy", parameters.shouldDeploy);
- name: Download artifact - name: Download artifact
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env: env:
ARTIFACT_JSON: ${{ needs.checks.outputs.artifact }} ARTIFACT_JSON: ${{ needs.checks.outputs.artifact }}
with: with:
@@ -211,7 +211,7 @@ jobs:
run: 'mise run //deployment:tf apply' run: 'mise run //deployment:tf apply'
- name: Comment - name: Comment
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0 uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0
if: ${{ steps.parameters.outputs.event == 'pr' }} if: ${{ steps.parameters.outputs.event == 'pr' }}
with: with:
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
run: 'mise run //deployment:tf destroy -- -refresh=false' run: 'mise run //deployment:tf destroy -- -refresh=false'
- name: Comment - name: Comment
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0 uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0
with: with:
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
number: ${{ github.event.number }} number: ${{ github.event.number }}
+4 -4
View File
@@ -16,7 +16,7 @@ jobs:
steps: steps:
- name: Generate a token - name: Generate a token
id: generate-token id: generate-token
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -29,7 +29,7 @@ jobs:
persist-credentials: true persist-credentials: true
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@08c4be7e2e672a47d11bd04269e27e5f3e8529cb # v6.0.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
@@ -42,13 +42,13 @@ jobs:
run: pnpm --recursive install && pnpm run --recursive --if-present --parallel format:fix run: pnpm --recursive install && pnpm run --recursive --if-present --parallel format:fix
- name: Commit and push - name: Commit and push
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4 uses: EndBug/add-and-commit@290ea2c423ad77ca9c62ae0f5b224379612c0321 # v10.0.0
with: with:
default_author: github_actions default_author: github_actions
message: 'chore: fix formatting' message: 'chore: fix formatting'
- name: Remove label - name: Remove label
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
if: always() if: always()
with: with:
github-token: ${{ steps.generate-token.outputs.token }} github-token: ${{ steps.generate-token.outputs.token }}
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
- name: Generate a token - name: Generate a token
id: generate_token id: generate_token
if: ${{ inputs.skip != true }} if: ${{ inputs.skip != true }}
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
+6 -6
View File
@@ -50,7 +50,7 @@ jobs:
steps: steps:
- name: Generate a token - name: Generate a token
id: generate-token id: generate-token
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -63,10 +63,10 @@ jobs:
ref: main ref: main
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 uses: pnpm/action-setup@08c4be7e2e672a47d11bd04269e27e5f3e8529cb # v6.0.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
@@ -86,7 +86,7 @@ jobs:
- name: Commit and tag - name: Commit and tag
id: push-tag id: push-tag
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4 uses: EndBug/add-and-commit@290ea2c423ad77ca9c62ae0f5b224379612c0321 # v10.0.0
with: with:
default_author: github_actions default_author: github_actions
message: 'chore: version ${{ steps.output.outputs.version }}' message: 'chore: version ${{ steps.output.outputs.version }}'
@@ -124,7 +124,7 @@ jobs:
steps: steps:
- name: Generate a token - name: Generate a token
id: generate-token id: generate-token
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -142,7 +142,7 @@ jobs:
github-token: ${{ steps.generate-token.outputs.token }} github-token: ${{ steps.generate-token.outputs.token }}
- name: Create draft release - name: Create draft release
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
with: with:
draft: true draft: true
tag_name: ${{ needs.bump_version.outputs.version }} tag_name: ${{ needs.bump_version.outputs.version }}
+4 -4
View File
@@ -19,7 +19,7 @@ jobs:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- uses: mshick/add-pr-comment@ffd016c7e151d97d69d21a843022fd4cd5b96fe5 # v3.9.0 - uses: mshick/add-pr-comment@64b8e914979889d746c99dea15a76e77ef64580a # v3.10.0
with: with:
github-token: ${{ steps.token.outputs.token }} github-token: ${{ steps.token.outputs.token }}
message-id: 'preview-status' message-id: 'preview-status'
@@ -37,7 +37,7 @@ jobs:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with: with:
github-token: ${{ steps.token.outputs.token }} github-token: ${{ steps.token.outputs.token }}
script: | script: |
@@ -48,14 +48,14 @@ jobs:
name: 'preview' name: 'preview'
}) })
- uses: mshick/add-pr-comment@ffd016c7e151d97d69d21a843022fd4cd5b96fe5 # v3.9.0 - uses: mshick/add-pr-comment@64b8e914979889d746c99dea15a76e77ef64580a # v3.10.0
if: ${{ github.event.pull_request.head.repo.fork }} if: ${{ github.event.pull_request.head.repo.fork }}
with: with:
github-token: ${{ steps.token.outputs.token }} github-token: ${{ steps.token.outputs.token }}
message-id: 'preview-status' message-id: 'preview-status'
message: 'PRs from forks cannot have preview environments.' message: 'PRs from forks cannot have preview environments.'
- uses: mshick/add-pr-comment@ffd016c7e151d97d69d21a843022fd4cd5b96fe5 # v3.9.0 - uses: mshick/add-pr-comment@64b8e914979889d746c99dea15a76e77ef64580a # v3.10.0
if: ${{ !github.event.pull_request.head.repo.fork }} if: ${{ !github.event.pull_request.head.repo.fork }}
with: with:
github-token: ${{ steps.token.outputs.token }} github-token: ${{ steps.token.outputs.token }}
+6 -6
View File
@@ -464,7 +464,7 @@ jobs:
run: docker compose logs --no-color > docker-compose-logs.txt run: docker compose logs --no-color > docker-compose-logs.txt
working-directory: ./e2e working-directory: ./e2e
- name: Archive Docker logs - name: Archive Docker logs
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always() if: always()
with: with:
name: e2e-server-docker-logs-${{ matrix.runner }} name: e2e-server-docker-logs-${{ matrix.runner }}
@@ -522,7 +522,7 @@ jobs:
run: pnpm test:web run: pnpm test:web
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Archive e2e test (web) results - name: Archive e2e test (web) results
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: success() || failure() if: success() || failure()
with: with:
name: e2e-web-test-results-${{ matrix.runner }} name: e2e-web-test-results-${{ matrix.runner }}
@@ -533,7 +533,7 @@ jobs:
run: pnpm test:web:ui run: pnpm test:web:ui
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Archive ui test (web) results - name: Archive ui test (web) results
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: success() || failure() if: success() || failure()
with: with:
name: e2e-ui-test-results-${{ matrix.runner }} name: e2e-ui-test-results-${{ matrix.runner }}
@@ -544,7 +544,7 @@ jobs:
run: pnpm test:web:maintenance run: pnpm test:web:maintenance
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Archive maintenance tests (web) results - name: Archive maintenance tests (web) results
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: success() || failure() if: success() || failure()
with: with:
name: e2e-maintenance-isolated-test-results-${{ matrix.runner }} name: e2e-maintenance-isolated-test-results-${{ matrix.runner }}
@@ -554,7 +554,7 @@ jobs:
run: docker compose logs --no-color > docker-compose-logs.txt run: docker compose logs --no-color > docker-compose-logs.txt
working-directory: ./e2e working-directory: ./e2e
- name: Archive Docker logs - name: Archive Docker logs
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always() if: always()
with: with:
name: e2e-web-docker-logs-${{ matrix.runner }} name: e2e-web-docker-logs-${{ matrix.runner }}
@@ -620,7 +620,7 @@ jobs:
persist-credentials: false persist-credentials: false
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with: with:
python-version: 3.11 python-version: 3.11
- name: Install dependencies - name: Install dependencies
+2
View File
@@ -28,3 +28,5 @@ vite.config.js.timestamp-*
.pnpm-store .pnpm-store
.devcontainer/library .devcontainer/library
.devcontainer/.env* .devcontainer/.env*
*.tsbuildinfo
*.tsbuildInfo
-3
View File
@@ -1,6 +1,3 @@
[submodule "mobile/.isar"]
path = mobile/.isar
url = https://github.com/isar/isar
[submodule "e2e/test-assets"] [submodule "e2e/test-assets"]
path = e2e/test-assets path = e2e/test-assets
url = https://github.com/immich-app/test-assets url = https://github.com/immich-app/test-assets
-12
View File
@@ -13,10 +13,6 @@
"editor.wordBasedSuggestions": "off" "editor.wordBasedSuggestions": "off"
}, },
"[javascript]": { "[javascript]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true
}, },
@@ -29,18 +25,10 @@
"editor.formatOnSave": true "editor.formatOnSave": true
}, },
"[svelte]": { "[svelte]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
"editor.defaultFormatter": "svelte.svelte-vscode", "editor.defaultFormatter": "svelte.svelte-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true
}, },
"[typescript]": { "[typescript]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true
}, },
+1 -1
View File
@@ -1 +1 @@
24.14.1 24.15.0
+6 -6
View File
@@ -1,6 +1,6 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.6.3", "version": "2.7.5",
"description": "Command Line Interface (CLI) for Immich", "description": "Command Line Interface (CLI) for Immich",
"type": "module", "type": "module",
"exports": "./dist/index.js", "exports": "./dist/index.js",
@@ -20,7 +20,7 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/micromatch": "^4.0.9", "@types/micromatch": "^4.0.9",
"@types/mock-fs": "^4.13.1", "@types/mock-fs": "^4.13.1",
"@types/node": "^24.12.0", "@types/node": "^24.12.2",
"@vitest/coverage-v8": "^4.0.0", "@vitest/coverage-v8": "^4.0.0",
"byte-size": "^9.0.0", "byte-size": "^9.0.0",
"cli-progress": "^3.12.0", "cli-progress": "^3.12.0",
@@ -28,13 +28,13 @@
"eslint": "^10.0.0", "eslint": "^10.0.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^63.0.0", "eslint-plugin-unicorn": "^64.0.0",
"globals": "^17.0.0", "globals": "^17.0.0",
"mock-fs": "^5.2.0", "mock-fs": "^5.2.0",
"prettier": "^3.7.4", "prettier": "^3.7.4",
"prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-organize-imports": "^4.0.0",
"typescript": "^5.3.3", "typescript": "^6.0.0",
"typescript-eslint": "^8.28.0", "typescript-eslint": "^8.58.0",
"vite": "^8.0.0", "vite": "^8.0.0",
"vitest": "^4.0.0", "vitest": "^4.0.0",
"vitest-fetch-mock": "^0.4.0", "vitest-fetch-mock": "^0.4.0",
@@ -68,6 +68,6 @@
"micromatch": "^4.0.8" "micromatch": "^4.0.8"
}, },
"volta": { "volta": {
"node": "24.14.1" "node": "24.15.0"
} }
} }
+6 -6
View File
@@ -4,7 +4,7 @@ import path from 'node:path';
import { setTimeout as sleep } from 'node:timers/promises'; import { setTimeout as sleep } from 'node:timers/promises';
import { describe, expect, it, MockedFunction, vi } from 'vitest'; import { describe, expect, it, MockedFunction, vi } from 'vitest';
import { Action, checkBulkUpload, defaults, getSupportedMediaTypes, Reason } from '@immich/sdk'; import { AssetRejectReason, AssetUploadAction, checkBulkUpload, defaults, getSupportedMediaTypes } from '@immich/sdk';
import createFetchMock from 'vitest-fetch-mock'; import createFetchMock from 'vitest-fetch-mock';
import { import {
@@ -120,7 +120,7 @@ describe('checkForDuplicates', () => {
vi.mocked(checkBulkUpload).mockResolvedValue({ vi.mocked(checkBulkUpload).mockResolvedValue({
results: [ results: [
{ {
action: Action.Accept, action: AssetUploadAction.Accept,
id: testFilePath, id: testFilePath,
}, },
], ],
@@ -144,10 +144,10 @@ describe('checkForDuplicates', () => {
vi.mocked(checkBulkUpload).mockResolvedValue({ vi.mocked(checkBulkUpload).mockResolvedValue({
results: [ results: [
{ {
action: Action.Reject, action: AssetUploadAction.Reject,
id: testFilePath, id: testFilePath,
assetId: 'fc5621b1-86f6-44a1-9905-403e607df9f5', assetId: 'fc5621b1-86f6-44a1-9905-403e607df9f5',
reason: Reason.Duplicate, reason: AssetRejectReason.Duplicate,
}, },
], ],
}); });
@@ -167,7 +167,7 @@ describe('checkForDuplicates', () => {
vi.mocked(checkBulkUpload).mockResolvedValue({ vi.mocked(checkBulkUpload).mockResolvedValue({
results: [ results: [
{ {
action: Action.Accept, action: AssetUploadAction.Accept,
id: testFilePath, id: testFilePath,
}, },
], ],
@@ -187,7 +187,7 @@ describe('checkForDuplicates', () => {
mocked.mockResolvedValue({ mocked.mockResolvedValue({
results: [ results: [
{ {
action: Action.Accept, action: AssetUploadAction.Accept,
id: testFilePath, id: testFilePath,
}, },
], ],
+2 -4
View File
@@ -1,9 +1,9 @@
import { import {
Action,
AssetBulkUploadCheckItem, AssetBulkUploadCheckItem,
AssetBulkUploadCheckResult, AssetBulkUploadCheckResult,
AssetMediaResponseDto, AssetMediaResponseDto,
AssetMediaStatus, AssetMediaStatus,
AssetUploadAction,
Permission, Permission,
addAssetsToAlbum, addAssetsToAlbum,
checkBulkUpload, checkBulkUpload,
@@ -234,7 +234,7 @@ export const checkForDuplicates = async (files: string[], { concurrency, skipHas
const results = response.results as AssetBulkUploadCheckResults; const results = response.results as AssetBulkUploadCheckResults;
for (const { id: filepath, assetId, action } of results) { for (const { id: filepath, assetId, action } of results) {
if (action === Action.Accept) { if (action === AssetUploadAction.Accept) {
newFiles.push(filepath); newFiles.push(filepath);
} else { } else {
// rejects are always duplicates // rejects are always duplicates
@@ -404,8 +404,6 @@ const uploadFile = async (input: string, stats: Stats): Promise<AssetMediaRespon
const { baseUrl, headers } = defaults; const { baseUrl, headers } = defaults;
const formData = new FormData(); const formData = new FormData();
formData.append('deviceAssetId', `${basename(input)}-${stats.size}`.replaceAll(/\s+/g, ''));
formData.append('deviceId', 'CLI');
formData.append('fileCreatedAt', stats.mtime.toISOString()); formData.append('fileCreatedAt', stats.mtime.toISOString());
formData.append('fileModifiedAt', stats.mtime.toISOString()); formData.append('fileModifiedAt', stats.mtime.toISOString());
formData.append('fileSize', String(stats.size)); formData.append('fileSize', String(stats.size));
+6 -2
View File
@@ -15,8 +15,12 @@
"incremental": true, "incremental": true,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": true,
"baseUrl": "./", "rootDir": "./src",
"paths": {
"src/*": ["./src/*"],
},
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"exclude": ["dist", "node_modules"] "exclude": ["dist", "node_modules", "vite.config.ts"]
} }
+2 -2
View File
@@ -1,6 +1,6 @@
[tools] [tools]
terragrunt = "0.99.4" terragrunt = "1.0.2"
opentofu = "1.11.5" opentofu = "1.11.6"
[tasks."tg:fmt"] [tasks."tg:fmt"]
run = "terragrunt hclfmt" run = "terragrunt hclfmt"
+2 -1
View File
@@ -20,6 +20,7 @@ services:
- /tmp - /tmp
volumes: volumes:
- ..:/usr/src/app - ..:/usr/src/app
# - ../../ui:/usr/src/ui
- pnpm_cache:/buildcache/pnpm_cache - pnpm_cache:/buildcache/pnpm_cache
- server_node_modules:/usr/src/app/server/node_modules - server_node_modules:/usr/src/app/server/node_modules
- web_node_modules:/usr/src/app/web/node_modules - web_node_modules:/usr/src/app/web/node_modules
@@ -156,7 +157,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:3eeb09785cd61ec8e3be35f8804c8892080f3ca21934d628abc24ee4ed1698f6 image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
+2 -2
View File
@@ -56,7 +56,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:3eeb09785cd61ec8e3be35f8804c8892080f3ca21934d628abc24ee4ed1698f6 image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
restart: always restart: always
@@ -85,7 +85,7 @@ services:
container_name: immich_prometheus container_name: immich_prometheus
ports: ports:
- 9090:9090 - 9090:9090
image: prom/prometheus@sha256:4a61322ac1103a0e3aea2a61ef1718422a48fa046441f299d71e660a3bc71ae9 image: prom/prometheus@sha256:e4254400b85610324913f0dc4acf92603d9984e7519414c5a12811aa6146acc3
volumes: volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml - ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus - prometheus-data:/prometheus
+1 -1
View File
@@ -61,7 +61,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:3eeb09785cd61ec8e3be35f8804c8892080f3ca21934d628abc24ee4ed1698f6 image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9
user: '1000:1000' user: '1000:1000'
security_opt: security_opt:
- no-new-privileges:true - no-new-privileges:true
+1 -1
View File
@@ -49,7 +49,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:3eeb09785cd61ec8e3be35f8804c8892080f3ca21934d628abc24ee4ed1698f6 image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
restart: always restart: always
+1 -1
View File
@@ -1 +1 @@
24.14.1 24.15.0
@@ -210,7 +210,7 @@ The provided restore process ensures your database is never in a broken state by
## Filesystem ## Filesystem
Immich stores two types of content in the filesystem: (a) original, unmodified assets (photos and videos), and (b) generated content. We recommend backing up the entire contents of `UPLOAD_LOCATION`, but only the original content is critical, which is stored in the following folders: Immich does not handle filesystem backups for you. You have to arrange these yourself! Immich stores two types of content in the filesystem: (a) original, unmodified assets (photos and videos), and (b) generated content. We recommend backing up the entire contents of `UPLOAD_LOCATION`, but only the original content is critical, which is stored in the following folders:
1. `UPLOAD_LOCATION/library` 1. `UPLOAD_LOCATION/library`
2. `UPLOAD_LOCATION/upload` 2. `UPLOAD_LOCATION/upload`
+7
View File
@@ -50,6 +50,10 @@ Before enabling OAuth in Immich, a new client application needs to be configured
- `https://immich.example.com/auth/login` - `https://immich.example.com/auth/login`
- `https://immich.example.com/user-settings` - `https://immich.example.com/user-settings`
3. Configure Backchannel logout URL
If the authentication server supports it, the **Backchannel logout URL** can be specified, and it is of the form: `http://DOMAIN:PORT/api/oauth/backchannel-logout`.
## Enable OAuth ## Enable OAuth
Once you have a new OAuth client application configured, Immich can be configured using the Administration Settings page, available on the web (Administration -> Settings). Once you have a new OAuth client application configured, Immich can be configured using the Administration Settings page, available on the web (Administration -> Settings).
@@ -63,6 +67,8 @@ Once you have a new OAuth client application configured, Immich can be configure
| `scope` | string | openid email profile | Full list of scopes to send with the request (space delimited) | | `scope` | string | openid email profile | Full list of scopes to send with the request (space delimited) |
| `id_token_signed_response_alg` | string | RS256 | The algorithm used to sign the id token (examples: RS256, HS256) | | `id_token_signed_response_alg` | string | RS256 | The algorithm used to sign the id token (examples: RS256, HS256) |
| `userinfo_signed_response_alg` | string | none | The algorithm used to sign the userinfo response (examples: RS256, HS256) | | `userinfo_signed_response_alg` | string | none | The algorithm used to sign the userinfo response (examples: RS256, HS256) |
| `prompt` | string | (empty) | Prompt parameter for authorization url (examples: select_account, login, consent) |
| `end_session_endpoint` | URL | (empty) | Http(s) alternative end session endpoint (logout URI) |
| Request timeout | string | 30,000 (30 seconds) | Number of milliseconds to wait for http requests to complete before giving up | | Request timeout | string | 30,000 (30 seconds) | Number of milliseconds to wait for http requests to complete before giving up |
| Storage Label Claim | string | preferred_username | Claim mapping for the user's storage label**¹** | | Storage Label Claim | string | preferred_username | Claim mapping for the user's storage label**¹** |
| Role Claim | string | immich_role | Claim mapping for the user's role. (should return "user" or "admin")**¹** | | Role Claim | string | immich_role | Claim mapping for the user's role. (should return "user" or "admin")**¹** |
@@ -181,6 +187,7 @@ Configuration of OAuth in Immich System Settings
| Scope | openid email profile immich_scope | | Scope | openid email profile immich_scope |
| ID Token Signed Response Algorithm | RS256 | | ID Token Signed Response Algorithm | RS256 |
| Userinfo Signed Response Algorithm | RS256 | | Userinfo Signed Response Algorithm | RS256 |
| End Session Endpoint | https://auth.example.com/logout?rd=https://immich.example.com/ |
| Storage Label Claim | uid | | Storage Label Claim | uid |
| Storage Quota Claim | immich_quota | | Storage Quota Claim | immich_quota |
| Default Storage Quota (GiB) | 0 (empty for unlimited quota) | | Default Storage Quota (GiB) | 0 (empty for unlimited quota) |
+3 -3
View File
@@ -80,9 +80,9 @@ 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. Install `@immich/ui` as a sibling to `immich/`, for example `/home/user/immich` and `/home/user/ui`
2. Build the `@immich/ui` project via `pnpm run build` 2. Build the `@immich/ui` project via `pnpm run build`
3. Uncomment the corresponding volume in web service of the `docker/docker-compose.dev.yaml` file (`../../ui:/usr/ui`) 3. Uncomment the corresponding volume in web service of the `docker/docker-compose.dev.yml` file (`../../ui:/usr/src/ui`)
4. Uncomment the corresponding alias in the `web/vite.config.js` file (`'@immich/ui': path.resolve(\_\_dirname, '../../ui')`) 4. Uncomment the corresponding alias in the `web/vite.config.ts` file (`'@immich/ui': path.resolve(\_\_dirname, '../../ui/packages/ui')`)
5. Uncomment the import statement in `web/src/app.css` file `@import '/usr/ui/dist/theme/default.css';` and comment out `@import '@immich/ui/theme/default.css';` 5. Uncomment the import statement in `web/src/app.css` file `@import '../../../ui/packages/ui/dist/theme/default.css';` and comment out `@import '@immich/ui/theme/default.css';`
6. Start up the stack via `make dev` 6. Start up the stack via `make dev`
7. After making changes in `@immich/ui`, rebuild it (`pnpm run build`) 7. After making changes in `@immich/ui`, rebuild it (`pnpm run build`)
+1 -1
View File
@@ -26,7 +26,7 @@ You can search the following types of content:
| Time frame | Start and end date of a specific time bucket | | Time frame | Start and end date of a specific time bucket |
| Media type | Image or video or both | | Media type | Image or video or both |
| Display options | In Archive, in Favorites or Not in any album | | Display options | In Archive, in Favorites or Not in any album |
| Start rating | User-assigned start rating | | Star rating | User-assigned star rating |
<img src={require('./img/advanced-search-filters.webp').default} width="70%" title='Advanced search filters' /> <img src={require('./img/advanced-search-filters.webp').default} width="70%" title='Advanced search filters' />
+1
View File
@@ -18,6 +18,7 @@ For the full list, refer to the [Immich source code](https://github.com/immich-a
| `JPEG 2000` | `.jp2` | :white_check_mark: | | | `JPEG 2000` | `.jp2` | :white_check_mark: | |
| `JPEG` | `.jpeg` `.jpg` `.jpe` `.insp` | :white_check_mark: | | | `JPEG` | `.jpeg` `.jpg` `.jpe` `.insp` | :white_check_mark: | |
| `JPEG XL` | `.jxl` | :white_check_mark: | | | `JPEG XL` | `.jxl` | :white_check_mark: | |
| `MPO` | `.mpo` | :white_check_mark: | Multi-Picture |
| `PNG` | `.png` | :white_check_mark: | | | `PNG` | `.png` | :white_check_mark: | |
| `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop | | `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop |
| `RAW` | `.raw` | :white_check_mark: | | | `RAW` | `.raw` | :white_check_mark: | |
-2
View File
@@ -20,8 +20,6 @@ def upload(file):
} }
data = { data = {
'deviceAssetId': f'{file}-{stats.st_mtime}',
'deviceId': 'python',
'fileCreatedAt': datetime.fromtimestamp(stats.st_mtime), 'fileCreatedAt': datetime.fromtimestamp(stats.st_mtime),
'fileModifiedAt': datetime.fromtimestamp(stats.st_mtime), 'fileModifiedAt': datetime.fromtimestamp(stats.st_mtime),
'isFavorite': 'false', 'isFavorite': 'false',
+1
View File
@@ -193,6 +193,7 @@ The default configuration looks like this:
"defaultStorageQuota": null, "defaultStorageQuota": null,
"enabled": false, "enabled": false,
"issuerUrl": "", "issuerUrl": "",
"endSessionEndpoint": "",
"mobileOverrideEnabled": false, "mobileOverrideEnabled": false,
"mobileRedirectUri": "", "mobileRedirectUri": "",
"profileSigningAlgorithm": "none", "profileSigningAlgorithm": "none",
+1 -1
View File
@@ -37,7 +37,7 @@ These environment variables are used by the `docker-compose.yml` file and do **N
| `IMMICH_LOG_FORMAT` | Log output format (`console`, `json`) | `console` | server | api, microservices | | `IMMICH_LOG_FORMAT` | Log output format (`console`, `json`) | `console` | server | api, microservices |
| `IMMICH_MEDIA_LOCATION` | Media location inside the container ⚠️**You probably shouldn't set this**<sup>\*2</sup>⚠️ | `/data` | server | api, microservices | | `IMMICH_MEDIA_LOCATION` | Media location inside the container ⚠️**You probably shouldn't set this**<sup>\*2</sup>⚠️ | `/data` | server | api, microservices |
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices | | `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
| `IMMICH_HELMET_FILE` | Path to a json file with [helmet](https://www.npmjs.com/package/helmet) options. Set to `false` to disable. Set to `true` to use `server/helmet.json`. | `false` | server | api, microservices | | `IMMICH_HELMET_FILE` | Path to a json file with [helmet](https://www.npmjs.com/package/helmet) options. Set to `false` to disable. Set to `true` to use `server/helmet.json`. | `false` | server | api |
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | | | `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
| `CPU_CORES` | Number of cores available to the Immich server | auto-detected CPU core count | server | | | `CPU_CORES` | Number of cores available to the Immich server | auto-detected CPU core count | server | |
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api | | `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
+1 -1
View File
@@ -49,7 +49,7 @@ Immich requires [**Docker**](https://docs.docker.com/get-started/get-docker/) wi
The Compose plugin will be installed by both Docker Engine and Desktop by following the linked installation guides; it can also be [separately installed](https://docs.docker.com/compose/install/). The Compose plugin will be installed by both Docker Engine and Desktop by following the linked installation guides; it can also be [separately installed](https://docs.docker.com/compose/install/).
:::note :::note
Immich requires the command `docker compose`; the similarly named `docker-compose` is [deprecated](https://docs.docker.com/compose/migrate/) and is no longer supported by Immich. Immich requires the command `docker compose`; the similarly named `docker-compose` is [deprecated](https://docs.docker.com/retired/#docker-compose-v1-replaced-by-compose-v2) and is no longer supported by Immich.
::: :::
### Special requirements for Windows users ### Special requirements for Windows users
+2
View File
@@ -6,6 +6,8 @@ You can read more about the differences between storage template engine on and o
The admin user can set the template by using the template builder in the `Administration -> Settings -> Storage Template`. Immich provides a set of variables that you can use in constructing the template, along with additional custom text. If the template produces [multiple files with the same filename, they won't be overwritten](https://github.com/immich-app/immich/discussions/3324) as a sequence number is appended to the filename. The admin user can set the template by using the template builder in the `Administration -> Settings -> Storage Template`. Immich provides a set of variables that you can use in constructing the template, along with additional custom text. If the template produces [multiple files with the same filename, they won't be overwritten](https://github.com/immich-app/immich/discussions/3324) as a sequence number is appended to the filename.
Date and time variables in storage templates are rendered in the server's local timezone.
```bash title="Default template" ```bash title="Default template"
Year/Year-Month-Day/Filename.Extension Year/Year-Month-Day/Filename.Extension
``` ```
+11 -11
View File
@@ -17,10 +17,10 @@
"write-heading-ids": "docusaurus write-heading-ids" "write-heading-ids": "docusaurus write-heading-ids"
}, },
"dependencies": { "dependencies": {
"@docusaurus/core": "~3.9.0", "@docusaurus/core": "~3.10.0",
"@docusaurus/preset-classic": "~3.9.0", "@docusaurus/preset-classic": "~3.10.0",
"@docusaurus/theme-common": "~3.9.0", "@docusaurus/theme-common": "~3.10.0",
"@docusaurus/theme-mermaid": "~3.9.0", "@docusaurus/theme-mermaid": "~3.10.0",
"@mdi/js": "^7.3.67", "@mdi/js": "^7.3.67",
"@mdi/react": "^1.6.1", "@mdi/react": "^1.6.1",
"@mdx-js/react": "^3.0.0", "@mdx-js/react": "^3.0.0",
@@ -30,17 +30,17 @@
"postcss": "^8.4.25", "postcss": "^8.4.25",
"prism-react-renderer": "^2.3.1", "prism-react-renderer": "^2.3.1",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",
"react": "^18.0.0", "react": "^19.0.0",
"react-dom": "^18.0.0", "react-dom": "^19.0.0",
"tailwindcss": "^3.2.4", "tailwindcss": "^3.2.4",
"url": "^0.11.0" "url": "^0.11.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "~3.9.0", "@docusaurus/module-type-aliases": "~3.10.0",
"@docusaurus/tsconfig": "^3.7.0", "@docusaurus/tsconfig": "^3.10.0",
"@docusaurus/types": "^3.7.0", "@docusaurus/types": "^3.10.0",
"prettier": "^3.7.4", "prettier": "^3.7.4",
"typescript": "^5.1.6" "typescript": "^6.0.0"
}, },
"browserslist": { "browserslist": {
"production": [ "production": [
@@ -58,6 +58,6 @@
"node": ">=20" "node": ">=20"
}, },
"volta": { "volta": {
"node": "24.14.1" "node": "24.15.0"
} }
} }
+4
View File
@@ -1,4 +1,8 @@
[ [
{
"label": "v2.7.5",
"url": "https://docs.v2.7.5.archive.immich.app"
},
{ {
"label": "v2.6.3", "label": "v2.6.3",
"url": "https://docs.v2.6.3.archive.immich.app" "url": "https://docs.v2.6.3.archive.immich.app"
+1 -5
View File
@@ -1,8 +1,4 @@
{ {
// This file is not used in compilation. It is here just for a nice editor experience. // This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@docusaurus/tsconfig", "extends": "@docusaurus/tsconfig"
"compilerOptions": {
"baseUrl": "."
}
} }
+31 -3
View File
@@ -1,5 +1,12 @@
import { exportJWK, generateKeyPair } from 'jose'; import {
calculateJwkThumbprint,
exportJWK,
importPKCS8,
importSPKI,
SignJWT,
} from 'jose';
import Provider from 'oidc-provider'; import Provider from 'oidc-provider';
import { PRIVATE_KEY_PEM, PUBLIC_KEY_PEM } from './test-keys';
export enum OAuthClient { export enum OAuthClient {
DEFAULT = 'client-default', DEFAULT = 'client-default',
@@ -44,6 +51,29 @@ const claims = [
}, },
]; ];
const privateKey = await importPKCS8(PRIVATE_KEY_PEM, 'RS256', {
extractable: true,
});
const publicKey = await importSPKI(PUBLIC_KEY_PEM, 'RS256', {
extractable: true,
});
const kid = await calculateJwkThumbprint(await exportJWK(publicKey));
export async function generateLogoutToken(iss: string, sub: string) {
return await new SignJWT({
iss: iss,
aud: OAuthClient.DEFAULT,
iat: Math.floor(Date.now() / 1000),
jti: crypto.randomUUID(),
sub: sub,
events: {
'http://schemas.openid.net/event/backchannel-logout': {},
},
})
.setProtectedHeader({ alg: 'RS256', typ: 'logout+jwt', kid: kid })
.sign(privateKey);
}
const withDefaultClaims = (sub: string) => ({ const withDefaultClaims = (sub: string) => ({
sub, sub,
email: `${sub}@immich.app`, email: `${sub}@immich.app`,
@@ -66,8 +96,6 @@ const getClaims = (sub: string, use?: string) => {
}; };
const setup = async () => { const setup = async () => {
const { privateKey, publicKey } = await generateKeyPair('RS256');
const redirectUris = [ const redirectUris = [
'http://127.0.0.1:2285/auth/login', 'http://127.0.0.1:2285/auth/login',
'https://photos.immich.app/oauth/mobile-redirect', 'https://photos.immich.app/oauth/mobile-redirect',
+1 -1
View File
@@ -7,7 +7,7 @@
"start": "tsx startup.ts" "start": "tsx startup.ts"
}, },
"devDependencies": { "devDependencies": {
"jose": "^5.6.3", "jose": "^6.0.0",
"@types/oidc-provider": "^9.0.0", "@types/oidc-provider": "^9.0.0",
"oidc-provider": "^9.0.0", "oidc-provider": "^9.0.0",
"tsx": "^4.20.6" "tsx": "^4.20.6"
+38
View File
@@ -0,0 +1,38 @@
export const PRIVATE_KEY_PEM = `-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCVj5C7hzN3E2HO
TcJ+DN/e2NSTQFj4rPylz4J8xjm8Es7l0k2kK5EEGvUNVGZbw7s055c+6kwP9eqg
B5XFE7+26Fcq1sou6Tbm310kU4dnMW5l2CgwrhaGyb1pNysao0AMLT60dFYqtUwn
ha9ceCsa+ZU1JrknVf3rONtppBvhWoI7CO9XX1keVQ0unHPzCWUjpXTzC8OGEbmB
2w7ZIUf8OfJkd5RZ4OtIpML71W9n13aDxT50x2/EW/pFLFtQ/oaleOKHpvlRXDRX
W86G4moUJym3gHMXMUj2aOcFG2UJnpLruKz3i5qZwYiTRlBP6O9EIQNCVtYxchuN
V1CCcBU1AgMBAAECggEAJLfXMu8Nx89ynPVyyUMMaFfoEpHC9iR0L5obQVpiPMYK
VRqVVLecdftPS9s7eQ58BNBRzdC0ZVu841aRYs3HLNbsZZhPkYZQpAxU//Dg5okY
fzj7Hv5yidt4HN9+Pd8z/3lRMnj4WapifLaBt8xJ2ujJBMBRxzJBsXDnT0+Kx7+y
bYDeuVfyUTEikaK3QZTbuRF3D3eiuN16GG+hv8UqTF2eYbPxdiLjYpTSHa4mH88C
qfJz2Xt4SEzmyeo3G+MO17wDFOwtEe8ojlJfULHnHJSFdUwTfYIFM1bg5/fJ9MOS
/fO3TSG+wkQqjQa6eoGssAzP87fL2XNLzlDtGY/7uQKBgQDHuJHOtf1EjOvNYiP7
EN+8QGs41ghzt9CQRQxWbHpusR3IW3P83KMXwYmrlG70oOUXBRGSB/ESXUofXc5W
pu5+Y55S44aUnu/a9yOBttYW0dtHZSL0zFT+PlVASwUzFZ2zcH1KXlUkSpfL5OAD
PyDDTnBZ2AWh45fRO9wLo6PPuQKBgQC/tI03RqU3mOjqukKbquYeIpXHfRU5Z0DM
u9ru1THYEl6fmkMXycxo/mvW3awyFuyKy/VodqIgKnFgumEqCHZh6OAMm/LC7TfA
l9tjFSs/MyOqQVD4kbX+z6Oq4c4GccDoXfsQ3gzECoBapegi/F+6/25y+/C8ghXb
J/Jg1GQXXQKBgQDFgWbfzuVZZyrBfu4qGLPJDMN7/114YizknwPma3xf/tN/EcGQ
K/k1QvWMMkvPq1UiAKcxjJ0AFjV482FcG9T6NDWbrtmmG88C8Sex3Ue2ZW2+GuwI
vhDHJIlV/Vp0/Elp7DJa2xLDwuh+gCZvz3vs6KL+ljxrrhCyn8mp0PfsMQKBgFFZ
KnuETOO0zVGdzFoGQTQUdP58A5+iQwsdxB+I9Ge+E80iRso3ZbhADj7VPhbbR3D2
b6LuhImluQrUzBpsEOAnU7vGCVPSGdBuIDiBaSKebsn2gYeZPWNtdQQ0YZq2dqek
Cb/0mfIuipzsvf7qnSza62F7q4IyqVegMegI+Jg5AoGATM3NMy7JZeKzSkm+3ohU
3xZOwgqKV9SH+0OeYWpuBxT7D7FlrKKI4NJ3XN3hg2f/DJAF6dH11CPe7pk94yol
HMbh+PQUQ6GYvAzxIOvagWboQ3lzeyubNMpyFjfOrIE/WOQCUBZ9tIwCHIarIuyi
QRuNOj3+U8T/n1Ww352HBdw=
-----END PRIVATE KEY-----`;
export const PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlY+Qu4czdxNhzk3Cfgzf
3tjUk0BY+Kz8pc+CfMY5vBLO5dJNpCuRBBr1DVRmW8O7NOeXPupMD/XqoAeVxRO/
tuhXKtbKLuk25t9dJFOHZzFuZdgoMK4Whsm9aTcrGqNADC0+tHRWKrVMJ4WvXHgr
GvmVNSa5J1X96zjbaaQb4VqCOwjvV19ZHlUNLpxz8wllI6V08wvDhhG5gdsO2SFH
/DnyZHeUWeDrSKTC+9VvZ9d2g8U+dMdvxFv6RSxbUP6GpXjih6b5UVw0V1vOhuJq
FCcpt4BzFzFI9mjnBRtlCZ6S67is94uamcGIk0ZQT+jvRCEDQlbWMXIbjVdQgnAV
NQIDAQAB
-----END PUBLIC KEY-----`;
+1 -1
View File
@@ -1 +1 @@
24.14.1 24.15.0
+1 -1
View File
@@ -44,7 +44,7 @@ services:
redis: redis:
container_name: immich-e2e-redis container_name: immich-e2e-redis
image: docker.io/valkey/valkey:9@sha256:3eeb09785cd61ec8e3be35f8804c8892080f3ca21934d628abc24ee4ed1698f6 image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
+6 -6
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "2.6.3", "version": "2.7.5",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
@@ -32,15 +32,15 @@
"@playwright/test": "^1.44.1", "@playwright/test": "^1.44.1",
"@socket.io/component-emitter": "^3.1.2", "@socket.io/component-emitter": "^3.1.2",
"@types/luxon": "^3.4.2", "@types/luxon": "^3.4.2",
"@types/node": "^24.12.0", "@types/node": "^24.12.2",
"@types/pg": "^8.15.1", "@types/pg": "^8.15.1",
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
"@types/supertest": "^6.0.2", "@types/supertest": "^7.0.0",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"eslint": "^10.0.0", "eslint": "^10.0.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^63.0.0", "eslint-plugin-unicorn": "^64.0.0",
"exiftool-vendored": "^35.0.0", "exiftool-vendored": "^35.0.0",
"globals": "^17.0.0", "globals": "^17.0.0",
"luxon": "^3.4.4", "luxon": "^3.4.4",
@@ -51,13 +51,13 @@
"sharp": "^0.34.5", "sharp": "^0.34.5",
"socket.io-client": "^4.7.4", "socket.io-client": "^4.7.4",
"supertest": "^7.0.0", "supertest": "^7.0.0",
"typescript": "^5.3.3", "typescript": "^6.0.0",
"typescript-eslint": "^8.28.0", "typescript-eslint": "^8.28.0",
"utimes": "^5.2.1", "utimes": "^5.2.1",
"vite-tsconfig-paths": "^6.1.1", "vite-tsconfig-paths": "^6.1.1",
"vitest": "^4.0.0" "vitest": "^4.0.0"
}, },
"volta": { "volta": {
"node": "24.14.1" "node": "24.15.0"
} }
} }
-13
View File
@@ -5,79 +5,66 @@ export const errorDto = {
error: 'Unauthorized', error: 'Unauthorized',
statusCode: 401, statusCode: 401,
message: 'Authentication required', message: 'Authentication required',
correlationId: expect.any(String),
}, },
unauthorizedWithMessage: (message: string) => ({ unauthorizedWithMessage: (message: string) => ({
error: 'Unauthorized', error: 'Unauthorized',
statusCode: 401, statusCode: 401,
message, message,
correlationId: expect.any(String),
}), }),
forbidden: { forbidden: {
error: 'Forbidden', error: 'Forbidden',
statusCode: 403, statusCode: 403,
message: expect.any(String), message: expect.any(String),
correlationId: expect.any(String),
}, },
missingPermission: (permission: string) => ({ missingPermission: (permission: string) => ({
error: 'Forbidden', error: 'Forbidden',
statusCode: 403, statusCode: 403,
message: `Missing required permission: ${permission}`, message: `Missing required permission: ${permission}`,
correlationId: expect.any(String),
}), }),
wrongPassword: { wrongPassword: {
error: 'Bad Request', error: 'Bad Request',
statusCode: 400, statusCode: 400,
message: 'Wrong password', message: 'Wrong password',
correlationId: expect.any(String),
}, },
invalidToken: { invalidToken: {
error: 'Unauthorized', error: 'Unauthorized',
statusCode: 401, statusCode: 401,
message: 'Invalid user token', message: 'Invalid user token',
correlationId: expect.any(String),
}, },
invalidShareKey: { invalidShareKey: {
error: 'Unauthorized', error: 'Unauthorized',
statusCode: 401, statusCode: 401,
message: 'Invalid share key', message: 'Invalid share key',
correlationId: expect.any(String),
}, },
passwordRequired: { passwordRequired: {
error: 'Unauthorized', error: 'Unauthorized',
statusCode: 401, statusCode: 401,
message: 'Password required', message: 'Password required',
correlationId: expect.any(String),
}, },
badRequest: (message: any = null) => ({ badRequest: (message: any = null) => ({
error: 'Bad Request', error: 'Bad Request',
statusCode: 400, statusCode: 400,
message: message ?? expect.anything(), message: message ?? expect.anything(),
correlationId: expect.any(String),
}), }),
noPermission: { noPermission: {
error: 'Bad Request', error: 'Bad Request',
statusCode: 400, statusCode: 400,
message: expect.stringContaining('Not found or no'), message: expect.stringContaining('Not found or no'),
correlationId: expect.any(String),
}, },
incorrectLogin: { incorrectLogin: {
error: 'Unauthorized', error: 'Unauthorized',
statusCode: 401, statusCode: 401,
message: 'Incorrect email or password', message: 'Incorrect email or password',
correlationId: expect.any(String),
}, },
alreadyHasAdmin: { alreadyHasAdmin: {
error: 'Bad Request', error: 'Bad Request',
statusCode: 400, statusCode: 400,
message: 'The server already has an admin', message: 'The server already has an admin',
correlationId: expect.any(String),
}, },
invalidEmail: { invalidEmail: {
error: 'Bad Request', error: 'Bad Request',
statusCode: 400, statusCode: 400,
message: ['email must be an email'], message: ['email must be an email'],
correlationId: expect.any(String),
}, },
}; };
+72 -63
View File
@@ -130,12 +130,11 @@ describe('/albums', () => {
describe('GET /albums', () => { describe('GET /albums', () => {
it("should not show other users' favorites", async () => { it("should not show other users' favorites", async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.get(`/albums/${user1Albums[0].id}?withoutAssets=false`) .get(`/albums/${user1Albums[0].id}`)
.set('Authorization', `Bearer ${user2.accessToken}`); .set('Authorization', `Bearer ${user2.accessToken}`);
expect(status).toEqual(200); expect(status).toEqual(200);
expect(body).toEqual({ expect(body).toEqual({
...user1Albums[0], ...user1Albums[0],
assets: [expect.objectContaining({ isFavorite: false })],
contributorCounts: [{ userId: user1.userId, assetCount: 1 }], contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
lastModifiedAssetTimestamp: expect.any(String), lastModifiedAssetTimestamp: expect.any(String),
startDate: expect.any(String), startDate: expect.any(String),
@@ -155,23 +154,31 @@ describe('/albums', () => {
expect(body).toEqual( expect(body).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedLink, albumName: user1SharedLink,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedEditorUser, albumName: user1SharedEditorUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedViewerUser, albumName: user1SharedViewerUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user2.userId,
albumName: user2SharedUser, albumName: user2SharedUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) },
]),
shared: true, shared: true,
}), }),
]), ]),
@@ -185,23 +192,31 @@ describe('/albums', () => {
expect(body).toEqual( expect(body).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedEditorUser, albumName: user1SharedEditorUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedViewerUser, albumName: user1SharedViewerUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedLink, albumName: user1SharedLink,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1NotShared, albumName: user1NotShared,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: false, shared: false,
}), }),
]), ]),
@@ -217,23 +232,31 @@ describe('/albums', () => {
expect(body).toEqual( expect(body).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedEditorUser, albumName: user1SharedEditorUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedViewerUser, albumName: user1SharedViewerUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedLink, albumName: user1SharedLink,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user2.userId,
albumName: user2SharedUser, albumName: user2SharedUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) },
]),
shared: true, shared: true,
}), }),
]), ]),
@@ -249,8 +272,10 @@ describe('/albums', () => {
expect(body).toEqual( expect(body).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1NotShared, albumName: user1NotShared,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: false, shared: false,
}), }),
]), ]),
@@ -287,13 +312,17 @@ describe('/albums', () => {
expect(body).toEqual( expect(body).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
ownerId: user4.userId,
albumName: user4DeletedAsset, albumName: user4DeletedAsset,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user4.userId }) },
]),
shared: false, shared: false,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user4.userId,
albumName: user4Empty, albumName: user4Empty,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user4.userId }) },
]),
shared: false, shared: false,
}), }),
]), ]),
@@ -304,13 +333,12 @@ describe('/albums', () => {
describe('GET /albums/:id', () => { describe('GET /albums/:id', () => {
it('should return album info for own album', async () => { it('should return album info for own album', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.get(`/albums/${user1Albums[0].id}?withoutAssets=false`) .get(`/albums/${user1Albums[0].id}`)
.set('Authorization', `Bearer ${user1.accessToken}`); .set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ expect(body).toEqual({
...user1Albums[0], ...user1Albums[0],
assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })],
contributorCounts: [{ userId: user1.userId, assetCount: 1 }], contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
lastModifiedAssetTimestamp: expect.any(String), lastModifiedAssetTimestamp: expect.any(String),
startDate: expect.any(String), startDate: expect.any(String),
@@ -322,7 +350,7 @@ describe('/albums', () => {
it('should return album info for shared album (editor)', async () => { it('should return album info for shared album (editor)', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.get(`/albums/${user2Albums[0].id}?withoutAssets=false`) .get(`/albums/${user2Albums[0].id}`)
.set('Authorization', `Bearer ${user1.accessToken}`); .set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200); expect(status).toBe(200);
@@ -331,14 +359,14 @@ describe('/albums', () => {
it('should return album info for shared album (viewer)', async () => { it('should return album info for shared album (viewer)', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.get(`/albums/${user1Albums[3].id}?withoutAssets=false`) .get(`/albums/${user1Albums[3].id}`)
.set('Authorization', `Bearer ${user2.accessToken}`); .set('Authorization', `Bearer ${user2.accessToken}`);
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toMatchObject({ id: user1Albums[3].id }); expect(body).toMatchObject({ id: user1Albums[3].id });
}); });
it('should return album info with assets when withoutAssets is undefined', async () => { it('should return album info', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.get(`/albums/${user1Albums[0].id}`) .get(`/albums/${user1Albums[0].id}`)
.set('Authorization', `Bearer ${user1.accessToken}`); .set('Authorization', `Bearer ${user1.accessToken}`);
@@ -346,25 +374,6 @@ describe('/albums', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ expect(body).toEqual({
...user1Albums[0], ...user1Albums[0],
assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })],
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
lastModifiedAssetTimestamp: expect.any(String),
startDate: expect.any(String),
endDate: expect.any(String),
albumUsers: expect.any(Array),
shared: true,
});
});
it('should return album info without assets when withoutAssets is true', async () => {
const { status, body } = await request(app)
.get(`/albums/${user1Albums[0].id}?withoutAssets=true`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({
...user1Albums[0],
assets: [],
contributorCounts: [{ userId: user1.userId, assetCount: 1 }], contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
assetCount: 1, assetCount: 1,
lastModifiedAssetTimestamp: expect.any(String), lastModifiedAssetTimestamp: expect.any(String),
@@ -379,21 +388,21 @@ describe('/albums', () => {
await utils.deleteAssets(user1.accessToken, [user1Asset2.id]); await utils.deleteAssets(user1.accessToken, [user1Asset2.id]);
const { status, body } = await request(app) const { status, body } = await request(app)
.get(`/albums/${user2Albums[0].id}?withoutAssets=true`) .get(`/albums/${user2Albums[0].id}`)
.set('Authorization', `Bearer ${user1.accessToken}`); .set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ expect(body).toEqual(
...user2Albums[0], expect.objectContaining({
assets: [], contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
contributorCounts: [{ userId: user1.userId, assetCount: 1 }], assetCount: 1,
assetCount: 1, lastModifiedAssetTimestamp: expect.any(String),
lastModifiedAssetTimestamp: expect.any(String), endDate: expect.any(String),
endDate: expect.any(String), startDate: expect.any(String),
startDate: expect.any(String), albumUsers: expect.any(Array),
albumUsers: expect.any(Array), shared: true,
shared: true, }),
}); );
}); });
}); });
@@ -419,16 +428,13 @@ describe('/albums', () => {
id: expect.any(String), id: expect.any(String),
createdAt: expect.any(String), createdAt: expect.any(String),
updatedAt: expect.any(String), updatedAt: expect.any(String),
ownerId: user1.userId,
albumName: 'New album', albumName: 'New album',
description: '', description: '',
albumThumbnailAssetId: null, albumThumbnailAssetId: null,
shared: false, shared: false,
albumUsers: [], albumUsers: [{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }],
hasSharedLink: false, hasSharedLink: false,
assets: [],
assetCount: 0, assetCount: 0,
owner: expect.objectContaining({ email: user1.userEmail }),
isActivityEnabled: true, isActivityEnabled: true,
order: AssetOrder.Desc, order: AssetOrder.Desc,
}); });
@@ -644,11 +650,11 @@ describe('/albums', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual( expect(body).toEqual(
expect.objectContaining({ expect.objectContaining({
albumUsers: [ albumUsers: expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
user: expect.objectContaining({ id: user2.userId }), user: expect.objectContaining({ id: user2.userId }),
}), }),
], ]),
}), }),
); );
}); });
@@ -660,7 +666,7 @@ describe('/albums', () => {
.send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] }); .send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Cannot be shared with owner')); expect(body).toEqual(errorDto.badRequest('User already added'));
}); });
it('should not be able to add existing user to shared album', async () => { it('should not be able to add existing user to shared album', async () => {
@@ -686,7 +692,7 @@ describe('/albums', () => {
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }], albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
}); });
expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer); expect(album.albumUsers[1].role).toEqual(AlbumUserRole.Viewer);
const { status } = await request(app) const { status } = await request(app)
.put(`/albums/${album.id}/user/${user2.userId}`) .put(`/albums/${album.id}/user/${user2.userId}`)
@@ -701,7 +707,10 @@ describe('/albums', () => {
.set('Authorization', `Bearer ${user1.accessToken}`); .set('Authorization', `Bearer ${user1.accessToken}`);
expect(body).toEqual( expect(body).toEqual(
expect.objectContaining({ expect.objectContaining({
albumUsers: [expect.objectContaining({ role: AlbumUserRole.Editor })], albumUsers: [
expect.objectContaining({ role: AlbumUserRole.Owner }),
expect.objectContaining({ role: AlbumUserRole.Editor }),
],
}), }),
); );
}); });
@@ -712,7 +721,7 @@ describe('/albums', () => {
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }], albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
}); });
expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer); expect(album.albumUsers[1].role).toEqual(AlbumUserRole.Viewer);
const { status, body } = await request(app) const { status, body } = await request(app)
.put(`/albums/${album.id}/user/${user2.userId}`) .put(`/albums/${album.id}/user/${user2.userId}`)
+5 -85
View File
@@ -1,7 +1,6 @@
import { import {
AssetMediaResponseDto, AssetMediaResponseDto,
AssetMediaStatus, AssetMediaStatus,
AssetResponseDto,
AssetTypeEnum, AssetTypeEnum,
AssetVisibility, AssetVisibility,
getAssetInfo, getAssetInfo,
@@ -19,7 +18,7 @@ import { Socket } from 'socket.io-client';
import { createUserDto, uuidDto } from 'src/fixtures'; import { createUserDto, uuidDto } from 'src/fixtures';
import { makeRandomImage } from 'src/generators'; import { makeRandomImage } from 'src/generators';
import { errorDto } from 'src/responses'; import { errorDto } from 'src/responses';
import { app, asBearerAuth, tempDir, TEN_TIMES, testAssetDir, utils } from 'src/utils'; import { app, asBearerAuth, tempDir, testAssetDir, utils } from 'src/utils';
import request from 'supertest'; import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { afterAll, beforeAll, describe, expect, it } from 'vitest';
@@ -95,8 +94,8 @@ describe('/asset', () => {
utils.createAsset(user1.accessToken), utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken, { utils.createAsset(user1.accessToken, {
isFavorite: true, isFavorite: true,
fileCreatedAt: yesterday.toISO(), fileCreatedAt: yesterday.toUTC().toISO(),
fileModifiedAt: yesterday.toISO(), fileModifiedAt: yesterday.toUTC().toISO(),
assetData: { filename: 'example.mp4' }, assetData: { filename: 'example.mp4' },
}), }),
utils.createAsset(user1.accessToken), utils.createAsset(user1.accessToken),
@@ -380,62 +379,12 @@ describe('/asset', () => {
}); });
}); });
describe('GET /assets/random', () => {
beforeAll(async () => {
await Promise.all([
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken),
]);
await utils.waitForQueueFinish(admin.accessToken, 'thumbnailGeneration');
});
it.each(TEN_TIMES)('should return 1 random assets', async () => {
const { status, body } = await request(app)
.get('/assets/random')
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
const assets: AssetResponseDto[] = body;
expect(assets.length).toBe(1);
expect(assets[0].ownerId).toBe(user1.userId);
});
it.each(TEN_TIMES)('should return 2 random assets', async () => {
const { status, body } = await request(app)
.get('/assets/random?count=2')
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
const assets: AssetResponseDto[] = body;
expect(assets.length).toBe(2);
for (const asset of assets) {
expect(asset.ownerId).toBe(user1.userId);
}
});
it.skip('should return 1 asset if there are 10 assets in the database but user 2 only has 1', async () => {
const { status, body } = await request(app)
.get('/assets/random')
.set('Authorization', `Bearer ${user2.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual([expect.objectContaining({ id: user2Assets[0].id })]);
});
});
describe('PUT /assets/:id', () => { describe('PUT /assets/:id', () => {
it('should require access', async () => { it('should require access', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.put(`/assets/${user2Assets[0].id}`) .put(`/assets/${user2Assets[0].id}`)
.set('Authorization', `Bearer ${user1.accessToken}`); .set('Authorization', `Bearer ${user1.accessToken}`)
.send({});
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.noPermission); expect(body).toEqual(errorDto.noPermission);
}); });
@@ -1142,8 +1091,6 @@ describe('/asset', () => {
const { body, status } = await request(app) const { body, status } = await request(app)
.post('/assets') .post('/assets')
.set('Authorization', `Bearer ${quotaUser.accessToken}`) .set('Authorization', `Bearer ${quotaUser.accessToken}`)
.field('deviceAssetId', 'example-image')
.field('deviceId', 'e2e')
.field('fileCreatedAt', new Date().toISOString()) .field('fileCreatedAt', new Date().toISOString())
.field('fileModifiedAt', new Date().toISOString()) .field('fileModifiedAt', new Date().toISOString())
.attach('assetData', makeRandomImage(), 'example.jpg'); .attach('assetData', makeRandomImage(), 'example.jpg');
@@ -1160,8 +1107,6 @@ describe('/asset', () => {
const { body, status } = await request(app) const { body, status } = await request(app)
.post('/assets') .post('/assets')
.set('Authorization', `Bearer ${quotaUser.accessToken}`) .set('Authorization', `Bearer ${quotaUser.accessToken}`)
.field('deviceAssetId', 'example-image')
.field('deviceId', 'e2e')
.field('fileCreatedAt', new Date().toISOString()) .field('fileCreatedAt', new Date().toISOString())
.field('fileModifiedAt', new Date().toISOString()) .field('fileModifiedAt', new Date().toISOString())
.attach('assetData', randomBytes(2014), 'example.jpg'); .attach('assetData', randomBytes(2014), 'example.jpg');
@@ -1215,29 +1160,4 @@ describe('/asset', () => {
expect(video.checksum).toStrictEqual(checksum); expect(video.checksum).toStrictEqual(checksum);
}); });
}); });
describe('POST /assets/exist', () => {
it('ignores invalid deviceAssetIds', async () => {
const response = await utils.checkExistingAssets(user1.accessToken, {
deviceId: 'test-assets-exist',
deviceAssetIds: ['invalid', 'INVALID'],
});
expect(response.existingIds).toHaveLength(0);
});
it('returns the IDs of existing assets', async () => {
await utils.createAsset(user1.accessToken, {
deviceId: 'test-assets-exist',
deviceAssetId: 'test-asset-0',
});
const response = await utils.checkExistingAssets(user1.accessToken, {
deviceId: 'test-assets-exist',
deviceAssetIds: ['test-asset-0'],
});
expect(response.existingIds).toEqual(['test-asset-0']);
});
});
}); });
+7 -7
View File
@@ -110,7 +110,7 @@ describe('/libraries', () => {
}); });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(["All importPaths's elements must be unique"])); expect(body).toEqual(errorDto.badRequest(['[importPaths] Array must have unique items']));
}); });
it('should not create an external library with duplicate exclusion patterns', async () => { it('should not create an external library with duplicate exclusion patterns', async () => {
@@ -125,7 +125,7 @@ describe('/libraries', () => {
}); });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(["All exclusionPatterns's elements must be unique"])); expect(body).toEqual(errorDto.badRequest(['[exclusionPatterns] Array must have unique items']));
}); });
}); });
@@ -157,7 +157,7 @@ describe('/libraries', () => {
.send({ name: '' }); .send({ name: '' });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['name should not be empty'])); expect(body).toEqual(errorDto.badRequest(['[name] Too small: expected string to have >=1 characters']));
}); });
it('should change the import paths', async () => { it('should change the import paths', async () => {
@@ -181,7 +181,7 @@ describe('/libraries', () => {
.send({ importPaths: [''] }); .send({ importPaths: [''] });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['each value in importPaths should not be empty'])); expect(body).toEqual(errorDto.badRequest(['[importPaths] Array items must not be empty']));
}); });
it('should reject duplicate import paths', async () => { it('should reject duplicate import paths', async () => {
@@ -191,7 +191,7 @@ describe('/libraries', () => {
.send({ importPaths: ['/path', '/path'] }); .send({ importPaths: ['/path', '/path'] });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(["All importPaths's elements must be unique"])); expect(body).toEqual(errorDto.badRequest(['[importPaths] Array must have unique items']));
}); });
it('should change the exclusion pattern', async () => { it('should change the exclusion pattern', async () => {
@@ -215,7 +215,7 @@ describe('/libraries', () => {
.send({ exclusionPatterns: ['**/*.jpg', '**/*.jpg'] }); .send({ exclusionPatterns: ['**/*.jpg', '**/*.jpg'] });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(["All exclusionPatterns's elements must be unique"])); expect(body).toEqual(errorDto.badRequest(['[exclusionPatterns] Array must have unique items']));
}); });
it('should reject an empty exclusion pattern', async () => { it('should reject an empty exclusion pattern', async () => {
@@ -225,7 +225,7 @@ describe('/libraries', () => {
.send({ exclusionPatterns: [''] }); .send({ exclusionPatterns: [''] });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['each value in exclusionPatterns should not be empty'])); expect(body).toEqual(errorDto.badRequest(['[exclusionPatterns] Array items must not be empty']));
}); });
}); });
+4 -4
View File
@@ -109,7 +109,7 @@ describe('/map', () => {
.get('/map/reverse-geocode?lon=123') .get('/map/reverse-geocode?lon=123')
.set('Authorization', `Bearer ${admin.accessToken}`); .set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['lat must be a number between -90 and 90'])); expect(body).toEqual(errorDto.badRequest(['[lat] Invalid input: expected number, received NaN']));
}); });
it('should throw an error if a lat is not a number', async () => { it('should throw an error if a lat is not a number', async () => {
@@ -117,7 +117,7 @@ describe('/map', () => {
.get('/map/reverse-geocode?lat=abc&lon=123.456') .get('/map/reverse-geocode?lat=abc&lon=123.456')
.set('Authorization', `Bearer ${admin.accessToken}`); .set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['lat must be a number between -90 and 90'])); expect(body).toEqual(errorDto.badRequest(['[lat] Invalid input: expected number, received NaN']));
}); });
it('should throw an error if a lat is out of range', async () => { it('should throw an error if a lat is out of range', async () => {
@@ -125,7 +125,7 @@ describe('/map', () => {
.get('/map/reverse-geocode?lat=91&lon=123.456') .get('/map/reverse-geocode?lat=91&lon=123.456')
.set('Authorization', `Bearer ${admin.accessToken}`); .set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['lat must be a number between -90 and 90'])); expect(body).toEqual(errorDto.badRequest(['[lat] Too big: expected number to be <=90']));
}); });
it('should throw an error if a lon is not provided', async () => { it('should throw an error if a lon is not provided', async () => {
@@ -133,7 +133,7 @@ describe('/map', () => {
.get('/map/reverse-geocode?lat=75') .get('/map/reverse-geocode?lat=75')
.set('Authorization', `Bearer ${admin.accessToken}`); .set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['lon must be a number between -180 and 180'])); expect(body).toEqual(errorDto.badRequest(['[lon] Invalid input: expected number, received NaN']));
}); });
const reverseGeocodeTestCases = [ const reverseGeocodeTestCases = [
+118 -15
View File
@@ -1,9 +1,10 @@
import { OAuthClient, OAuthUser } from '@immich/e2e-auth-server'; import { OAuthClient, OAuthUser, generateLogoutToken } from '@immich/e2e-auth-server';
import { import {
LoginResponseDto, LoginResponseDto,
SystemConfigOAuthDto, SystemConfigOAuthDto,
getConfigDefaults, getConfigDefaults,
getMyUser, getMyUser,
getSessions,
startOAuth, startOAuth,
updateConfig, updateConfig,
} from '@immich/sdk'; } from '@immich/sdk';
@@ -76,6 +77,7 @@ const setupOAuth = async (token: string, dto: Partial<SystemConfigOAuthDto>) =>
...defaults.oauth, ...defaults.oauth,
buttonText: 'Login with Immich', buttonText: 'Login with Immich',
issuerUrl: `${authServer.internal}/.well-known/openid-configuration`, issuerUrl: `${authServer.internal}/.well-known/openid-configuration`,
allowInsecureRequests: true,
...dto, ...dto,
}; };
await updateConfig({ systemConfigDto: { ...defaults, oauth: merged } }, options); await updateConfig({ systemConfigDto: { ...defaults, oauth: merged } }, options);
@@ -87,21 +89,23 @@ describe(`/oauth`, () => {
beforeAll(async () => { beforeAll(async () => {
await utils.resetDatabase(); await utils.resetDatabase();
admin = await utils.adminSetup(); admin = await utils.adminSetup();
await setupOAuth(admin.accessToken, {
enabled: true,
clientId: OAuthClient.DEFAULT,
clientSecret: OAuthClient.DEFAULT,
buttonText: 'Login with Immich',
storageLabelClaim: 'immich_username',
});
}); });
describe('POST /oauth/authorize', () => { describe('POST /oauth/authorize', () => {
beforeAll(async () => {
await setupOAuth(admin.accessToken, {
enabled: true,
clientId: OAuthClient.DEFAULT,
clientSecret: OAuthClient.DEFAULT,
buttonText: 'Login with Immich',
storageLabelClaim: 'immich_username',
});
});
it(`should throw an error if a redirect uri is not provided`, async () => { it(`should throw an error if a redirect uri is not provided`, async () => {
const { status, body } = await request(app).post('/oauth/authorize').send({}); const { status, body } = await request(app).post('/oauth/authorize').send({});
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['redirectUri must be a string', 'redirectUri should not be empty'])); expect(body).toEqual(errorDto.badRequest(['[redirectUri] Invalid input: expected string, received undefined']));
}); });
it('should return a redirect uri', async () => { it('should return a redirect uri', async () => {
@@ -117,19 +121,56 @@ describe(`/oauth`, () => {
expect(params.get('redirect_uri')).toBe('http://127.0.0.1:2285/auth/login'); expect(params.get('redirect_uri')).toBe('http://127.0.0.1:2285/auth/login');
expect(params.get('state')).toBeDefined(); expect(params.get('state')).toBeDefined();
}); });
it('should not include the prompt parameter when not configured', async () => {
const { status, body } = await request(app)
.post('/oauth/authorize')
.send({ redirectUri: 'http://127.0.0.1:2285/auth/login' });
expect(status).toBe(201);
const params = new URL(body.url).searchParams;
expect(params.get('prompt')).toBeNull();
});
it('should include the prompt parameter when configured', async () => {
await setupOAuth(admin.accessToken, {
enabled: true,
clientId: OAuthClient.DEFAULT,
clientSecret: OAuthClient.DEFAULT,
prompt: 'select_account',
});
const { status, body } = await request(app)
.post('/oauth/authorize')
.send({ redirectUri: 'http://127.0.0.1:2285/auth/login' });
expect(status).toBe(201);
const params = new URL(body.url).searchParams;
expect(params.get('prompt')).toBe('select_account');
});
}); });
describe('POST /oauth/callback', () => { describe('POST /oauth/callback', () => {
beforeAll(async () => {
await setupOAuth(admin.accessToken, {
enabled: true,
clientId: OAuthClient.DEFAULT,
clientSecret: OAuthClient.DEFAULT,
buttonText: 'Login with Immich',
storageLabelClaim: 'immich_username',
});
});
it(`should throw an error if a url is not provided`, async () => { it(`should throw an error if a url is not provided`, async () => {
const { status, body } = await request(app).post('/oauth/callback').send({}); const { status, body } = await request(app).post('/oauth/callback').send({});
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['url must be a string', 'url should not be empty'])); expect(body).toEqual(errorDto.badRequest(['[url] Invalid input: expected string, received undefined']));
}); });
it(`should throw an error if the url is empty`, async () => { it(`should throw an error if the url is empty`, async () => {
const { status, body } = await request(app).post('/oauth/callback').send({ url: '' }); const { status, body } = await request(app).post('/oauth/callback').send({ url: '' });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['url should not be empty'])); expect(body).toEqual(errorDto.badRequest(['[url] Too small: expected string to have >=1 characters']));
}); });
it(`should throw an error if the state is not provided`, async () => { it(`should throw an error if the state is not provided`, async () => {
@@ -158,10 +199,9 @@ describe(`/oauth`, () => {
it(`should throw an error if the codeVerifier doesn't match the challenge`, async () => { it(`should throw an error if the codeVerifier doesn't match the challenge`, async () => {
const callbackParams = await loginWithOAuth('oauth-auto-register'); const callbackParams = await loginWithOAuth('oauth-auto-register');
const { codeVerifier } = await loginWithOAuth('oauth-auto-register'); const { codeVerifier } = await loginWithOAuth('oauth-auto-register');
const { status, body } = await request(app) const { status } = await request(app)
.post('/oauth/callback') .post('/oauth/callback')
.send({ ...callbackParams, codeVerifier }); .send({ ...callbackParams, codeVerifier });
console.log(body);
expect(status).toBeGreaterThanOrEqual(400); expect(status).toBeGreaterThanOrEqual(400);
}); });
@@ -258,7 +298,7 @@ describe(`/oauth`, () => {
accessToken: expect.any(String), accessToken: expect.any(String),
isAdmin: false, isAdmin: false,
name: 'OAuth User', name: 'OAuth User',
userEmail: 'oauth-RS256-token@immich.app', userEmail: 'oauth-rs256-token@immich.app',
userId: expect.any(String), userId: expect.any(String),
}); });
}); });
@@ -333,6 +373,50 @@ describe(`/oauth`, () => {
}); });
}); });
describe(`POST /oauth/backchannel-logout`, () => {
it(`should throw an error if the logout_token is not provided`, async () => {
const { status, body } = await request(app).post('/oauth/backchannel-logout').send({});
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['[logout_token] Invalid input: expected string, received undefined']));
});
it(`should throw an error if an invalid logout token is provided`, async () => {
const { status, body } = await request(app)
.post('/oauth/backchannel-logout')
.send({ logout_token: 'invalid token' });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Error backchannel logout: token validation failed'));
});
it(`should logout user if a valid logout token is provided`, async () => {
await setupOAuth(admin.accessToken, {
enabled: true,
clientId: OAuthClient.DEFAULT,
clientSecret: OAuthClient.DEFAULT,
autoRegister: true,
signingAlgorithm: 'RS256',
buttonText: 'Login with Immich',
});
const callbackParams = await loginWithOAuth('backchannel-logout-user');
const { status: callbackStatus, body: callbackBody } = await request(app)
.post('/oauth/callback')
.send(callbackParams);
expect(callbackStatus).toBe(201);
await expect(getSessions({ headers: asBearerAuth(callbackBody.accessToken) })).resolves.toHaveLength(1);
const logoutToken = await generateLogoutToken('http://0.0.0.0:2286', 'backchannel-logout-user');
const { status, body } = await request(app).post('/oauth/backchannel-logout').send({ logout_token: logoutToken });
expect(status).toBe(200);
expect(body).toMatchObject({});
await expect(getSessions({ headers: asBearerAuth(callbackBody.accessToken) })).rejects.toMatchObject({
status: 401,
});
});
});
describe('mobile redirect override', () => { describe('mobile redirect override', () => {
beforeAll(async () => { beforeAll(async () => {
await setupOAuth(admin.accessToken, { await setupOAuth(admin.accessToken, {
@@ -399,4 +483,23 @@ describe(`/oauth`, () => {
}); });
}); });
}); });
describe('allowInsecureRequests: false', () => {
beforeAll(async () => {
await setupOAuth(admin.accessToken, {
enabled: true,
clientId: OAuthClient.DEFAULT,
clientSecret: OAuthClient.DEFAULT,
allowInsecureRequests: false,
});
});
it('should reject OAuth discovery over HTTP', async () => {
const { status, body } = await request(app)
.post('/oauth/authorize')
.send({ redirectUri: 'http://127.0.0.1:2285/auth/login' });
expect(status).toBe(500);
expect(body).toMatchObject({ statusCode: 500 });
});
});
}); });
+1 -2
View File
@@ -74,7 +74,6 @@ describe('/search', () => {
const bytes = await readFile(join(testAssetDir, filename)); const bytes = await readFile(join(testAssetDir, filename));
assets.push( assets.push(
await utils.createAsset(admin.accessToken, { await utils.createAsset(admin.accessToken, {
deviceAssetId: `test-${filename}`,
assetData: { bytes, filename }, assetData: { bytes, filename },
...dto, ...dto,
}), }),
@@ -458,7 +457,7 @@ describe('/search', () => {
expect(Array.isArray(body)).toBe(true); expect(Array.isArray(body)).toBe(true);
if (Array.isArray(body)) { if (Array.isArray(body)) {
expect(body.length).toBeGreaterThan(10); expect(body.length).toBeGreaterThan(10);
expect(body[0].name).toEqual(name); expect(body[0].name).toEqual(expect.stringContaining(name));
expect(body[0].admin2name).toEqual(name); expect(body[0].admin2name).toEqual(name);
} }
}); });
@@ -207,16 +207,6 @@ describe('/server', () => {
}); });
}); });
describe('GET /server/theme', () => {
it('should respond with the server theme', async () => {
const { status, body } = await request(app).get('/server/theme');
expect(status).toBe(200);
expect(body).toEqual({
customCss: '',
});
});
});
describe('GET /server/license', () => { describe('GET /server/license', () => {
it('should require authentication', async () => { it('should require authentication', async () => {
const { status, body } = await request(app).get('/server/license'); const { status, body } = await request(app).get('/server/license');
@@ -243,9 +243,21 @@ describe('/shared-links', () => {
}); });
it('should get data for correct password protected link', async () => { it('should get data for correct password protected link', async () => {
const response = await request(app)
.post('/shared-links/login')
.send({ password: 'foo' })
.query({ key: linkWithPassword.key });
expect(response.status).toBe(201);
const cookies = response.get('Set-Cookie') ?? [];
expect(cookies).toHaveLength(1);
expect(cookies[0]).toContain('immich_shared_link_token');
const { status, body } = await request(app) const { status, body } = await request(app)
.get('/shared-links/me') .get('/shared-links/me')
.query({ key: linkWithPassword.key, password: 'foo' }); .query({ key: linkWithPassword.key })
.set('Cookie', cookies);
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual( expect(body).toEqual(
+2 -2
View File
@@ -309,7 +309,7 @@ describe('/tags', () => {
.get(`/tags/${uuidDto.invalid}`) .get(`/tags/${uuidDto.invalid}`)
.set('Authorization', `Bearer ${admin.accessToken}`); .set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); expect(body).toEqual(errorDto.badRequest(['[id] Invalid UUID']));
}); });
it('should get tag details', async () => { it('should get tag details', async () => {
@@ -427,7 +427,7 @@ describe('/tags', () => {
.delete(`/tags/${uuidDto.invalid}`) .delete(`/tags/${uuidDto.invalid}`)
.set('Authorization', `Bearer ${admin.accessToken}`); .set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); expect(body).toEqual(errorDto.badRequest(['[id] Invalid UUID']));
}); });
it('should delete a tag', async () => { it('should delete a tag', async () => {
@@ -287,7 +287,8 @@ describe('/admin/users', () => {
it('should delete user', async () => { it('should delete user', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.delete(`/admin/users/${userToDelete.userId}`) .delete(`/admin/users/${userToDelete.userId}`)
.set('Authorization', `Bearer ${admin.accessToken}`); .set('Authorization', `Bearer ${admin.accessToken}`)
.send({});
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toMatchObject({ expect(body).toMatchObject({
+6 -2
View File
@@ -178,7 +178,9 @@ describe('/users', () => {
.set('Authorization', `Bearer ${admin.accessToken}`); .set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['download.archiveSize must be an integer number'])); expect(body).toEqual(
errorDto.badRequest(['[download.archiveSize] Invalid input: expected int, received number']),
);
}); });
it('should update download archive size', async () => { it('should update download archive size', async () => {
@@ -204,7 +206,9 @@ describe('/users', () => {
.set('Authorization', `Bearer ${admin.accessToken}`); .set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['download.includeEmbeddedVideos must be a boolean value'])); expect(body).toEqual(
errorDto.badRequest(['[download.includeEmbeddedVideos] Invalid input: expected boolean, received number']),
);
}); });
it('should update download include embedded videos', async () => { it('should update download include embedded videos', async () => {
@@ -1,7 +1,9 @@
import { AssetMediaResponseDto, LoginResponseDto, SharedLinkType } from '@immich/sdk'; import { AssetMediaResponseDto, LoginResponseDto, SharedLinkType } from '@immich/sdk';
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { readFile } from 'node:fs/promises';
import { basename, join } from 'node:path';
import type { Socket } from 'socket.io-client'; import type { Socket } from 'socket.io-client';
import { utils } from 'src/utils'; import { testAssetDir, utils } from 'src/utils';
test.describe('Detail Panel', () => { test.describe('Detail Panel', () => {
let admin: LoginResponseDto; let admin: LoginResponseDto;
@@ -83,4 +85,42 @@ test.describe('Detail Panel', () => {
await utils.waitForWebsocketEvent({ event: 'assetUpdate', id: asset.id }); await utils.waitForWebsocketEvent({ event: 'assetUpdate', id: asset.id });
await expect(textarea).toHaveValue('new description'); await expect(textarea).toHaveValue('new description');
}); });
test.describe('Date editor', () => {
test('displays inferred asset timezone', async ({ context, page }) => {
const test = {
filepath: 'metadata/dates/datetimeoriginal-gps.jpg',
expected: {
dateTime: '2025-12-01T11:30',
// Test with a timezone which is NOT the first among timezones with the same offset
// This is to check that the editor does not simply fall back to the first available timezone with that offset
// America/Denver (-07:00) is not the first among timezones with offset -07:00
timeZoneWithOffset: 'America/Denver (-07:00)',
},
};
const asset = await utils.createAsset(admin.accessToken, {
assetData: {
bytes: await readFile(join(testAssetDir, test.filepath)),
filename: basename(test.filepath),
},
});
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
// asset viewer -> detail panel -> date editor
await utils.setAuthCookies(context, admin.accessToken);
await page.goto(`/photos/${asset.id}`);
await page.waitForSelector('#immich-asset-viewer');
await page.getByRole('button', { name: 'Info' }).click();
await page.getByTestId('detail-panel-edit-date-button').click();
await page.waitForSelector('[role="dialog"]');
const datetime = page.locator('#datetime');
await expect(datetime).toHaveValue(test.expected.dateTime);
const timezone = page.getByRole('combobox', { name: 'Timezone' });
await expect(timezone).toHaveValue(test.expected.timeZoneWithOffset);
});
});
}); });
+2 -2
View File
@@ -16,8 +16,8 @@ test.describe('Duplicates Utility', () => {
test.beforeEach(async ({ context }) => { test.beforeEach(async ({ context }) => {
[firstAsset, secondAsset] = await Promise.all([ [firstAsset, secondAsset] = await Promise.all([
utils.createAsset(admin.accessToken, { deviceAssetId: 'duplicate-a' }), utils.createAsset(admin.accessToken, {}),
utils.createAsset(admin.accessToken, { deviceAssetId: 'duplicate-b' }), utils.createAsset(admin.accessToken, {}),
]); ]);
await updateAssets( await updateAssets(
@@ -77,18 +77,4 @@ test.describe('Photo Viewer', () => {
}); });
expect(tagAtCenter).toBe('IMG'); expect(tagAtCenter).toBe('IMG');
}); });
test('reloads photo when checksum changes', async ({ page }) => {
await page.goto(`/photos/${asset.id}`);
const preview = page.getByTestId('preview').filter({ visible: true });
await expect(preview).toHaveAttribute('src', /.+/);
const initialSrc = await preview.getAttribute('src');
const websocketEvent = utils.waitForWebsocketEvent({ event: 'assetUpdate', id: asset.id });
await utils.replaceAsset(admin.accessToken, asset.id);
await websocketEvent;
await expect(preview).not.toHaveAttribute('src', initialSrc!);
});
}); });
@@ -3,6 +3,7 @@
*/ */
import { import {
AlbumUserRole,
AssetTypeEnum, AssetTypeEnum,
AssetVisibility, AssetVisibility,
UserAvatarColor, UserAvatarColor,
@@ -315,11 +316,9 @@ export function toAssetResponseDto(asset: MockTimelineAsset, owner?: UserRespons
return { return {
id: asset.id, id: asset.id,
deviceAssetId: `device-${asset.id}`,
ownerId: asset.ownerId, ownerId: asset.ownerId,
owner: owner || defaultOwner, owner: owner || defaultOwner,
libraryId: `library-${asset.ownerId}`, libraryId: `library-${asset.ownerId}`,
deviceId: `device-${asset.ownerId}`,
type: asset.isVideo ? AssetTypeEnum.Video : AssetTypeEnum.Image, type: asset.isVideo ? AssetTypeEnum.Video : AssetTypeEnum.Image,
originalPath: `/original/${asset.id}.${asset.isVideo ? 'mp4' : 'jpg'}`, originalPath: `/original/${asset.id}.${asset.isVideo ? 'mp4' : 'jpg'}`,
originalFileName: `${asset.id}.${asset.isVideo ? 'mp4' : 'jpg'}`, originalFileName: `${asset.id}.${asset.isVideo ? 'mp4' : 'jpg'}`,
@@ -334,7 +333,7 @@ export function toAssetResponseDto(asset: MockTimelineAsset, owner?: UserRespons
isArchived: false, isArchived: false,
isTrashed: asset.isTrashed, isTrashed: asset.isTrashed,
visibility: asset.visibility, visibility: asset.visibility,
duration: asset.duration || '0:00:00.00000', duration: asset.duration,
exifInfo, exifInfo,
livePhotoVideoId: asset.livePhotoVideoId, livePhotoVideoId: asset.livePhotoVideoId,
tags: [], tags: [],
@@ -422,14 +421,11 @@ export function getAlbum(
albumThumbnailAssetId: album.thumbnailAssetId, albumThumbnailAssetId: album.thumbnailAssetId,
createdAt: album.createdAt, createdAt: album.createdAt,
updatedAt: album.updatedAt, updatedAt: album.updatedAt,
ownerId: albumOwner.id, albumUsers: [{ user: albumOwner, role: AlbumUserRole.Owner }],
owner: albumOwner,
albumUsers: [], // Empty array for non-shared album
shared: false, shared: false,
hasSharedLink: false, hasSharedLink: false,
isActivityEnabled: true, isActivityEnabled: true,
assetCount: albumAssets.length, assetCount: albumAssets.length,
assets: albumAssets,
startDate: albumAssets.length > 0 ? albumAssets.at(-1)?.fileCreatedAt : undefined, startDate: albumAssets.length > 0 ? albumAssets.at(-1)?.fileCreatedAt : undefined,
endDate: albumAssets.length > 0 ? albumAssets[0].fileCreatedAt : undefined, endDate: albumAssets.length > 0 ? albumAssets[0].fileCreatedAt : undefined,
lastModifiedAssetTimestamp: albumAssets.length > 0 ? albumAssets[0].fileCreatedAt : undefined, lastModifiedAssetTimestamp: albumAssets.length > 0 ? albumAssets[0].fileCreatedAt : undefined,
+2 -1
View File
@@ -1,5 +1,5 @@
import { BrowserContext } from '@playwright/test'; import { BrowserContext } from '@playwright/test';
import { playwrightHost } from 'playwright.config'; import { playwrightHost } from 'src/../playwright.config';
export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserId: string) => { export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserId: string) => {
await context.addCookies([ await context.addCookies([
@@ -223,6 +223,7 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI
'.jp2', '.jp2',
'.jpe', '.jpe',
'.jxl', '.jxl',
'.mpo',
'.svg', '.svg',
'.tif', '.tif',
'.tiff', '.tiff',
@@ -16,7 +16,6 @@ export const createMockStackAsset = (ownerId: string): AssetResponseDto => {
const now = new Date().toISOString(); const now = new Date().toISOString();
return { return {
id: assetId, id: assetId,
deviceAssetId: `device-${assetId}`,
ownerId, ownerId,
owner: { owner: {
id: ownerId, id: ownerId,
@@ -27,7 +26,6 @@ export const createMockStackAsset = (ownerId: string): AssetResponseDto => {
avatarColor: 'blue' as never, avatarColor: 'blue' as never,
}, },
libraryId: `library-${ownerId}`, libraryId: `library-${ownerId}`,
deviceId: `device-${ownerId}`,
type: AssetTypeEnum.Image, type: AssetTypeEnum.Image,
originalPath: `/original/${assetId}.jpg`, originalPath: `/original/${assetId}.jpg`,
originalFileName: `${assetId}.jpg`, originalFileName: `${assetId}.jpg`,
@@ -42,7 +40,7 @@ export const createMockStackAsset = (ownerId: string): AssetResponseDto => {
isArchived: false, isArchived: false,
isTrashed: false, isTrashed: false,
visibility: AssetVisibility.Timeline, visibility: AssetVisibility.Timeline,
duration: '0:00:00.00000', duration: null,
exifInfo: { exifInfo: {
make: null, make: null,
model: null, model: null,
@@ -69,7 +67,7 @@ export const createMockStackAsset = (ownerId: string): AssetResponseDto => {
tags: [], tags: [],
people: [], people: [],
unassignedFaces: [], unassignedFaces: [],
stack: null, stack: undefined,
isOffline: false, isOffline: false,
hasMetadata: true, hasMetadata: true,
duplicateId: null, duplicateId: null,
@@ -349,7 +349,7 @@ test.describe('Timeline', () => {
expect(visibleMockAssetsYearMonths).toContain(month); expect(visibleMockAssetsYearMonths).toContain(month);
} }
}); });
test('Deep link to last photo, scroll up', async ({ page }) => { test.skip('Deep link to last photo, scroll up', async ({ page }) => {
const lastAsset = assets.at(-1)!; const lastAsset = assets.at(-1)!;
await pageUtils.deepLinkPhotosPage(page, lastAsset.id); await pageUtils.deepLinkPhotosPage(page, lastAsset.id);
@@ -361,7 +361,7 @@ test.describe('Timeline', () => {
await thumbnailUtils.expectInViewport(page, '14e5901f-fd7f-40c0-b186-4d7e7fc67968'); await thumbnailUtils.expectInViewport(page, '14e5901f-fd7f-40c0-b186-4d7e7fc67968');
}); });
test('Deep link to first bucket, scroll down', async ({ page }) => { test.skip('Deep link to first bucket, scroll down', async ({ page }) => {
const lastAsset = assets.at(0)!; const lastAsset = assets.at(0)!;
await pageUtils.deepLinkPhotosPage(page, lastAsset.id); await pageUtils.deepLinkPhotosPage(page, lastAsset.id);
await timelineUtils.locator(page).hover(); await timelineUtils.locator(page).hover();
@@ -440,7 +440,7 @@ test.describe('Timeline', () => {
await thumbnailUtils.expectInViewport(page, asset.id); await thumbnailUtils.expectInViewport(page, asset.id);
await thumbnailUtils.expectSelectedDisabled(page, asset.id); await thumbnailUtils.expectSelectedDisabled(page, asset.id);
}); });
test('Add photos to album', async ({ page }) => { test.skip('Add photos to album', async ({ page }) => {
const album = timelineRestData.album; const album = timelineRestData.album;
await pageUtils.openAlbumPage(page, album.id); await pageUtils.openAlbumPage(page, album.id);
await page.locator('nav button[aria-label="Add photos"]').click(); await page.locator('nav button[aria-label="Add photos"]').click();
@@ -752,7 +752,7 @@ test.describe('Timeline', () => {
await page.getByText('Photos', { exact: true }).click(); await page.getByText('Photos', { exact: true }).click();
await thumbnailUtils.expectInViewport(page, assetToFavorite.id); await thumbnailUtils.expectInViewport(page, assetToFavorite.id);
}); });
test('open /favorites, archive photo, unarchive photo', async ({ page }) => { test.skip('open /favorites, archive photo, unarchive photo', async ({ page }) => {
await pageUtils.openFavorites(page); await pageUtils.openFavorites(page);
const assetToArchive = getAsset(timelineRestData, 'ad31e29f-2069-4574-b9a9-ad86523c92cb')!; const assetToArchive = getAsset(timelineRestData, 'ad31e29f-2069-4574-b9a9-ad86523c92cb')!;
await thumbnailUtils.withAssetId(page, assetToArchive.id).hover(); await thumbnailUtils.withAssetId(page, assetToArchive.id).hover();
-41
View File
@@ -3,7 +3,6 @@ import {
AssetMediaResponseDto, AssetMediaResponseDto,
AssetResponseDto, AssetResponseDto,
AssetVisibility, AssetVisibility,
CheckExistingAssetsDto,
CreateAlbumDto, CreateAlbumDto,
CreateLibraryDto, CreateLibraryDto,
JobCreateDto, JobCreateDto,
@@ -20,7 +19,6 @@ import {
UserAdminCreateDto, UserAdminCreateDto,
UserPreferencesUpdateDto, UserPreferencesUpdateDto,
ValidateLibraryDto, ValidateLibraryDto,
checkExistingAssets,
createAlbum, createAlbum,
createApiKey, createApiKey,
createJob, createJob,
@@ -343,8 +341,6 @@ export const utils = {
}, },
) => { ) => {
const _dto = { const _dto = {
deviceAssetId: 'test-1',
deviceId: 'test',
fileCreatedAt: new Date().toISOString(), fileCreatedAt: new Date().toISOString(),
fileModifiedAt: new Date().toISOString(), fileModifiedAt: new Date().toISOString(),
...dto, ...dto,
@@ -375,40 +371,6 @@ export const utils = {
return body as AssetMediaResponseDto; return body as AssetMediaResponseDto;
}, },
replaceAsset: async (
accessToken: string,
assetId: string,
dto?: Partial<Omit<AssetMediaCreateDto, 'assetData'>> & { assetData?: FileData },
) => {
const _dto = {
deviceAssetId: 'test-1',
deviceId: 'test',
fileCreatedAt: new Date().toISOString(),
fileModifiedAt: new Date().toISOString(),
...dto,
};
const assetData = dto?.assetData?.bytes || makeRandomImage();
const filename = dto?.assetData?.filename || 'example.png';
if (dto?.assetData?.bytes) {
console.log(`Uploading ${filename}`);
}
const builder = request(app)
.put(`/assets/${assetId}/original`)
.attach('assetData', assetData, filename)
.set('Authorization', `Bearer ${accessToken}`);
for (const [key, value] of Object.entries(_dto)) {
void builder.field(key, String(value));
}
const { body } = await builder;
return body as AssetMediaResponseDto;
},
createImageFile: (path: string) => { createImageFile: (path: string) => {
if (!existsSync(dirname(path))) { if (!existsSync(dirname(path))) {
mkdirSync(dirname(path), { recursive: true }); mkdirSync(dirname(path), { recursive: true });
@@ -450,9 +412,6 @@ export const utils = {
getAssetInfo: (accessToken: string, id: string) => getAssetInfo({ id }, { headers: asBearerAuth(accessToken) }), getAssetInfo: (accessToken: string, id: string) => getAssetInfo({ id }, { headers: asBearerAuth(accessToken) }),
checkExistingAssets: (accessToken: string, checkExistingAssetsDto: CheckExistingAssetsDto) =>
checkExistingAssets({ checkExistingAssetsDto }, { headers: asBearerAuth(accessToken) }),
searchAssets: async (accessToken: string, dto: MetadataSearchDto) => { searchAssets: async (accessToken: string, dto: MetadataSearchDto) => {
return searchAssets({ metadataSearchDto: dto }, { headers: asBearerAuth(accessToken) }); return searchAssets({ metadataSearchDto: dto }, { headers: asBearerAuth(accessToken) });
}, },
+3 -1
View File
@@ -14,8 +14,10 @@
"outDir": "./dist", "outDir": "./dist",
"incremental": true, "incremental": true,
"skipLibCheck": true, "skipLibCheck": true,
"paths": {
"src/*": ["./src/*"]
},
"esModuleInterop": true, "esModuleInterop": true,
"baseUrl": "./"
}, },
"include": ["src/**/*.ts", "vitest*.config.ts"], "include": ["src/**/*.ts", "vitest*.config.ts"],
"exclude": ["dist", "node_modules"] "exclude": ["dist", "node_modules"]
+17 -8
View File
@@ -3,7 +3,7 @@
"account": "حساب", "account": "حساب",
"account_settings": "إعدادات الحساب", "account_settings": "إعدادات الحساب",
"acknowledge": "أُدرك ذلك", "acknowledge": "أُدرك ذلك",
"action": "عملية", "action": "إجراء",
"action_common_update": "تحديث", "action_common_update": "تحديث",
"action_description": "مجموعة من الفعاليات التي ستنفذ على الأصول التي تم تصفيتها", "action_description": "مجموعة من الفعاليات التي ستنفذ على الأصول التي تم تصفيتها",
"actions": "عمليات", "actions": "عمليات",
@@ -61,8 +61,8 @@
"backup_onboarding_1_description": "نسخة خارج الموقع في موقع آخر.", "backup_onboarding_1_description": "نسخة خارج الموقع في موقع آخر.",
"backup_onboarding_2_description": "نسخ محلية على أجهزة مختلفة. يشمل ذلك الملفات الرئيسية ونسخة احتياطية محلية منها.", "backup_onboarding_2_description": "نسخ محلية على أجهزة مختلفة. يشمل ذلك الملفات الرئيسية ونسخة احتياطية محلية منها.",
"backup_onboarding_3_description": "إجمالي نُسخ بياناتك، بما في ذلك الملفات الأصلية. يشمل ذلك نسخةً واحدةً خارج الموقع ونسختين محليتين.", "backup_onboarding_3_description": "إجمالي نُسخ بياناتك، بما في ذلك الملفات الأصلية. يشمل ذلك نسخةً واحدةً خارج الموقع ونسختين محليتين.",
"backup_onboarding_description": "يُنصح باتباع <backblaze-link>استراتيجية النسخ الاحتياطي 3-2-1</backblaze-link> لحماية بياناتك. احتفظ بنسخ احتياطية من صورك/فيديوهاتك المحمّلة، بالإضافة إلى قاعدة بيانات Immich، لضمان حل نسخ احتياطي شامل.", "backup_onboarding_description": "يُنصح باتباع <backblaze-link>استراتيجية النسخ الاحتياطي 3-2- 1</backblaze-link> لحماية بياناتك. احتفظ بنسخ احتياطية من صورك/فيديوهاتك المحمّلة، بالإضافة إلى قاعدة بيانات Immich، لضمان حل نسخ احتياطي شامل.",
"backup_onboarding_footer": "لمزيد من المعلومات حول النسخ الاحتياطي لـ Immich، يرجى الرجوع إلى <link> التعليمات </link>.", "backup_onboarding_footer": "لمزيد من المعلومات حول النسخ الاحتياطي لـ <link>Immich</link>، يرجى الرجوع إلى <link>الوثائق</link>.",
"backup_onboarding_parts_title": "يتضمن النسخ الاحتياطي 3-2-1 ما يلي:", "backup_onboarding_parts_title": "يتضمن النسخ الاحتياطي 3-2-1 ما يلي:",
"backup_onboarding_title": "النسخ الاحتياطية", "backup_onboarding_title": "النسخ الاحتياطية",
"backup_settings": "إعدادات تفريغ قاعدة البيانات", "backup_settings": "إعدادات تفريغ قاعدة البيانات",
@@ -333,7 +333,7 @@
"storage_template_migration_description": "قم بتطبيق القالب الحالي <link>{template}</link> على المحتويات التي تم رفعها سابقًا", "storage_template_migration_description": "قم بتطبيق القالب الحالي <link>{template}</link> على المحتويات التي تم رفعها سابقًا",
"storage_template_migration_info": "تغييرات النموذج الخزني ستغير جميع الصيغ الى احرف صغيرة. تغييرات النموذج ستنطبق فقط على المحتويات الجديدة. لتطبيق النموذج على المحتويات التي تم رفعها سابقًا، قم بتشغيل <link>{job}</link>.", "storage_template_migration_info": "تغييرات النموذج الخزني ستغير جميع الصيغ الى احرف صغيرة. تغييرات النموذج ستنطبق فقط على المحتويات الجديدة. لتطبيق النموذج على المحتويات التي تم رفعها سابقًا، قم بتشغيل <link>{job}</link>.",
"storage_template_migration_job": "وظيفة تهجير قالب التخزين", "storage_template_migration_job": "وظيفة تهجير قالب التخزين",
"storage_template_more_details": "لمزيد من التفاصيل حول هذه الميزة، يرجى الرجوع إلى <template-link>Storage Template</template-link> و<implications-link>implications</implications-link>", "storage_template_more_details": "لمزيد من التفاصيل حول هذه الميزة، يرجى الرجوع إلى <template-link>Storage Template</template-link> و <implications-link>implications</implications-link>.",
"storage_template_onboarding_description_v2": "عند التفعيل. هذه الخاصية ستقوم بالترتيب التلقائي للملفات بناء على نموذج معرف من قبل المستخدم. رجاء اطلع على <link>التوثيق</link>.", "storage_template_onboarding_description_v2": "عند التفعيل. هذه الخاصية ستقوم بالترتيب التلقائي للملفات بناء على نموذج معرف من قبل المستخدم. رجاء اطلع على <link>التوثيق</link>.",
"storage_template_path_length": "الحد التقريبي لطول المسار: <b>{length, number}</b>/{limit, number}", "storage_template_path_length": "الحد التقريبي لطول المسار: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "قالب التخزين", "storage_template_settings": "قالب التخزين",
@@ -372,7 +372,7 @@
"transcoding_audio_codec": "كود الصوت", "transcoding_audio_codec": "كود الصوت",
"transcoding_audio_codec_description": "Opus هو الخيار ذو أعلى جودة، ولكنه يتمتع بتوافق أقل مع الأجهزة أو البرمجيات القديمة.", "transcoding_audio_codec_description": "Opus هو الخيار ذو أعلى جودة، ولكنه يتمتع بتوافق أقل مع الأجهزة أو البرمجيات القديمة.",
"transcoding_bitrate_description": "مقاطع الفيديو التي يتجاوز معدل البت أقصى قيمة أو التي لا تكون في تنسيق مقبول", "transcoding_bitrate_description": "مقاطع الفيديو التي يتجاوز معدل البت أقصى قيمة أو التي لا تكون في تنسيق مقبول",
"transcoding_codecs_learn_more": "لمعرفة المزيد حول المصطلحات المستخدمة هنا، يرجى الرجوع إلى وثائق FFmpeg لل<h264-link>H.264 codec</h264-link>, <hevc-link>HEVC codec</hevc-link> and <vp9-link>VP9 codec</vp9-link>.", "transcoding_codecs_learn_more": "لمعرفة المزيد حول المصطلحات المستخدمة هنا، يرجى الرجوع إلى وثائق FFmpeg لـ <h264-link>H.264 codec</h264-link>، و <hevc-link>HEVC codec</hevc-link> و <vp9-link>VP9 codec</vp9-link>.",
"transcoding_constant_quality_mode": "وضع الجودة الثابتة", "transcoding_constant_quality_mode": "وضع الجودة الثابتة",
"transcoding_constant_quality_mode_description": "ICQ أفضل من CQP، ولكن بعض أجهزة عتاد التسريع لا تدعم هذا الوضع. تعيين هذا الخيار يسجعل الأفضلية للوضع المحدد عند استخدام الترميز بناءً على الجودة. يتم تجاهله بواسطة NVENC لأنه لا يدعم ICQ.", "transcoding_constant_quality_mode_description": "ICQ أفضل من CQP، ولكن بعض أجهزة عتاد التسريع لا تدعم هذا الوضع. تعيين هذا الخيار يسجعل الأفضلية للوضع المحدد عند استخدام الترميز بناءً على الجودة. يتم تجاهله بواسطة NVENC لأنه لا يدعم ICQ.",
"transcoding_constant_rate_factor": "عامل معدل الجودة الثابت (-crf)", "transcoding_constant_rate_factor": "عامل معدل الجودة الثابت (-crf)",
@@ -849,9 +849,12 @@
"create_link_to_share": "إنشاء رابط للمشاركة", "create_link_to_share": "إنشاء رابط للمشاركة",
"create_link_to_share_description": "السماح لأي شخص لديه الرابط بمشاهدة الصورة (الصور) المحددة", "create_link_to_share_description": "السماح لأي شخص لديه الرابط بمشاهدة الصورة (الصور) المحددة",
"create_new": "انشاء جديد", "create_new": "انشاء جديد",
"create_new_face": "إنشاء وجه جديد",
"create_new_person": "إنشاء شخص جديد", "create_new_person": "إنشاء شخص جديد",
"create_new_person_hint": "تعيين المحتويات المحددة لشخص جديد", "create_new_person_hint": "تعيين المحتويات المحددة لشخص جديد",
"create_new_user": "إنشاء مستخدم جديد", "create_new_user": "إنشاء مستخدم جديد",
"create_person": "إنشاء شخص",
"create_person_subtitle": "أضف اسماً للوجه المحدد لإنشاء الشخص الجديد والإشارة إليه",
"create_shared_album_page_share_add_assets": "إضافة الأصول", "create_shared_album_page_share_add_assets": "إضافة الأصول",
"create_shared_album_page_share_select_photos": "حدد الصور", "create_shared_album_page_share_select_photos": "حدد الصور",
"create_shared_link": "انشاء رابط مشترك", "create_shared_link": "انشاء رابط مشترك",
@@ -892,6 +895,8 @@
"day": "يوم", "day": "يوم",
"days": "ايام", "days": "ايام",
"deduplicate_all": "إلغاء تكرار الكل", "deduplicate_all": "إلغاء تكرار الكل",
"default_locale": "الإعدادات المحلية الافتراضية",
"default_locale_description": "تنسيق التواريخ والأرقام بناءً على الإعدادات المحلية للمتصفح",
"delete": "حذف", "delete": "حذف",
"delete_action_confirmation_message": "هل انت متأكد من حذف هذا الملف؟ هذا سؤدي الى نقل الملف الى سلة مهملات الخادم وسيتم اشعارك ان كنت تريد حذفه على الجهاز", "delete_action_confirmation_message": "هل انت متأكد من حذف هذا الملف؟ هذا سؤدي الى نقل الملف الى سلة مهملات الخادم وسيتم اشعارك ان كنت تريد حذفه على الجهاز",
"delete_action_prompt": "تم حذف {count}", "delete_action_prompt": "تم حذف {count}",
@@ -1384,9 +1389,11 @@
"library_page_sort_title": "عنوان الألبوم", "library_page_sort_title": "عنوان الألبوم",
"licenses": "رُخَص", "licenses": "رُخَص",
"light": "المضيئ", "light": "المضيئ",
"light_theme": "التبديل إلى المظهر الفاتح",
"like": "اعجاب", "like": "اعجاب",
"like_deleted": "تم حذف الإعجاب", "like_deleted": "تم حذف الإعجاب",
"link_motion_video": "رابط فيديو الحركة", "link_motion_video": "رابط فيديو الحركة",
"link_to_docs": "لمزيد من المعلومات، يُرجى الرجوع إلى <link>الوثائق</link>.",
"link_to_oauth": "الربط مع OAuth", "link_to_oauth": "الربط مع OAuth",
"linked_oauth_account": "حساب مرتبط بـ OAuth", "linked_oauth_account": "حساب مرتبط بـ OAuth",
"list": "قائمة", "list": "قائمة",
@@ -2210,6 +2217,7 @@
"tag": "العلامة", "tag": "العلامة",
"tag_assets": "أصول العلامة", "tag_assets": "أصول العلامة",
"tag_created": "تم إنشاء العلامة: {tag}", "tag_created": "تم إنشاء العلامة: {tag}",
"tag_face": "علِّم الوجه",
"tag_feature_description": "تصفح الصور ومقاطع الفيديو المجمعة حسب مواضيع العلامات المنطقية", "tag_feature_description": "تصفح الصور ومقاطع الفيديو المجمعة حسب مواضيع العلامات المنطقية",
"tag_not_found_question": "لا يمكن العثور على علامة؟ <link>قم بإنشاء علامة جديدة.</link>", "tag_not_found_question": "لا يمكن العثور على علامة؟ <link>قم بإنشاء علامة جديدة.</link>",
"tag_people": "علِّم الأشخاص", "tag_people": "علِّم الأشخاص",
@@ -2384,13 +2392,14 @@
"view_name": "عرض", "view_name": "عرض",
"view_next_asset": "عرض المحتوى التالي", "view_next_asset": "عرض المحتوى التالي",
"view_previous_asset": "عرض المحتوى السابق", "view_previous_asset": "عرض المحتوى السابق",
"view_qr_code": "­عرض رمز الاستجابة السريعة", "view_qr_code": "عرض رمز الاستجابة السريعة",
"view_similar_photos": "عرض صور مشابهة", "view_similar_photos": "عرض صور مشابهة",
"view_stack": "عرض التكديس", "view_stack": "عرض التكديس",
"view_user": "عرض المستخدم", "view_user": "عرض المستخدم",
"viewer_remove_from_stack": "حذف من الكومه أو المجموعة", "viewer_remove_from_stack": "حذف من الكومه أو المجموعة",
"viewer_stack_use_as_main_asset": "استخدم كأصل رئيسي", "viewer_stack_use_as_main_asset": "استخدم كأصل رئيسي",
"viewer_unstack": "فك الكومه", "viewer_unstack": "فك الكومه",
"visibility": "إمكانية الرؤية",
"visibility_changed": "الرؤية تغيرت لـ {count, plural, one {شخص واحد} other {# عدة أشخاص}}", "visibility_changed": "الرؤية تغيرت لـ {count, plural, one {شخص واحد} other {# عدة أشخاص}}",
"visual": "مرئي", "visual": "مرئي",
"visual_builder": "اداة نشاء مرئية", "visual_builder": "اداة نشاء مرئية",
@@ -2402,14 +2411,14 @@
"welcome_to_immich": "مرحباً بك في Immich", "welcome_to_immich": "مرحباً بك في Immich",
"width": "عُرض", "width": "عُرض",
"wifi_name": "اسم شبكة Wi-Fi", "wifi_name": "اسم شبكة Wi-Fi",
"workflow_delete_prompt": "هل أنت متأكد من حذف سير العمل هذا؟", "workflow_delete_prompt": "متأكد من حذف سير العمل هذا؟",
"workflow_deleted": "تم حذف سير العمل", "workflow_deleted": "تم حذف سير العمل",
"workflow_description": "وصف سير العمل", "workflow_description": "وصف سير العمل",
"workflow_info": "معلومات سير العمل", "workflow_info": "معلومات سير العمل",
"workflow_json": "ملف JSON لسير العمل", "workflow_json": "ملف JSON لسير العمل",
"workflow_json_help": "قم بتعديل إعدادات سير العمل بصيغة JSON. ستتم مزامنة التغييرات مع أداة الإنشاء المرئية.", "workflow_json_help": "قم بتعديل إعدادات سير العمل بصيغة JSON. ستتم مزامنة التغييرات مع أداة الإنشاء المرئية.",
"workflow_name": "اسم سير العمل", "workflow_name": "اسم سير العمل",
"workflow_navigation_prompt": "هل انت متاكد من المغادرة بدون حفظ التغييرات؟", "workflow_navigation_prompt": "متاكد من المغادرة بدون حفظ التغييرات؟",
"workflow_summary": "ملخص سير العمل", "workflow_summary": "ملخص سير العمل",
"workflow_update_success": "تم تحديث سير العمل بنجاح", "workflow_update_success": "تم تحديث سير العمل بنجاح",
"workflow_updated": "تم تحديث سير العمل", "workflow_updated": "تم تحديث سير العمل",
+4
View File
@@ -849,9 +849,12 @@
"create_link_to_share": "Създаване на линк за споделяне", "create_link_to_share": "Създаване на линк за споделяне",
"create_link_to_share_description": "Позволете на всеки, който има линк, да види избраната(ите) снимка(и)", "create_link_to_share_description": "Позволете на всеки, който има линк, да види избраната(ите) снимка(и)",
"create_new": "СЪЗДАЙ НОВ", "create_new": "СЪЗДАЙ НОВ",
"create_new_face": "Създай ново лице",
"create_new_person": "Създаване на ново лице", "create_new_person": "Създаване на ново лице",
"create_new_person_hint": "Присвойте избраните файлове на нов човек", "create_new_person_hint": "Присвойте избраните файлове на нов човек",
"create_new_user": "Създаване на нов потребител", "create_new_user": "Създаване на нов потребител",
"create_person": "Създай човек",
"create_person_subtitle": "Добави име към избраното лице за да създадеш и да сложиш етикет на новия човек",
"create_shared_album_page_share_add_assets": "ДОБАВИ ОБЕКТИ", "create_shared_album_page_share_add_assets": "ДОБАВИ ОБЕКТИ",
"create_shared_album_page_share_select_photos": "Избери снимки", "create_shared_album_page_share_select_photos": "Избери снимки",
"create_shared_link": "Създай линк за споделяне", "create_shared_link": "Създай линк за споделяне",
@@ -2214,6 +2217,7 @@
"tag": "Таг", "tag": "Таг",
"tag_assets": "Тагни елементи", "tag_assets": "Тагни елементи",
"tag_created": "Създаден етикет: {tag}", "tag_created": "Създаден етикет: {tag}",
"tag_face": "Отбележи лице",
"tag_feature_description": "Разглеждане на снимки и видеоклипове, групирани по теми с логически тагове", "tag_feature_description": "Разглеждане на снимки и видеоклипове, групирани по теми с логически тагове",
"tag_not_found_question": "Не можете да намерите етикет? <link>Създайте нов етикет.</link>", "tag_not_found_question": "Не можете да намерите етикет? <link>Създайте нов етикет.</link>",
"tag_people": "Отбележи Хора", "tag_people": "Отбележи Хора",
+4
View File
@@ -849,9 +849,12 @@
"create_link_to_share": "Crear enllaç per compartir", "create_link_to_share": "Crear enllaç per compartir",
"create_link_to_share_description": "Deixa que qualsevol persona amb l'enllaç vegi les fotos seleccionades", "create_link_to_share_description": "Deixa que qualsevol persona amb l'enllaç vegi les fotos seleccionades",
"create_new": "CREAR NOU", "create_new": "CREAR NOU",
"create_new_face": "Crea una nova cara",
"create_new_person": "Crea una nova persona", "create_new_person": "Crea una nova persona",
"create_new_person_hint": "Assigna els elements seleccionats a una persona nova", "create_new_person_hint": "Assigna els elements seleccionats a una persona nova",
"create_new_user": "Crea un usuari nou", "create_new_user": "Crea un usuari nou",
"create_person": "Crea una persona",
"create_person_subtitle": "Afegeix un nom a la cara seleccionada per crear i etiquetar la nova persona",
"create_shared_album_page_share_add_assets": "AFEGEIX ELEMENTS", "create_shared_album_page_share_add_assets": "AFEGEIX ELEMENTS",
"create_shared_album_page_share_select_photos": "Escull fotografies", "create_shared_album_page_share_select_photos": "Escull fotografies",
"create_shared_link": "Crea un enllaç compartit", "create_shared_link": "Crea un enllaç compartit",
@@ -2214,6 +2217,7 @@
"tag": "Etiqueta", "tag": "Etiqueta",
"tag_assets": "Etiquetar actius", "tag_assets": "Etiquetar actius",
"tag_created": "Etiqueta creada: {tag}", "tag_created": "Etiqueta creada: {tag}",
"tag_face": "Etiqueta una cara",
"tag_feature_description": "Exploreu fotos i vídeos agrupats per temes d'etiquetes lògiques", "tag_feature_description": "Exploreu fotos i vídeos agrupats per temes d'etiquetes lògiques",
"tag_not_found_question": "No trobeu una etiqueta? <link>Crear una nova etiqueta.</link>", "tag_not_found_question": "No trobeu una etiqueta? <link>Crear una nova etiqueta.</link>",
"tag_people": "Etiquetar personas", "tag_people": "Etiquetar personas",
+5
View File
@@ -849,9 +849,12 @@
"create_link_to_share": "Opret link for at dele", "create_link_to_share": "Opret link for at dele",
"create_link_to_share_description": "Tillad alle med linket at se de(t) valgte billede(r)", "create_link_to_share_description": "Tillad alle med linket at se de(t) valgte billede(r)",
"create_new": "OPRET NY", "create_new": "OPRET NY",
"create_new_face": "Opret nyt ansigt",
"create_new_person": "Opret ny person", "create_new_person": "Opret ny person",
"create_new_person_hint": "Tildel valgte aktiver til en ny person", "create_new_person_hint": "Tildel valgte aktiver til en ny person",
"create_new_user": "Opret ny bruger", "create_new_user": "Opret ny bruger",
"create_person": "Opret person",
"create_person_subtitle": "Tilføj et navn til det valgte ansigt for at oprette og tagge den nye person",
"create_shared_album_page_share_add_assets": "TILFØJ ELEMENT", "create_shared_album_page_share_add_assets": "TILFØJ ELEMENT",
"create_shared_album_page_share_select_photos": "Vælg Billeder", "create_shared_album_page_share_select_photos": "Vælg Billeder",
"create_shared_link": "Opret delt link", "create_shared_link": "Opret delt link",
@@ -892,6 +895,7 @@
"day": "Dag", "day": "Dag",
"days": "Dage", "days": "Dage",
"deduplicate_all": "Dedubliker alle", "deduplicate_all": "Dedubliker alle",
"default_locale": "Standard sprog",
"default_locale_description": "Formatér datoer og tal baseret på din browsers landestandard", "default_locale_description": "Formatér datoer og tal baseret på din browsers landestandard",
"delete": "Slet", "delete": "Slet",
"delete_action_confirmation_message": "Er du sikker på, at du vil slette dette objekt? Denne handling vil flytte objektet til serverens papirkurv, og vil spørge dig, om du vil slette den lokalt", "delete_action_confirmation_message": "Er du sikker på, at du vil slette dette objekt? Denne handling vil flytte objektet til serverens papirkurv, og vil spørge dig, om du vil slette den lokalt",
@@ -2213,6 +2217,7 @@
"tag": "Tag", "tag": "Tag",
"tag_assets": "Tag mediefiler", "tag_assets": "Tag mediefiler",
"tag_created": "Oprettet tag: {tag}", "tag_created": "Oprettet tag: {tag}",
"tag_face": "Tag ansigt",
"tag_feature_description": "Gennemse billeder og videoer grupperet efter logiske tag-emner", "tag_feature_description": "Gennemse billeder og videoer grupperet efter logiske tag-emner",
"tag_not_found_question": "Kan du ikke finde et tag? <link>Opret et nyt tag.</link>", "tag_not_found_question": "Kan du ikke finde et tag? <link>Opret et nyt tag.</link>",
"tag_people": "Tag personer", "tag_people": "Tag personer",
+8 -4
View File
@@ -541,7 +541,7 @@
"app_settings": "App-Einstellungen", "app_settings": "App-Einstellungen",
"app_stores": "App Stores", "app_stores": "App Stores",
"app_update_available": "App Update verfügbar", "app_update_available": "App Update verfügbar",
"appears_in": "Erscheint in", "appears_in": "Enthalten in",
"apply_count": "Anwenden ({count, number})", "apply_count": "Anwenden ({count, number})",
"archive": "Archiv", "archive": "Archiv",
"archive_action_prompt": "{count} zum Archiv hinzugefügt", "archive_action_prompt": "{count} zum Archiv hinzugefügt",
@@ -812,8 +812,8 @@
"confirm_keep_this_delete_others": "Alle anderen Dateien im Stapel bis auf diese werden gelöscht. Bist du sicher, dass du fortfahren möchten?", "confirm_keep_this_delete_others": "Alle anderen Dateien im Stapel bis auf diese werden gelöscht. Bist du sicher, dass du fortfahren möchten?",
"confirm_new_pin_code": "Neuen PIN-Code bestätigen", "confirm_new_pin_code": "Neuen PIN-Code bestätigen",
"confirm_password": "Passwort bestätigen", "confirm_password": "Passwort bestätigen",
"confirm_tag_face": "Wollen Sie dieses Gesicht mit {name} markieren?", "confirm_tag_face": "Wollen Sie dieses Gesicht mit {name} taggen?",
"confirm_tag_face_unnamed": "Möchten Sie dieses Gesicht markieren?", "confirm_tag_face_unnamed": "Möchten Sie dieses Gesicht taggen?",
"connected_device": "Verbundenes Gerät", "connected_device": "Verbundenes Gerät",
"connected_to": "Verbunden mit", "connected_to": "Verbunden mit",
"contain": "Vollständig", "contain": "Vollständig",
@@ -849,9 +849,12 @@
"create_link_to_share": "Link zum Teilen erstellen", "create_link_to_share": "Link zum Teilen erstellen",
"create_link_to_share_description": "Lass jeden mit dem Link die ausgewählten Fotos sehen", "create_link_to_share_description": "Lass jeden mit dem Link die ausgewählten Fotos sehen",
"create_new": "NEUES ERSTELLEN", "create_new": "NEUES ERSTELLEN",
"create_new_face": "Neues Gesicht erstellen",
"create_new_person": "Neue Person anlegen", "create_new_person": "Neue Person anlegen",
"create_new_person_hint": "Ausgewählte Dateien einer neuen Person zuweisen", "create_new_person_hint": "Ausgewählte Dateien einer neuen Person zuweisen",
"create_new_user": "Neuen Nutzer erstellen", "create_new_user": "Neuen Nutzer erstellen",
"create_person": "Person anlegen",
"create_person_subtitle": "Gib dem gewählten Gesicht einen Namen um die neue Person zu erstellen und zu taggen",
"create_shared_album_page_share_add_assets": "INHALTE HINZUFÜGEN", "create_shared_album_page_share_add_assets": "INHALTE HINZUFÜGEN",
"create_shared_album_page_share_select_photos": "Fotos auswählen", "create_shared_album_page_share_select_photos": "Fotos auswählen",
"create_shared_link": "Geteilten Link erstellen", "create_shared_link": "Geteilten Link erstellen",
@@ -1035,7 +1038,7 @@
"error_loading_partners": "Fehler beim Laden der Partner: {error}", "error_loading_partners": "Fehler beim Laden der Partner: {error}",
"error_retrieving_asset_information": "Fehler beim Abruf der Dateiinformationen", "error_retrieving_asset_information": "Fehler beim Abruf der Dateiinformationen",
"error_saving_image": "Fehler: {error}", "error_saving_image": "Fehler: {error}",
"error_tag_face_bounding_box": "Fehler beim Markieren des Gesichts - Begrenzungen können nicht abgerufen werden", "error_tag_face_bounding_box": "Fehler beim Taggen des Gesichts - Begrenzungen können nicht abgerufen werden",
"error_title": "Fehler - Etwas ist schief gelaufen", "error_title": "Fehler - Etwas ist schief gelaufen",
"error_while_navigating": "Fehler beim Navigieren zur Datei", "error_while_navigating": "Fehler beim Navigieren zur Datei",
"errors": { "errors": {
@@ -2214,6 +2217,7 @@
"tag": "Tag", "tag": "Tag",
"tag_assets": "Dateien taggen", "tag_assets": "Dateien taggen",
"tag_created": "Tag erstellt: {tag}", "tag_created": "Tag erstellt: {tag}",
"tag_face": "Gesicht taggen",
"tag_feature_description": "Durchsuchen von Fotos und Videos, gruppiert nach logischen Tag-Themen", "tag_feature_description": "Durchsuchen von Fotos und Videos, gruppiert nach logischen Tag-Themen",
"tag_not_found_question": "Kein Tag vorhanden? <link>Erstelle einen neuen Tag.</link>", "tag_not_found_question": "Kein Tag vorhanden? <link>Erstelle einen neuen Tag.</link>",
"tag_people": "Personen taggen", "tag_people": "Personen taggen",
+111 -111
View File
@@ -1,132 +1,132 @@
{ {
"about": "Über", "about": "Über",
"account": "Konto", "account": "Konto",
"account_settings": "Konto Istelligä", "account_settings": "Konto Einstellungen",
"acknowledge": "Bestätige", "acknowledge": "Bestätigä",
"action": "Aktion", "action": "Aktion",
"action_common_update": "Update", "action_common_update": "Update",
"action_description": "Es paar Aktione, wo a de gfilterete Assets usgführt wärde sölled", "action_description": "Aktionä, wo uf de gefilterti Mediä ausgführt werdä solled",
"actions": "Aktione", "actions": "Aktionen",
"active": "Aktiv", "active": "Aktiv",
"active_count": "Aktivi: {count}", "active_count": "Aktiv: {count}",
"activity": "Aktivität", "activity": "Aktivität",
"activity_changed": "Aktivität isch {enabled, select, true {aktiviert} other {deaktiviert}}", "activity_changed": "Aktivität ist {enabled, select, true {aktiviert} other {deaktiviert}}",
"add": "Hinzuefüe", "add": "Hinzuefüge",
"add_a_description": "Beschriibig hinzueege", "add_a_description": "Beschreibung hinzufügen",
"add_a_location": "Standort hinzuefüege", "add_a_location": "Standort hinzuefü",
"add_a_name": "Name hinzuefüege", "add_a_name": "Namä hinzefü",
"add_a_title": "Titel hinzuefüege", "add_a_title": "Titel hinzufeügä",
"add_action": "Aktion hinzuefüege", "add_action": "Aktion hinzuefü",
"add_action_description": "Aklicke um en Aktion dure zfüehre", "add_action_description": "Klick do zum e Aktion hinzuefüge",
"add_assets": "Assets hinzufüege", "add_assets": "Mediä hinzuefüge",
"add_birthday": "Geburtstag hinzuefüege", "add_birthday": "Geburtstag hinzuefüge",
"add_endpoint": "Endpunkt hinzuefüge", "add_endpoint": "Endpunkt hinzuefüge",
"add_exclusion_pattern": "Uuschlussmuster hinzueege", "add_exclusion_pattern": "Ausschlussmuster hinzufügen",
"add_filter": "Filter hinzuefüge", "add_filter": "Filter hinzufügen",
"add_filter_description": "Klicke, um e Filterbedingig hinzuezfüege", "add_filter_description": "Klicke hier um eine Filterbedingung hinzuzufügen",
"add_location": "Standort hinzueege", "add_location": "Standort hinzufügen",
"add_more_users": "Meh Benutzer hinzueege", "add_more_users": "Mehr Benutzer hinzufügen",
"add_partner": "Partner hinzueege", "add_partner": "Partner hinzufügen",
"add_path": "Pfad hinzueege", "add_path": "Pfad hinzufügen",
"add_photos": "Föteli hinzueege", "add_photos": "Fotos hinzufügen",
"add_tag": "Tag hinzueege", "add_tag": "Tag hinzufügen",
"add_to": "Hinzueege zu …", "add_to": "Hinzufügen zu…",
"add_to_album": "Zum Album hinzueege", "add_to_album": "Zu Album hinzufügen",
"add_to_album_bottom_sheet_added": "Zu {album} hinzuegfüegt", "add_to_album_bottom_sheet_added": "Zu {album} hinzugefügt",
"add_to_album_bottom_sheet_already_exists": "Scho in {album}", "add_to_album_bottom_sheet_already_exists": "Bereits in {album}",
"add_to_album_bottom_sheet_some_local_assets": "Es hend es paar lokali Dateie nöd chöne im Album hinzuegfüegt werde", "add_to_album_bottom_sheet_some_local_assets": "Einige lokale Dateien konnten nicht zum Album hinzugefügt werden",
"add_to_album_toggle": "Uuswahl umschalte für {album}", "add_to_album_toggle": "Auswahl umschalten für {album}",
"add_to_albums": "Zu Albe hinzueege", "add_to_albums": "Zu Alben hinzufügen",
"add_to_albums_count": "Zu Albe hinzueege ({count})", "add_to_albums_count": "Zu Alben hinzufügen ({count})",
"add_to_bottom_bar": "Hinzueege zu", "add_to_bottom_bar": "Hinzufügen zu",
"add_to_shared_album": "Zum teilte Album hinzueege", "add_to_shared_album": "Zu geteiltem Album hinzufügen",
"add_upload_to_stack": "Upload zum Stack hinzueege", "add_upload_to_stack": "Upload zum Stapel hinzufügen",
"add_url": "URL hinzueege", "add_url": "URL hinzufügen",
"add_workflow_step": "Workflow-Schritt hinzueege", "add_workflow_step": "Workflow-Schritt hinzufügen",
"added_to_archive": "Is Archiv verschobe", "added_to_archive": "Zum Archiv hinzugefügt",
"added_to_favorites": "Zu dine Favoritä hinzuegfüegt", "added_to_favorites": "Zu Favoriten hinzugefügt",
"added_to_favorites_count": "{count, number} zu Favorite hinzuegfüegt", "added_to_favorites_count": "{count, number} zu Favoriten hinzugefügt",
"admin": { "admin": {
"add_exclusion_pattern_description": "Uusschlussmuster hinzuefüge. Platzhalter, wie *, **, und ? wärded understützt. Zum all Dateie i eim Verzeichnis namens „Raw\" ignoriere, „**/Raw/**“ verwände. Zum all Dateien ignorieren, wo uf „.tif“ änded, „**/*.tif“ verwände. Zum en absolute Pfad ignoriere, „/pfad/zum/ignoriere/**“ verwände.", "add_exclusion_pattern_description": "Ausschlussmuster hinzufügen. Platzhalter, wie *, **, und ? werden unterstützt. Um alle Dateien in einem Verzeichnis namens „Raw“ zu ignorieren, „**/Raw/**“ verwenden. Um alle Dateien zu ignorieren, die auf „.tif“ enden, „**/*.tif“ verwenden. Um einen absoluten Pfad zu ignorieren, „/pfad/zum/ignorieren/**“ verwenden.",
"admin_user": "Admin Benutzer", "admin_user": "Administrator",
"asset_offline_description": "Die Datei vonere externe Bibliothek isch nümme uf de Festplatte und isch in Papierchorb verschobe worde. Falls die Datei innerhalb vo de Bibliothek verschoben worde isch, überprüf dini Ziitleiste uf die neui entsprechendi Datei. Zum die Datei wiederherstelle, stell bitte sicher, dass Immich uf de unde stehendi Dateipfad chan zuegriife und scann d'Bibliothek.", "asset_offline_description": "Diese Datei einer externen Bibliothek befindet sich nicht mehr auf der Festplatte und wurde in den Papierkorb verschoben. Falls die Datei innerhalb der Bibliothek verschoben wurde, überprüfe deine Zeitleiste auf die neue entsprechende Datei. Um diese Datei wiederherzustellen, stelle bitte sicher, dass Immich auf den unten stehenden Dateipfad zugreifen kann und scanne die Bibliothek.",
"authentication_settings": "Authentifizierigs Iistellige", "authentication_settings": "Authentifizierungseinstellungen",
"authentication_settings_description": "Passwort, OAuth und anderi Authentifizierigseinstellige verwalte", "authentication_settings_description": "Passwort-, OAuth- und andere Authentifizierungseinstellungen verwalten",
"authentication_settings_disable_all": "Bisch sicher, dass du alli Login-Methodä wotsch deaktivierä? S Login isch denn komplett deaktiviert.", "authentication_settings_disable_all": "Bist du sicher, dass du alle Loginmethoden deaktivieren willst? Die Anmeldung wird vollständig deaktiviert.",
"authentication_settings_reenable": "Bruuch ein <link>Server-Befehl</link> zum reaktiviere.", "authentication_settings_reenable": "Nutze einen <link>Server-Befehl</link> zur Reaktivierung.",
"background_task_job": "Hintergrund Ufgabä", "background_task_job": "Hintergrundaufgaben",
"backup_database": "Datenbank-Dump aalege", "backup_database": "Datenbanksicherung erstellen",
"backup_database_enable_description": "Datenbank-Dumps aktiviere", "backup_database_enable_description": "Datenbank regelmässig sichern",
"backup_keep_last_amount": "Aazahl vo de vorherige Dumps, wo bhalte werde sölle", "backup_keep_last_amount": "Anzahl der aufzubewahrenden früheren Sicherungen",
"backup_onboarding_1_description": "Offsite-Kopie i dä Cloud oder amene andere physische Standort.", "backup_onboarding_1_description": "Offsite-Kopie in der Cloud oder an einem anderen physischen Ort.",
"backup_onboarding_2_description": "Lokali Kopie uf verschiedene Grät. Das beinhaltet d Hauptdateie und e lokali Sicherig vo dene Dateie.", "backup_onboarding_2_description": "lokale Kopien auf verschiedenen Geräten. Dazu gehören die Hauptdateien und eine lokale Sicherung dieser Dateien.",
"backup_onboarding_3_description": "Total aazahl vo dine Dateikopie, inklusiv d Originaldateie. Das beinhaltet 1 Offsite-Kopie und 2 lokali Kopie.", "backup_onboarding_3_description": "Kopien deiner Daten inklusive Originaldateien. Dies umfasst 1 Kopie an einem anderen Ort und 2 lokale Kopien.",
"backup_onboarding_description": "E <backblaze-link>3-2-1-Backup-Strategie</backblaze-link> wird empfohle, zum dini Dateie z schütze. Du söttsch sowohl Kopie vo dine ufgeladene Fotos/Videos wie au d Immich-Datenbank bhalte, für e rundum sauberi Backup-Lösig.", "backup_onboarding_description": "Eine <backblaze-link>3-2-1 Sicherungsstrategie</backblaze-link> wird empfohlen, um deine Daten zu schützen. Du solltest sowohl Kopien deiner hochgeladenen Fotos/Videos als auch der Immich-Datenbank aufbewahren, um eine umfassende Sicherungslösung zu haben.",
"backup_onboarding_footer": "Für meh Infos zum Backup vo Immich lueg bitte i d <link>Dokumentation</link>.", "backup_onboarding_footer": "Weitere Informationen zum Sichern von Immich findest du in der <link>Dokumentation</link>.",
"backup_onboarding_parts_title": "Es 3-2-1-Backup beinhaltet:", "backup_onboarding_parts_title": "Eine 3-2-1-Sicherung umfasst:",
"backup_onboarding_title": "Backups", "backup_onboarding_title": "Backups",
"backup_settings": "Iistellige für Datenbank-Dumps", "backup_settings": "Einstellungen für Datenbanksicherung",
"backup_settings_description": "Datenbank-Dump-Iistellige verwalte.", "backup_settings_description": "Einstellungen zur regelmässigen Sicherung der Datenbank.",
"cleared_jobs": "Jobs glöscht für: {job}", "cleared_jobs": "Folgende Aufgaben zurückgesetzt: {job}",
"config_set_by_file": "D Konfiguration isch aktuell dur e Konfigurationsdatei gsetzt", "config_set_by_file": "Die Konfiguration ist aktuell durch eine Konfigurationsdatei gsetzt",
"confirm_delete_library": "Bisch sicher, dass du d Bibliothek {library} wotsch lösche?", "confirm_delete_library": "Bist du sicher, dass du die Bibliothek {library} löschen willst?",
"confirm_delete_library_assets": "Bisch sicher, dass du die Bibliothek wotsch lösche? Das löscht {count, plural, one {# enthaltenes Asset} other {alli # enthaltene Assets}} us Immich und chan nöd rückgängig gmacht werde. D Dateie bliibed uf em Dateträger.", "confirm_delete_library_assets": "Bist du sicher, dass du diese Bibliothek löschen willst? Dies löscht {count, plural, one {# enthaltene Datei} other {alle # enthaltenen Dateien}} aus Immich und kann nicht rückgängig gemacht werden. Die Dateien bleiben auf der Festplatte erhalten.",
"confirm_email_below": "Zum bestätige bitte \"{email}\" une iitippe", "confirm_email_below": "Zum Bestätigen, tippe unten \"{email}\" ein",
"confirm_reprocess_all_faces": "Bisch sicher, dass du alli Gsichter neu verarbeite wotsch? Däbii werde au benannti Persone glöscht.", "confirm_reprocess_all_faces": "Bist du sicher, dass du alle Gesichter erneut verarbeiten möchtest? Dies löscht auch alle bereits benannten Personen.",
"confirm_user_password_reset": "Bisch sicher, dass du s Passwort für {user} möchtisch zruggsetze?", "confirm_user_password_reset": "Bist du sicher, dass du das Passwort für {user} zurücksetzen möchtest?",
"confirm_user_pin_code_reset": "Bisch sicher, dass du de PIN-Code vo {user} möchtisch zruggsetze?", "confirm_user_pin_code_reset": "Bist du sicher, dass du den PIN-Code von {user} zurücksetzen möchtest?",
"copy_config_to_clipboard_description": "Kopier die aktuelli Systemkonfiguration als JSON-Objekt i d'Zwüschenablage", "copy_config_to_clipboard_description": "Aktuelle Systemkonfiguration als JSON-Objekt in die Zwischenablage kopieren",
"create_job": "Uufgabe erstelle", "create_job": "Aufgabe erstellen",
"cron_expression": "Cron-Ziitagabe", "cron_expression": "Cron-Ausdruck",
"cron_expression_description": "Setz s Scanintervall im Cron-Format. Hilf mit däm Format bütet z. B. der <link>Crontab Guru</link>", "cron_expression_description": "Setze das Scanintervall im Cron-Format. Für mehr Informationen, siehe z. B. <link>Crontab Guru</link>",
"cron_expression_presets": "Vorlage für Cron-Uusdruck", "cron_expression_presets": "Vorlagen für Cron-Ausdrücke",
"disable_login": "Login deaktiviere", "disable_login": "Login deaktivieren",
"duplicate_detection_job_description": "Die Uufgab füehrt s maschinelle Lärne für jedi Datei us, zum Duplikat finde. Die Uufgabe berueht uf de intelligente Suechi", "duplicate_detection_job_description": "Verwendet maschinelles Lernen auf den Dateien, um Duplikate zu finden. Baut auf der intelligenten Suche auf",
"exclusion_pattern_description": "Mit Uusschlussmuster chönnd Dateie und Ordner bim Scanne vo dinere Bibliothek ignoriert wärde. Das isch nützlich, wenn du Ordner häsch, wo Dateien drin händ, wo d nöd wotsch importiere, wie z. B. RAW-Dateie.", "exclusion_pattern_description": "Mit Ausschlussmustern können Dateien und Ordner beim Scannen deiner Bibliothek ignoriert werden. Dies ist nützlich, wenn du Ordner hast, die Dateien enthalten, die du nicht importieren möchtest, wie z. B. RAW-Dateien.",
"export_config_as_json_description": "Lad die aktuelli Systemkonfiguration als JSON-Datei abe", "export_config_as_json_description": "Aktuelle Systemkonfiguration als JSON-Datei herunterladen",
"external_libraries_page_description": "Externi Bibliothekssiite für Administratore", "external_libraries_page_description": "Externe Bibliotheksseite für Administratoren",
"face_detection": "Gsichtserkennig", "face_detection": "Gesichtserkennung",
"face_detection_description": "Die Uufgab erfasst Gsichter in Dateien dur maschinells Lerne. Bi Video wird nur d'Miniaturasicht brucht. „Aktualisiere“ verarbeitet all Dateie neu. „Zruggsetze“ setzt au no all Gsichter zrugg. „Fehlendistellt nur nöd verarbeiteti Dateie in d'Warteschlange. Erfassti Gsichter wärdet zur Gsichtsidentifizierig in diWarteschlange gstellt, damit sie i bestehendi oder neui Persone z'gruppiere.", "face_detection_description": "Diese Aufgabe erkennt mit maschinellem Lernen Gesichter in Dateien. Bei Videos wird nur das Vorschaubild verwendet. „Aktualisieren“ verarbeitet alle Dateien neu. „Zurücksetzen“ setzt zusätzlich alle Gesichter zurück. „Fehlendefügt nur nicht verarbeitete Dateien in die Warteschlange ein. Erfasste Gesichter werden zur Gesichtsidentifizierung in die Warteschlange eingefügt, um sie in bestehende oder neue Personen zu gruppieren.",
"facial_recognition_job_description": "Die Uufgabe gruppiert im Anschluss an d'Gsichtserfassig die erfasste Gsichter zu Persone. „Zruggsetze“ gruppiert alli Gsichter neu und mit „Fehlendi“ werdet Gsichter ohni Zuordnig i d'Warteschlange gstellt.", "facial_recognition_job_description": "Diese Aufgabe gruppiert im Anschluss an die Gesichtserkennung die erkannten Gesichter zu Personen. „Zurücksetzen“ gruppiert alle Gesichter neu, während „Fehlende“ Gesichter ohne Zuordnung in die Warteschlange stellt.",
"failed_job_command": "Befehl {command} t für d'Uufgabe {job} nöd funktioniert", "failed_job_command": "Befehl {command} ist für Aufgabe {job} fehlgeschlagen",
"force_delete_user_warning": "WARNIG: Die Aktion löscht Benutzer und all sini Dateie. Das chann nöd rückgängig gmacht wärde und d'Dateie chönnd nöd wiederhergstellt wärde.", "force_delete_user_warning": "WARNUNG: Diese Aktion löscht sofort den Benutzer und all seine Dateien. Dies kann nicht rückgängig gemacht werden und die Dateien können nicht wiederhergestellt werden.",
"image_format": "Format", "image_format": "Format",
"image_format_description": "WebP erzeugt chlineri Dateie we JPEG, isch aber es bitz langsamer i de Erstellig.", "image_format_description": "WebP erzeugt kleinere Dateien als JPEG, ist aber etwas langsamer in der Erstellung.",
"image_fullsize_description": "Hochuflösends Bild mit glöschte Metadate, wo bim Zoome brucht wird", "image_fullsize_description": "Hochauflösendes Bild mit entfernten Metadaten, das beim Zoomen verwendet wird",
"image_fullsize_enabled": "Hochuflösendi Vorschaubilder aktiviere", "image_fullsize_enabled": "Hochauflösende Vorschaubilder aktivieren",
"image_fullsize_enabled_description": "Generiere hochauflösende Vorschaubilder in Originalauflösung für nicht web-kompatibel Formate. Wenn \"Eingebettete Vorschau bevorzugen\" aktiviert ist, werden eingebettete Vorschaubilder direkt verwendet. Hat keinen Einfluss auf web-kompatible Formate wie JPEG.", "image_fullsize_enabled_description": "Generiere Vorschaubilder in Originalauflösung für nicht web-kompatible Formate. Wenn \"Eingebettete Vorschau bevorzugen\" aktiviert ist, werden eingebettete Vorschaubilder direkt verwendet. Hat keinen Einfluss auf web-kompatible Formate wie JPEG.",
"image_fullsize_quality_description": "Qualität der hochauflösenden Vorschaubilder von 1-100. Höher ist besser, erzeugt aber grössere Dateien.", "image_fullsize_quality_description": "Qualität der hochauflösenden Vorschaubilder von 1-100. Höher ist besser, erzeugt aber grössere Dateien.",
"image_fullsize_title": "Hochauflösende Vorschaueinstellungen", "image_fullsize_title": "Hochauflösende Vorschaueinstellungen",
"image_prefer_embedded_preview": "Eingebettete Vorschau bevorzugen", "image_prefer_embedded_preview": "Eingebettete Vorschau bevorzugen",
"image_prefer_embedded_preview_setting_description": "Verwende eingebettete Vorschaubilder in RAW-Fotos als Grundlage für die Bildverarbeitung, sofern diese zur Verfügung stehen. Dies kann bei einigen Bildern genauere Farben erzeugen, allerdings ist die Qualität der Vorschau kameraabhängig und das Bild kann mehr Kompressionsartefakte aufweisen.", "image_prefer_embedded_preview_setting_description": "Verwende eingebettete Vorschaubilder in RAW-Fotos als Grundlage für die Bildverarbeitung, sofern diese zur Verfügung stehen. Dies kann bei einigen Bildern genauere Farben erzeugen, allerdings ist die Qualität der Vorschau kameraabhängig und das Bild kann mehr Kompressionsartefakte aufweisen.",
"image_prefer_wide_gamut": "Breites Spektrum bevorzugen", "image_prefer_wide_gamut": "Breites Spektrum bevorzugen",
"image_prefer_wide_gamut_setting_description": "Bruuch Display P3 für Vorschaubildli. Das erhaltet d'Vitalität von Bildli mit grossem Farbruum besser. Uf alte Grät mit alte Browser chann das aber andersch uusgseh. sRGB-Bildli wärdet als sRGB bhalte zum Farbänderige vermiide.", "image_prefer_wide_gamut_setting_description": "Display P3 (DCI-P3) für Vorschaubilder verwenden. Dadurch bleibt die Lebendigkeit von Bildern mit breiten Farbräumen besser erhalten, aber die Bilder können auf älteren Geräten mit einer älteren Browserversion etwas anders aussehen. sRGB-Bilder werden im sRGB-Format belassen, um Farbverschiebungen zu vermeiden.",
"image_preview_description": "Mittelgrossi Bildli ohni Metadate, bruuchts für Einzelaasichte und fürs maschinelle Lärne", "image_preview_description": "Mittelgrosses Bild mit entfernten Metadaten, das bei der Betrachtung einer einzelnen Datei und für maschinelles Lernen verwendet wird",
"image_preview_quality_description": "Vorschauqualität vo 1-100. Höcher isch besser, git aber grösseri Dateie und chan d'App Schwuppdizität reduziere. Z tüffi Wert chönnd s maschinelle Lärne beiträchtige.", "image_preview_quality_description": "Vorschauqualität von 1-100. Ein höherer Wert ist besser, erzeugt dadurch aber grössere Dateien und kann die Reaktionsfähigkeit der App beeinträchtigen. Ein niedriger Wert kann dafür aber die Qualität des maschinellen Lernens beeinträchtigen.",
"image_preview_title": "Vorschauiistellige", "image_preview_title": "Vorschaueinstellungen",
"image_progressive": "Fortlaufend", "image_progressive": "Fortlaufend",
"image_progressive_description": "Codier fortlaufendi JPEG-Bildi: Sie wärdet bim Lade aufbauend aazeiget. Das hät kei Würkig uf WebP-Bildi.", "image_progressive_description": "JPEG-Bilder schrittweise kodieren, um ein stufenweises Laden zu ermöglichen. Dies hat keine Auswirkungen auf WebP-Bilder.",
"image_quality": "Qualität", "image_quality": "Qualität",
"image_resolution": "Uuflösig", "image_resolution": "Auflösung",
"image_resolution_description": "Höcheri Uuflösig erhaltet meh Detail, gaht aber länger zum codiere, macht grösseri Dateie und chan d'App Schuppdizität reduziere.", "image_resolution_description": "Höhere Auflösungen können mehr Details erhalten, benötigen aber mehr Zeit für die Kodierung, haben grössere Dateigrössen und können die Reaktionsfähigkeit der App beeinträchtigen.",
"image_settings": "Bild-Iistellige", "image_settings": "Bildeinstellungen",
"image_settings_description": "Qualität und Uuflösig von erstellte Bildli verwalte", "image_settings_description": "Qualität und Auflösung der generierten Bilder verwalten",
"image_thumbnail_description": "Chlini Vorschaubildli ohni Metadate, bruuchts für Aasichte mit Gruppe vo Föteli wie i de Hauptziitachse", "image_thumbnail_description": "Kleines Vorschaubild mit entfernten Metadaten, die bei der Anzeige von Sammlungen von Fotos wie der Zeitleiste verwendet wird",
"image_thumbnail_quality_description": "Vorschauqualität vo 1-100. Höcher isch besser, git aber grösseri Dateie und chan d'App Schwuppdizität reduziere.", "image_thumbnail_quality_description": "Qualität der Vorschaubilder von 1-100. Höher ist besser, erzeugt aber grössere Dateien und kann die Reaktionsfähigkeit der App beeinträchtigen.",
"image_thumbnail_title": "Iistellige für Vorschaubildli", "image_thumbnail_title": "Einstellungen für Vorschaubilder",
"import_config_from_json_description": "Systemkonfiguration importiere durs Ufelade vonere JSON-Datei", "import_config_from_json_description": "Systemkonfiguration von hochgeladener JSON-Konfigurationsdatei importieren",
"job_concurrency": "{job} Näbeläufigkeit", "job_concurrency": "{job} (Anzahl gleichzeitig laufende Prozesse)",
"job_created": "Uufgab erstellt", "job_created": "Aufgabe erstellt",
"job_not_concurrency_safe": "Die Uufgabe ist nöd für Paralleluusführig gmacht.", "job_not_concurrency_safe": "Diese Aufgabe kann nicht mehrmals parallel laufen gelassen werden.",
"job_settings": "Uufgabe-Iistellige", "job_settings": "Aufgabeneinstellungen",
"job_settings_description": "Uufgabe-Näbeläufigkeit verwalte", "job_settings_description": "Gleichzeitige Ausführung von Aufgaben verwalten",
"jobs_over_time": "Uufgabe in ziitliche Verlauf", "jobs_over_time": "Jobs im Laufe der Zeit",
"library_created": "Bibliothek erstellt: {library}", "library_created": "Bibliothek erstellt: {library}",
"library_deleted": "Bibliothek glöscht", "library_deleted": "Bibliothek gelöscht",
"library_details": "Bibliotheks-Details", "library_details": "Bibliotheksdetails",
"library_folder_description": "Gib en Order zum Importiere a. Dä Order mit sine Underordner wird nach Bildli und Videos durchsucht.", "library_folder_description": "Wähle einen Ordner zum Importieren. Dieser Ordner wird inklusive Unterordnern nach Bildern und Videos durchsucht.",
"library_remove_exclusion_pattern_prompt": "Bisch sicher, dass das Uuschluss-Muster wotsch lösche?", "library_remove_exclusion_pattern_prompt": "Bilst du sicher, dass du dieses Ausschlussmuster entfernen möchtest?",
"library_remove_folder_prompt": "Bisch sicher, dass dä Import-Ordner wotsch lösche?", "library_remove_folder_prompt": "Bist du sicher, dass du diesen Import-Ordner entfernen möchtest?",
"library_scanning": "Regelmässigi Überprüefig" "library_scanning": "Regelmässiges Scannen"
} }
} }
+5 -1
View File
@@ -849,9 +849,12 @@
"create_link_to_share": "Δημιουργία συνδέσμου για διαμοιρασμό", "create_link_to_share": "Δημιουργία συνδέσμου για διαμοιρασμό",
"create_link_to_share_description": "Επιτρέψτε σε οποιονδήποτε έχει τον σύνδεσμο να δει τη/τις επιλεγμένη/ες φωτογραφία/ες", "create_link_to_share_description": "Επιτρέψτε σε οποιονδήποτε έχει τον σύνδεσμο να δει τη/τις επιλεγμένη/ες φωτογραφία/ες",
"create_new": "ΔΗΜΙΟΥΡΓΙΑ ΝΕΟΥ", "create_new": "ΔΗΜΙΟΥΡΓΙΑ ΝΕΟΥ",
"create_new_person": "Δημιουργία νέου προσώπου", "create_new_face": "Δημιουργία νέου προσώπου",
"create_new_person": "Δημιουργία νέου ατόμου",
"create_new_person_hint": "Αντιστοίχιση των επιλεγμένων αρχείων σε ένα νέο πρόσωπο", "create_new_person_hint": "Αντιστοίχιση των επιλεγμένων αρχείων σε ένα νέο πρόσωπο",
"create_new_user": "Δημιουργία νέου χρήστη", "create_new_user": "Δημιουργία νέου χρήστη",
"create_person": "Δημιουργία ατόμου",
"create_person_subtitle": "Προσθέστε ένα όνομα στο επιλεγμένο πρόσωπο για να δημιουργηθεί και να επισημανθεί το νέο άτομο",
"create_shared_album_page_share_add_assets": "ΠΡΟΣΘΗΚΗ ΣΤΟΙΧΕΙΩΝ", "create_shared_album_page_share_add_assets": "ΠΡΟΣΘΗΚΗ ΣΤΟΙΧΕΙΩΝ",
"create_shared_album_page_share_select_photos": "Επιλέξτε Φωτογραφίες", "create_shared_album_page_share_select_photos": "Επιλέξτε Φωτογραφίες",
"create_shared_link": "Δημιουργία κοινόχρηστου συνδέσμου", "create_shared_link": "Δημιουργία κοινόχρηστου συνδέσμου",
@@ -2214,6 +2217,7 @@
"tag": "Ετικέτα", "tag": "Ετικέτα",
"tag_assets": "Ετικετοποίηση στοιχείων", "tag_assets": "Ετικετοποίηση στοιχείων",
"tag_created": "Δημιουργήθηκε ετικέτα: {tag}", "tag_created": "Δημιουργήθηκε ετικέτα: {tag}",
"tag_face": "Επισήμανση προσώπου",
"tag_feature_description": "Περιήγηση σε φωτογραφίες και βίντεο που είναι οργανωμένα σύμφωνα με λογικά θέματα ετικετών", "tag_feature_description": "Περιήγηση σε φωτογραφίες και βίντεο που είναι οργανωμένα σύμφωνα με λογικά θέματα ετικετών",
"tag_not_found_question": "Δεν μπορείτε να βρείτε μια ετικέτα; <link>Δημιουργήστε μια νέα ετικέτα.</link>", "tag_not_found_question": "Δεν μπορείτε να βρείτε μια ετικέτα; <link>Δημιουργήστε μια νέα ετικέτα.</link>",
"tag_people": "Επισήμανση ατόμων", "tag_people": "Επισήμανση ατόμων",
+11
View File
@@ -267,6 +267,8 @@
"notification_enable_email_notifications": "Enable email notifications", "notification_enable_email_notifications": "Enable email notifications",
"notification_settings": "Notification Settings", "notification_settings": "Notification Settings",
"notification_settings_description": "Manage notification settings, including email", "notification_settings_description": "Manage notification settings, including email",
"oauth_allow_insecure_requests": "Allow insecure requests",
"oauth_allow_insecure_requests_description": "WARNING: This disables TLS certificate validation for OAuth requests and may expose you to MITM attacks.",
"oauth_auto_launch": "Auto launch", "oauth_auto_launch": "Auto launch",
"oauth_auto_launch_description": "Start the OAuth login flow automatically upon navigating to the login page", "oauth_auto_launch_description": "Start the OAuth login flow automatically upon navigating to the login page",
"oauth_auto_register": "Auto register", "oauth_auto_register": "Auto register",
@@ -274,9 +276,11 @@
"oauth_button_text": "Button text", "oauth_button_text": "Button text",
"oauth_client_secret_description": "Required for confidential client, or if PKCE (Proof Key for Code Exchange) is not supported for public client.", "oauth_client_secret_description": "Required for confidential client, or if PKCE (Proof Key for Code Exchange) is not supported for public client.",
"oauth_enable_description": "Login with OAuth", "oauth_enable_description": "Login with OAuth",
"oauth_end_session_url_description": "Redirect the user to this URI when they log out.",
"oauth_mobile_redirect_uri": "Mobile redirect URI", "oauth_mobile_redirect_uri": "Mobile redirect URI",
"oauth_mobile_redirect_uri_override": "Mobile redirect URI override", "oauth_mobile_redirect_uri_override": "Mobile redirect URI override",
"oauth_mobile_redirect_uri_override_description": "Enable when OAuth provider does not allow a mobile URI, like ''{callback}''", "oauth_mobile_redirect_uri_override_description": "Enable when OAuth provider does not allow a mobile URI, like ''{callback}''",
"oauth_prompt_description": "Prompt parameter (e.g. select_account, login, consent)",
"oauth_role_claim": "Role Claim", "oauth_role_claim": "Role Claim",
"oauth_role_claim_description": "Automatically grant admin access based on the presence of this claim. The claim may have either 'user' or 'admin'.", "oauth_role_claim_description": "Automatically grant admin access based on the presence of this claim. The claim may have either 'user' or 'admin'.",
"oauth_settings": "OAuth", "oauth_settings": "OAuth",
@@ -1392,6 +1396,7 @@
"light_theme": "Switch to light theme", "light_theme": "Switch to light theme",
"like": "Like", "like": "Like",
"like_deleted": "Like deleted", "like_deleted": "Like deleted",
"link": "Link",
"link_motion_video": "Link motion video", "link_motion_video": "Link motion video",
"link_to_docs": "For more information, refer to the <link>documentation</link>.", "link_to_docs": "For more information, refer to the <link>documentation</link>.",
"link_to_oauth": "Link to OAuth", "link_to_oauth": "Link to OAuth",
@@ -1562,6 +1567,8 @@
"multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping", "multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping",
"mute_memories": "Mute Memories", "mute_memories": "Mute Memories",
"my_albums": "My albums", "my_albums": "My albums",
"my_immich_description": "Copy current page as a My Immich link",
"my_immich_title": "My Immich link",
"name": "Name", "name": "Name",
"name_or_nickname": "Name or nickname", "name_or_nickname": "Name or nickname",
"name_required": "Name is required", "name_required": "Name is required",
@@ -1926,6 +1933,8 @@
"scan_settings": "Scan Settings", "scan_settings": "Scan Settings",
"scanning": "Scanning", "scanning": "Scanning",
"scanning_for_album": "Scanning for album...", "scanning_for_album": "Scanning for album...",
"screencast_mode_description": "Show keyboard and mouse event indicators on the screen",
"screencast_mode_title": "Toggle screencast mode",
"search": "Search", "search": "Search",
"search_albums": "Search albums", "search_albums": "Search albums",
"search_by_context": "Search by context", "search_by_context": "Search by context",
@@ -2214,6 +2223,8 @@
"sync_status": "Sync Status", "sync_status": "Sync Status",
"sync_status_subtitle": "View and manage the sync system", "sync_status_subtitle": "View and manage the sync system",
"sync_upload_album_setting_subtitle": "Create and upload your photos and videos to the selected albums on Immich", "sync_upload_album_setting_subtitle": "Create and upload your photos and videos to the selected albums on Immich",
"system_theme": "System theme",
"system_theme_command_description": "Use the system theme ({value})",
"tag": "Tag", "tag": "Tag",
"tag_assets": "Tag assets", "tag_assets": "Tag assets",
"tag_created": "Created tag: {tag}", "tag_created": "Created tag: {tag}",
+32 -1
View File
@@ -849,9 +849,12 @@
"create_link_to_share": "Krei ligilon por dividi", "create_link_to_share": "Krei ligilon por dividi",
"create_link_to_share_description": "Permesi, ke iu ajn kun la ligilo povu vidi la elektita(j)n foto(j)n", "create_link_to_share_description": "Permesi, ke iu ajn kun la ligilo povu vidi la elektita(j)n foto(j)n",
"create_new": "KREI NOVAN", "create_new": "KREI NOVAN",
"create_new_face": "Krei novan vizaĝon",
"create_new_person": "Krei novan homon", "create_new_person": "Krei novan homon",
"create_new_person_hint": "Atribui elektitajn elementojn al nova homo", "create_new_person_hint": "Atribui elektitajn elementojn al nova homo",
"create_new_user": "Krei novan uzanton", "create_new_user": "Krei novan uzanton",
"create_person": "Krei homon",
"create_person_subtitle": "Aldoni nomon al la elektita vizaĝo por krei kaj etikedi novan homon",
"create_shared_album_page_share_add_assets": "ALDONI ELEMENTOJN", "create_shared_album_page_share_add_assets": "ALDONI ELEMENTOJN",
"create_shared_album_page_share_select_photos": "Elekti fotojn", "create_shared_album_page_share_select_photos": "Elekti fotojn",
"create_shared_link": "Krei dividitan ligilon", "create_shared_link": "Krei dividitan ligilon",
@@ -1043,7 +1046,7 @@
"cannot_navigate_previous_asset": "Ne eblas navigi al antaŭa elemento", "cannot_navigate_previous_asset": "Ne eblas navigi al antaŭa elemento",
"cant_apply_changes": "Ne eblas apliki ŝanĝojn", "cant_apply_changes": "Ne eblas apliki ŝanĝojn",
"cant_change_activity": "Ne eblas {enabled, select, true {malŝalti} other {ŝalti}} tiun agon", "cant_change_activity": "Ne eblas {enabled, select, true {malŝalti} other {ŝalti}} tiun agon",
"cant_change_asset_favorite": "Ne eblas ŝanĝi preferaton por tiu elemento", "cant_change_asset_favorite": "Ne eblas ŝanĝi preferon por tiu elemento",
"cant_change_metadata_assets_count": "Ne eblas ŝanĝi metadatumojn de {count, plural, one {# elemento} other {# elementoj}}", "cant_change_metadata_assets_count": "Ne eblas ŝanĝi metadatumojn de {count, plural, one {# elemento} other {# elementoj}}",
"cant_get_faces": "Ne eblas trovi vizaĝojn", "cant_get_faces": "Ne eblas trovi vizaĝojn",
"cant_get_number_of_comments": "Ne eblas trovi nombron da komentoj", "cant_get_number_of_comments": "Ne eblas trovi nombron da komentoj",
@@ -1074,7 +1077,18 @@
"incorrect_email_or_password": "Neĝusta retadreso aŭ pasvorto", "incorrect_email_or_password": "Neĝusta retadreso aŭ pasvorto",
"library_folder_already_exists": "Tiu ĉi import-vojo jam ekzistas.", "library_folder_already_exists": "Tiu ĉi import-vojo jam ekzistas.",
"page_not_found": "Paĝo ne trovita", "page_not_found": "Paĝo ne trovita",
"paths_validation_failed": "Evidentiĝis, ke {paths, plural, one {# vojo estas nevalida} other {# vojoj estas nevalidaj}}",
"profile_picture_transparent_pixels": "Ne eblas havi travideblaj bilderoj en profilbildo. Bonvolu zomi kaj/aŭ ŝovi la bildon al loko sen tiaj bilderoj.",
"quota_higher_than_disk_size": "Vi donis kvoton pli grandan ol la disko mem",
"something_went_wrong": "Io misis",
"unable_to_add_album_users": "Ne eblas aldoni uzantojn al la albumo",
"unable_to_add_assets_to_shared_link": "Ne eblas aldoni elementojn al la dividita ligilo",
"unable_to_add_comment": "Ne eblas aldoni komenton",
"unable_to_add_exclusion_pattern": "Ne eblas aldoni skemon de ekskludo", "unable_to_add_exclusion_pattern": "Ne eblas aldoni skemon de ekskludo",
"unable_to_add_partners": "Ne eblas aldoni partnerojn",
"unable_to_add_remove_archive": "Ne eblas {archived, select, true {forigi elementon de} other {aldoni elementon al}} la arĥivo",
"unable_to_add_remove_favorites": "Ne eblas {favorite, select, true {aldoni elementon al} other {forigi elementon de}} preferataĵoj",
"unable_to_change_favorite": "Ne eblas ŝanĝi preferon por tiu elemento",
"unable_to_create": "Ne eblis krei laborfluon", "unable_to_create": "Ne eblis krei laborfluon",
"unable_to_delete_exclusion_pattern": "Ne eblas forigi skemon de ekskludo", "unable_to_delete_exclusion_pattern": "Ne eblas forigi skemon de ekskludo",
"unable_to_delete_workflow": "Ne eblis forigi laborfluon", "unable_to_delete_workflow": "Ne eblis forigi laborfluon",
@@ -1088,15 +1102,25 @@
"expand_all": "Etendi ĉiujn", "expand_all": "Etendi ĉiujn",
"explore": "Esplori", "explore": "Esplori",
"explorer": "Foliumilo", "explorer": "Foliumilo",
"favorite": "Preferataĵo",
"favorite_action_prompt": "{count} aldonita(j) al Preferataĵoj",
"favorite_or_unfavorite_photo": "Aldoni/forigi foton al/de preferataĵoj",
"favorites": "Preferataĵoj",
"favorites_page_no_favorites": "Neniuj preferataj elementoj trovitaj",
"free_up_space": "Liberigi spacon", "free_up_space": "Liberigi spacon",
"free_up_space_description": "Vi forigos fotojn kaj/aŭ videojn, kiuj havas savkopiojn en la servilo, por liberigi spacon en via aparato. La kopioj en la servilo restos.", "free_up_space_description": "Vi forigos fotojn kaj/aŭ videojn, kiuj havas savkopiojn en la servilo, por liberigi spacon en via aparato. La kopioj en la servilo restos.",
"general": "Ĝeneralaj", "general": "Ĝeneralaj",
"home_page_favorite_err_local": "Ankoraŭ ne eblas aldoni lokajn elementojn al Preferataĵoj; ignorita(j)",
"home_page_favorite_err_partner": "Ankoraŭ ne eblas aldoni elementojn de partnero al Preferataĵoj; ignorita(j)",
"keep_favorites": "Konservi preferataĵojn",
"manage_media_access_settings": "Malfermi agordaĵaron", "manage_media_access_settings": "Malfermi agordaĵaron",
"manage_the_app_settings": "Agordi la apon", "manage_the_app_settings": "Agordi la apon",
"map_settings_only_show_favorites": "Montri nur preferataĵojn",
"missing": "Netraktitaj", "missing": "Netraktitaj",
"networking_subtitle": "Administri agordojn pri finpunktoj de la servilo", "networking_subtitle": "Administri agordojn pri finpunktoj de la servilo",
"no_devices": "Neniuj aprobitaj aparatoj", "no_devices": "Neniuj aprobitaj aparatoj",
"no_explore_results_message": "Alŝutu pli da fotoj por esplori vian kolekton.", "no_explore_results_message": "Alŝutu pli da fotoj por esplori vian kolekton.",
"no_favorites_message": "Aldoni al Preferataĵoj por rapide retrovi viajn plej bonajn bildojn kaj videojn",
"no_notifications": "Neniuj sciigoj", "no_notifications": "Neniuj sciigoj",
"no_results_description": "Provu sinonimon aŭ pli ĝeneralan ŝlosilvorton", "no_results_description": "Provu sinonimon aŭ pli ĝeneralan ŝlosilvorton",
"notification_permission_dialog_content": "Por ŝalti sciigojn, iru al Agordoj kaj elektu 'permesi'.", "notification_permission_dialog_content": "Por ŝalti sciigojn, iru al Agordoj kaj elektu 'permesi'.",
@@ -1106,10 +1130,14 @@
"notification_toggle_setting_description": "Ŝalti sciigojn per retmesaĝo", "notification_toggle_setting_description": "Ŝalti sciigojn per retmesaĝo",
"notifications": "Sciigoj", "notifications": "Sciigoj",
"notifications_setting_description": "Administri sciigojn", "notifications_setting_description": "Administri sciigojn",
"only_favorites": "Nur preferataĵoj",
"preferences_settings_subtitle": "Administri agordojn pri la apo", "preferences_settings_subtitle": "Administri agordojn pri la apo",
"purchase_settings_server_activated": "La administranto respondecas pri la ŝlosilo de aŭtentikeco por la servilo", "purchase_settings_server_activated": "La administranto respondecas pri la ŝlosilo de aŭtentikeco por la servilo",
"rating_clear": "Forviŝi pritakson", "rating_clear": "Forviŝi pritakson",
"refresh": "Denove", "refresh": "Denove",
"remove_from_favorites": "Forigi el preferataĵoj",
"removed_from_favorites": "Forigita(j) el preferataĵoj",
"removed_from_favorites_count": "{count, plural, other {Forigis #}} el Preferataĵoj",
"rescan": "Reanalizi", "rescan": "Reanalizi",
"reset": "Restartigi", "reset": "Restartigi",
"reset_sqlite_clear_app_data": "Forviŝi datumojn", "reset_sqlite_clear_app_data": "Forviŝi datumojn",
@@ -1127,7 +1155,10 @@
"setting_notifications_subtitle": "Redakti viajn preferojn pri sciigoj", "setting_notifications_subtitle": "Redakti viajn preferojn pri sciigoj",
"start_date": "Komenca dato", "start_date": "Komenca dato",
"start_date_before_end_date": "Komenca dato devas esti antaŭ fina dato", "start_date_before_end_date": "Komenca dato devas esti antaŭ fina dato",
"to_favorite": "Aldoni al preferataĵoj",
"trigger_description": "Evento, kiu ekfunkciigas la laborfluon", "trigger_description": "Evento, kiu ekfunkciigas la laborfluon",
"unfavorite": "Forigi el preferataĵoj",
"unfavorite_action_prompt": "{count} forigita(j) el Preferataĵoj",
"untitled_workflow": "Sentitola laborfluo", "untitled_workflow": "Sentitola laborfluo",
"upload_concurrency": "Nombro da samtempaj alŝutoj", "upload_concurrency": "Nombro da samtempaj alŝutoj",
"user_pin_code_settings_description": "Administri vian PIN-kodon", "user_pin_code_settings_description": "Administri vian PIN-kodon",
+6 -2
View File
@@ -849,9 +849,12 @@
"create_link_to_share": "Crear enlace compartido", "create_link_to_share": "Crear enlace compartido",
"create_link_to_share_description": "Permitir que cualquier persona con el enlace vea la(s) foto(s) seleccionada(s)", "create_link_to_share_description": "Permitir que cualquier persona con el enlace vea la(s) foto(s) seleccionada(s)",
"create_new": "CREAR NUEVO", "create_new": "CREAR NUEVO",
"create_new_face": "Crear nueva cara",
"create_new_person": "Crear nueva persona", "create_new_person": "Crear nueva persona",
"create_new_person_hint": "Asignar los recursos seleccionados a una nueva persona", "create_new_person_hint": "Asignar los recursos seleccionados a una nueva persona",
"create_new_user": "Crear nuevo usuario", "create_new_user": "Crear nuevo usuario",
"create_person": "Crear persona",
"create_person_subtitle": "Añade un nombre a la cara seleccionada para crear y etiquetar a la nueva persona",
"create_shared_album_page_share_add_assets": "AÑADIR RECURSOS", "create_shared_album_page_share_add_assets": "AÑADIR RECURSOS",
"create_shared_album_page_share_select_photos": "Seleccionar fotos", "create_shared_album_page_share_select_photos": "Seleccionar fotos",
"create_shared_link": "Crear un enlace compartido", "create_shared_link": "Crear un enlace compartido",
@@ -1022,7 +1025,7 @@
"enable_biometric_auth_description": "Introduce tu código PIN para habilitar la autentificación biométrica", "enable_biometric_auth_description": "Introduce tu código PIN para habilitar la autentificación biométrica",
"enabled": "Habilitado", "enabled": "Habilitado",
"end_date": "Fecha final", "end_date": "Fecha final",
"enqueued": "Agregado a la cola", "enqueued": "Añadido a la cola",
"enter_wifi_name": "Introduce el nombre Wi-Fi", "enter_wifi_name": "Introduce el nombre Wi-Fi",
"enter_your_pin_code": "Introduce tu código PIN", "enter_your_pin_code": "Introduce tu código PIN",
"enter_your_pin_code_subtitle": "Introduce tu código PIN para acceder a la carpeta protegida", "enter_your_pin_code_subtitle": "Introduce tu código PIN para acceder a la carpeta protegida",
@@ -1085,7 +1088,7 @@
"unable_to_add_partners": "No se pueden añadir miembros", "unable_to_add_partners": "No se pueden añadir miembros",
"unable_to_add_remove_archive": "No se pudo {archived, select, true {eliminar el recurso del} other {añadir el recurso al}} archivo", "unable_to_add_remove_archive": "No se pudo {archived, select, true {eliminar el recurso del} other {añadir el recurso al}} archivo",
"unable_to_add_remove_favorites": "No se pudo {favorite, select, true {añadir el recuso a} other {eliminar el recurso de}} los favoritos", "unable_to_add_remove_favorites": "No se pudo {favorite, select, true {añadir el recuso a} other {eliminar el recurso de}} los favoritos",
"unable_to_archive_unarchive": "No se pudo {archived, select, true {agregar el elemento al} other {quitar el elemento del}} archivo", "unable_to_archive_unarchive": "No se pudo {archived, select, true {añadir el elemento al} other {quitar el elemento del}} archivo",
"unable_to_change_album_user_role": "No se puede cambiar la función del usuario del álbum", "unable_to_change_album_user_role": "No se puede cambiar la función del usuario del álbum",
"unable_to_change_date": "No se puede cambiar la fecha", "unable_to_change_date": "No se puede cambiar la fecha",
"unable_to_change_description": "Imposible cambiar la descripción", "unable_to_change_description": "Imposible cambiar la descripción",
@@ -2214,6 +2217,7 @@
"tag": "Etiqueta", "tag": "Etiqueta",
"tag_assets": "Etiquetar recursos", "tag_assets": "Etiquetar recursos",
"tag_created": "Etiqueta creada: {tag}", "tag_created": "Etiqueta creada: {tag}",
"tag_face": "Etiquetar cara",
"tag_feature_description": "Explore fotos y videos agrupados por temas de etiquetas lógicas", "tag_feature_description": "Explore fotos y videos agrupados por temas de etiquetas lógicas",
"tag_not_found_question": "¿No encuentra una etiqueta? <link>Crea una nueva etiqueta.</link>", "tag_not_found_question": "¿No encuentra una etiqueta? <link>Crea una nueva etiqueta.</link>",
"tag_people": "Etiquetar personas", "tag_people": "Etiquetar personas",
+14 -2
View File
@@ -849,9 +849,12 @@
"create_link_to_share": "Luo linkki jaettavaksi", "create_link_to_share": "Luo linkki jaettavaksi",
"create_link_to_share_description": "Salli kaikkien linkin saaneiden nähdä valitut kuvat", "create_link_to_share_description": "Salli kaikkien linkin saaneiden nähdä valitut kuvat",
"create_new": "LUO UUSI", "create_new": "LUO UUSI",
"create_new_face": "Luo uudet kasvot",
"create_new_person": "Luo uusi henkilö", "create_new_person": "Luo uusi henkilö",
"create_new_person_hint": "Määritä valitut mediat uudelle henkilölle", "create_new_person_hint": "Määritä valitut mediat uudelle henkilölle",
"create_new_user": "Luo uusi käyttäjä", "create_new_user": "Luo uusi käyttäjä",
"create_person": "Luo henkilö",
"create_person_subtitle": "Lisää nimi valituille kasvoille luodaksesi uudelle henkilölle tunnisteen",
"create_shared_album_page_share_add_assets": "LISÄÄ KOHTEITA", "create_shared_album_page_share_add_assets": "LISÄÄ KOHTEITA",
"create_shared_album_page_share_select_photos": "Valitse kuvat", "create_shared_album_page_share_select_photos": "Valitse kuvat",
"create_shared_link": "Luo jakolinkki", "create_shared_link": "Luo jakolinkki",
@@ -866,6 +869,7 @@
"crop_aspect_ratio_fixed": "Kiinteä", "crop_aspect_ratio_fixed": "Kiinteä",
"crop_aspect_ratio_free": "Vapaa", "crop_aspect_ratio_free": "Vapaa",
"crop_aspect_ratio_original": "Alkuperäinen", "crop_aspect_ratio_original": "Alkuperäinen",
"crop_aspect_ratio_square": "Neliö",
"curated_object_page_title": "Asiat", "curated_object_page_title": "Asiat",
"current_device": "Nykyinen laite", "current_device": "Nykyinen laite",
"current_pin_code": "Nykyinen PIN-koodi", "current_pin_code": "Nykyinen PIN-koodi",
@@ -880,7 +884,7 @@
"daily_title_text_date": "E, dd MMM", "daily_title_text_date": "E, dd MMM",
"daily_title_text_date_year": "E, dd MMM, yyyy", "daily_title_text_date_year": "E, dd MMM, yyyy",
"dark": "Tumma", "dark": "Tumma",
"dark_theme": "Vaihda tumma teema", "dark_theme": "Vaihda tummaan teemaan",
"date": "Päivämäärä", "date": "Päivämäärä",
"date_after": "Päivämäärän jälkeen", "date_after": "Päivämäärän jälkeen",
"date_and_time": "Päivämäärä ja aika", "date_and_time": "Päivämäärä ja aika",
@@ -891,6 +895,8 @@
"day": "Päivä", "day": "Päivä",
"days": "Päivää", "days": "Päivää",
"deduplicate_all": "Poista kaikkien kaksoiskappaleet", "deduplicate_all": "Poista kaikkien kaksoiskappaleet",
"default_locale": "Oletuskieli",
"default_locale_description": "Muotoile päivämäärät ja luvut selaimesi kieliasetusten mukaan",
"delete": "Poista", "delete": "Poista",
"delete_action_confirmation_message": "Haluatko varmasti poistaa tämän aineiston? Tämä toiminto siirtää aineiston palvelimen roskakoriin ja kysyy, haluatko poistaa sen myös paikallisesti", "delete_action_confirmation_message": "Haluatko varmasti poistaa tämän aineiston? Tämä toiminto siirtää aineiston palvelimen roskakoriin ja kysyy, haluatko poistaa sen myös paikallisesti",
"delete_action_prompt": "{count} poistettu", "delete_action_prompt": "{count} poistettu",
@@ -966,7 +972,7 @@
"downloading_media": "Median lataaminen", "downloading_media": "Median lataaminen",
"drop_files_to_upload": "Pudota tiedostot mihin tahansa ladataksesi ne", "drop_files_to_upload": "Pudota tiedostot mihin tahansa ladataksesi ne",
"duplicates": "Kaksoiskappaleet", "duplicates": "Kaksoiskappaleet",
"duplicates_description": "Selvitä jokaisen kohdalla mitkä (jos mitkään) ovat kaksoiskappaleita", "duplicates_description": "Selvitä jokaisen kohdalla mitkä (jos mitkään) ovat kaksoiskappaleita.",
"duration": "Kesto", "duration": "Kesto",
"edit": "Muokkaa", "edit": "Muokkaa",
"edit_album": "Muokkaa albumia", "edit_album": "Muokkaa albumia",
@@ -1003,6 +1009,8 @@
"editor_edits_applied_success": "Muutokset otettu käyttöön", "editor_edits_applied_success": "Muutokset otettu käyttöön",
"editor_flip_horizontal": "Käännä vaakatasossa", "editor_flip_horizontal": "Käännä vaakatasossa",
"editor_flip_vertical": "Käännä pystytasossa", "editor_flip_vertical": "Käännä pystytasossa",
"editor_handle_corner": "{corner, select, top_left {Vasen yläkulma} top_right {Oikea yläkulma} bottom_left {Vasen alakulma} bottom_right {Oikea alakulma} other {A}} kulman kahva",
"editor_handle_edge": "{edge, select, top {Yläreuna} bottom {Alareuna} left {Vasen reuna} right {Oikea reuna} other {En}} reunan kahva",
"editor_orientation": "Suunta", "editor_orientation": "Suunta",
"editor_reset_all_changes": "Nollaa muutokset", "editor_reset_all_changes": "Nollaa muutokset",
"editor_rotate_left": "Kierrä 90° vastapäivään", "editor_rotate_left": "Kierrä 90° vastapäivään",
@@ -1381,9 +1389,11 @@
"library_page_sort_title": "Albumin otsikko", "library_page_sort_title": "Albumin otsikko",
"licenses": "Lisenssit", "licenses": "Lisenssit",
"light": "Vaalea", "light": "Vaalea",
"light_theme": "Vaihda vaaleaan teemaan",
"like": "Tykkää", "like": "Tykkää",
"like_deleted": "Tykkäys poistettu", "like_deleted": "Tykkäys poistettu",
"link_motion_video": "Linkitä liikevideo", "link_motion_video": "Linkitä liikevideo",
"link_to_docs": "Lisätietoja löytyy <link>dokumentaatiosta</link>.",
"link_to_oauth": "Linkki OAuth", "link_to_oauth": "Linkki OAuth",
"linked_oauth_account": "Linkitetty OAuth-tili", "linked_oauth_account": "Linkitetty OAuth-tili",
"list": "Lista", "list": "Lista",
@@ -2207,6 +2217,7 @@
"tag": "Tunniste", "tag": "Tunniste",
"tag_assets": "Lisää tunnisteita", "tag_assets": "Lisää tunnisteita",
"tag_created": "Luotu tunniste: {tag}", "tag_created": "Luotu tunniste: {tag}",
"tag_face": "Merkitse kasvot",
"tag_feature_description": "Selaa valokuvia ja videoita, jotka on ryhmitelty loogisten tunnisteotsikoiden mukaan", "tag_feature_description": "Selaa valokuvia ja videoita, jotka on ryhmitelty loogisten tunnisteotsikoiden mukaan",
"tag_not_found_question": "Etkö löydä tunnistetta? <link>Luo uusi tunniste.</link>", "tag_not_found_question": "Etkö löydä tunnistetta? <link>Luo uusi tunniste.</link>",
"tag_people": "Merkitse henkilö tunnisteella", "tag_people": "Merkitse henkilö tunnisteella",
@@ -2388,6 +2399,7 @@
"viewer_remove_from_stack": "Poista pinosta", "viewer_remove_from_stack": "Poista pinosta",
"viewer_stack_use_as_main_asset": "Käytä pääkohteena", "viewer_stack_use_as_main_asset": "Käytä pääkohteena",
"viewer_unstack": "Pura pino", "viewer_unstack": "Pura pino",
"visibility": "Näkyvyys",
"visibility_changed": "{count, plural, one {# henkilön} other {# henkilöiden}} näkyvyys vaihdettu", "visibility_changed": "{count, plural, one {# henkilön} other {# henkilöiden}} näkyvyys vaihdettu",
"visual": "Visuaalinen", "visual": "Visuaalinen",
"visual_builder": "Visuaalinen koostaja", "visual_builder": "Visuaalinen koostaja",
+4
View File
@@ -849,9 +849,12 @@
"create_link_to_share": "Créer un lien pour partager", "create_link_to_share": "Créer un lien pour partager",
"create_link_to_share_description": "Permettre à n'importe qui ayant le lien de voir la(es) photo(s) sélectionnée(s)", "create_link_to_share_description": "Permettre à n'importe qui ayant le lien de voir la(es) photo(s) sélectionnée(s)",
"create_new": "NOUVEAU", "create_new": "NOUVEAU",
"create_new_face": "Créer un nouveau visage",
"create_new_person": "Créer une nouvelle personne", "create_new_person": "Créer une nouvelle personne",
"create_new_person_hint": "Attribuer les médias sélectionnés à une nouvelle personne", "create_new_person_hint": "Attribuer les médias sélectionnés à une nouvelle personne",
"create_new_user": "Créer un nouvel utilisateur", "create_new_user": "Créer un nouvel utilisateur",
"create_person": "Créer une personne",
"create_person_subtitle": "Ajouter un nom au visage sélectionné pour créer et étiqueter la nouvelle personne",
"create_shared_album_page_share_add_assets": "AJOUTER DES ÉLÉMENTS", "create_shared_album_page_share_add_assets": "AJOUTER DES ÉLÉMENTS",
"create_shared_album_page_share_select_photos": "Sélectionner les photos", "create_shared_album_page_share_select_photos": "Sélectionner les photos",
"create_shared_link": "Créer un lien partagé", "create_shared_link": "Créer un lien partagé",
@@ -2214,6 +2217,7 @@
"tag": "Étiquette", "tag": "Étiquette",
"tag_assets": "Étiqueter les médias", "tag_assets": "Étiqueter les médias",
"tag_created": "Étiquette créée: {tag}", "tag_created": "Étiquette créée: {tag}",
"tag_face": "Étiqueter le visage",
"tag_feature_description": "Parcourir les photos et vidéos groupées par thèmes logiques", "tag_feature_description": "Parcourir les photos et vidéos groupées par thèmes logiques",
"tag_not_found_question": "Vous ne trouvez pas une étiquette? <link>Créer une nouvelle étiquette.</link>", "tag_not_found_question": "Vous ne trouvez pas une étiquette? <link>Créer une nouvelle étiquette.</link>",
"tag_people": "Étiqueter les personnes", "tag_people": "Étiqueter les personnes",
+4
View File
@@ -849,9 +849,12 @@
"create_link_to_share": "Crear ligazón para compartir", "create_link_to_share": "Crear ligazón para compartir",
"create_link_to_share_description": "Permitir que calquera persoa coa ligazón vexa a(s) foto(s) seleccionada(s)", "create_link_to_share_description": "Permitir que calquera persoa coa ligazón vexa a(s) foto(s) seleccionada(s)",
"create_new": "CREAR NOVO", "create_new": "CREAR NOVO",
"create_new_face": "Crear nova cara",
"create_new_person": "Crear nova persoa", "create_new_person": "Crear nova persoa",
"create_new_person_hint": "Asignar activos seleccionados a unha nova persoa", "create_new_person_hint": "Asignar activos seleccionados a unha nova persoa",
"create_new_user": "Crear novo usuario", "create_new_user": "Crear novo usuario",
"create_person": "Crear persona",
"create_person_subtitle": "Engade un nome á cara seleccionada para crear e etiquetar á nova persona",
"create_shared_album_page_share_add_assets": "ENGADIR ACTIVOS", "create_shared_album_page_share_add_assets": "ENGADIR ACTIVOS",
"create_shared_album_page_share_select_photos": "Seleccionar Fotos", "create_shared_album_page_share_select_photos": "Seleccionar Fotos",
"create_shared_link": "Crear ligazón compartida", "create_shared_link": "Crear ligazón compartida",
@@ -2214,6 +2217,7 @@
"tag": "Etiqueta", "tag": "Etiqueta",
"tag_assets": "Etiquetar activos", "tag_assets": "Etiquetar activos",
"tag_created": "Etiqueta creada: {tag}", "tag_created": "Etiqueta creada: {tag}",
"tag_face": "Etiquetar cara",
"tag_feature_description": "Navegar por fotos e vídeos agrupados por temas de etiquetas lóxicas", "tag_feature_description": "Navegar por fotos e vídeos agrupados por temas de etiquetas lóxicas",
"tag_not_found_question": "Non atopa unha etiqueta? <link>Crear unha nova etiqueta.</link>", "tag_not_found_question": "Non atopa unha etiqueta? <link>Crear unha nova etiqueta.</link>",
"tag_people": "Etiquetar Persoas", "tag_people": "Etiquetar Persoas",
+13 -1
View File
@@ -845,9 +845,12 @@
"create_link_to_share": "Izradite vezu za dijeljenje", "create_link_to_share": "Izradite vezu za dijeljenje",
"create_link_to_share_description": "Dopusti svakome s vezom da vidi odabrane fotografije", "create_link_to_share_description": "Dopusti svakome s vezom da vidi odabrane fotografije",
"create_new": "KREIRAJ NOVO", "create_new": "KREIRAJ NOVO",
"create_new_face": "Stvori novo lice",
"create_new_person": "Stvorite novu osobu", "create_new_person": "Stvorite novu osobu",
"create_new_person_hint": "Dodijelite odabrane stavke novoj osobi", "create_new_person_hint": "Dodijelite odabrane stavke novoj osobi",
"create_new_user": "Kreiraj novog korisnika", "create_new_user": "Kreiraj novog korisnika",
"create_person": "Stvori novu osobu",
"create_person_subtitle": "Dodaj ime odabranom licu kako bi stvorio i tagirao novu osobu",
"create_shared_album_page_share_add_assets": "DODAJ STAVKE", "create_shared_album_page_share_add_assets": "DODAJ STAVKE",
"create_shared_album_page_share_select_photos": "Odaberi fotografije", "create_shared_album_page_share_select_photos": "Odaberi fotografije",
"create_shared_link": "Kreiraj dijeljeni link", "create_shared_link": "Kreiraj dijeljeni link",
@@ -922,6 +925,7 @@
"deselect_all": "Poništi odabir svih", "deselect_all": "Poništi odabir svih",
"details": "Detalji", "details": "Detalji",
"direction": "Smjer", "direction": "Smjer",
"disable": "Onesposobi",
"disabled": "Onemogućeno", "disabled": "Onemogućeno",
"disallow_edits": "Zabrani izmjene", "disallow_edits": "Zabrani izmjene",
"discord": "Discord", "discord": "Discord",
@@ -947,6 +951,7 @@
"download_include_embedded_motion_videos": "Ugrađeni videozapisi", "download_include_embedded_motion_videos": "Ugrađeni videozapisi",
"download_include_embedded_motion_videos_description": "Uključite videozapise ugrađene u fotografije s pokretom kao zasebnu datoteku", "download_include_embedded_motion_videos_description": "Uključite videozapise ugrađene u fotografije s pokretom kao zasebnu datoteku",
"download_notfound": "Preuzimanje nije pronađeno", "download_notfound": "Preuzimanje nije pronađeno",
"download_original": "Preuzmi original",
"download_paused": "Preuzimanje pauzirano", "download_paused": "Preuzimanje pauzirano",
"download_settings": "Preuzmi", "download_settings": "Preuzmi",
"download_settings_description": "Upravljajte postavkama vezanim uz preuzimanje stavki", "download_settings_description": "Upravljajte postavkama vezanim uz preuzimanje stavki",
@@ -956,10 +961,11 @@
"download_waiting_to_retry": "Čeka se ponovni pokušaj", "download_waiting_to_retry": "Čeka se ponovni pokušaj",
"downloading": "Preuzimanje", "downloading": "Preuzimanje",
"downloading_asset_filename": "Preuzimanje stavke {filename}", "downloading_asset_filename": "Preuzimanje stavke {filename}",
"downloading_from_icloud": "Preuzmi s iCloud",
"downloading_media": "Preuzimanje medija", "downloading_media": "Preuzimanje medija",
"drop_files_to_upload": "Ispustite datoteke bilo gdje za prijenos", "drop_files_to_upload": "Ispustite datoteke bilo gdje za prijenos",
"duplicates": "Duplikati", "duplicates": "Duplikati",
"duplicates_description": "Razriješite svaku grupu tako da naznačite koji su duplikati, ako ih ima", "duplicates_description": "Razriješite svaku grupu tako da naznačite koji su duplikati, ako ih ima.",
"duration": "Trajanje", "duration": "Trajanje",
"edit": "Izmjena", "edit": "Izmjena",
"edit_album": "Uredi album", "edit_album": "Uredi album",
@@ -987,6 +993,12 @@
"editor": "Urednik", "editor": "Urednik",
"editor_close_without_save_prompt": "Promjene neće biti spremljene", "editor_close_without_save_prompt": "Promjene neće biti spremljene",
"editor_close_without_save_title": "Zatvoriti uređivač?", "editor_close_without_save_title": "Zatvoriti uređivač?",
"editor_confirm_reset_all_changes": "Jeste li sigurni da želite resetirati sve opcije?",
"editor_discard_edits_confirm": "Odbaci izmjene",
"editor_discard_edits_prompt": "Imate nesačuvane izmjene. Jeste li sigurni da ih želite odbaciti?",
"editor_discard_edits_title": "Odbaci izmjene?",
"editor_rotate_left": "Rotiraj 90° u suprotnom smjeru kazaljke na satu",
"editor_rotate_right": "Rotiraj 90° u smjeru kazaljke na satu",
"email": "E-pošta", "email": "E-pošta",
"email_notifications": "Obavijesti putem e-maila", "email_notifications": "Obavijesti putem e-maila",
"empty_folder": "Ova mapa je prazna", "empty_folder": "Ova mapa je prazna",
+4
View File
@@ -849,9 +849,12 @@
"create_link_to_share": "Buat tautan untuk dibagikan", "create_link_to_share": "Buat tautan untuk dibagikan",
"create_link_to_share_description": "Biarkan siapa pun dengan tautan melihat foto yang dipilih", "create_link_to_share_description": "Biarkan siapa pun dengan tautan melihat foto yang dipilih",
"create_new": "BUAT BARU", "create_new": "BUAT BARU",
"create_new_face": "Buat wajah baru",
"create_new_person": "Buat orang baru", "create_new_person": "Buat orang baru",
"create_new_person_hint": "Tetapkan aset yang dipilih ke orang yang baru", "create_new_person_hint": "Tetapkan aset yang dipilih ke orang yang baru",
"create_new_user": "Buat pengguna baru", "create_new_user": "Buat pengguna baru",
"create_person": "Buat orang",
"create_person_subtitle": "Tambahkan nama pada wajah yang dipilih untuk membuat dan menandai orang baru",
"create_shared_album_page_share_add_assets": "TAMBAHKAN ASET", "create_shared_album_page_share_add_assets": "TAMBAHKAN ASET",
"create_shared_album_page_share_select_photos": "Pilih Foto", "create_shared_album_page_share_select_photos": "Pilih Foto",
"create_shared_link": "Buat tautan bersama", "create_shared_link": "Buat tautan bersama",
@@ -2214,6 +2217,7 @@
"tag": "Tag", "tag": "Tag",
"tag_assets": "Tag aset", "tag_assets": "Tag aset",
"tag_created": "Tag yang dibuat: {tag}", "tag_created": "Tag yang dibuat: {tag}",
"tag_face": "Tandai wajah",
"tag_feature_description": "Menjelajahi foto dan video yang dikelompokkan berdasarkan topik tag yang logis", "tag_feature_description": "Menjelajahi foto dan video yang dikelompokkan berdasarkan topik tag yang logis",
"tag_not_found_question": "Tidak dapat menemukan tag? <link>Buat tag baru.</link>", "tag_not_found_question": "Tidak dapat menemukan tag? <link>Buat tag baru.</link>",
"tag_people": "Beri Tag Orang", "tag_people": "Beri Tag Orang",
+7
View File
@@ -849,9 +849,12 @@
"create_link_to_share": "Crea link da condividere", "create_link_to_share": "Crea link da condividere",
"create_link_to_share_description": "Permetti a chiunque con il link di vedere le foto selezionate", "create_link_to_share_description": "Permetti a chiunque con il link di vedere le foto selezionate",
"create_new": "CREA NUOVO", "create_new": "CREA NUOVO",
"create_new_face": "Crea nuova faccia",
"create_new_person": "Crea nuova persona", "create_new_person": "Crea nuova persona",
"create_new_person_hint": "Assegna le risorse selezionate a una nuova persona", "create_new_person_hint": "Assegna le risorse selezionate a una nuova persona",
"create_new_user": "Crea nuovo utente", "create_new_user": "Crea nuovo utente",
"create_person": "Crea persona",
"create_person_subtitle": "Aggiungi un nome alla faccia selezionata per creare e taggare la nuova persona",
"create_shared_album_page_share_add_assets": "AGGIUNGI RISORSE", "create_shared_album_page_share_add_assets": "AGGIUNGI RISORSE",
"create_shared_album_page_share_select_photos": "Seleziona foto", "create_shared_album_page_share_select_photos": "Seleziona foto",
"create_shared_link": "Crea link condiviso", "create_shared_link": "Crea link condiviso",
@@ -892,6 +895,8 @@
"day": "Giorno", "day": "Giorno",
"days": "Giorni", "days": "Giorni",
"deduplicate_all": "Elimina tutti i doppioni", "deduplicate_all": "Elimina tutti i doppioni",
"default_locale": "Predefinito Locale",
"default_locale_description": "Formatta le date e i numeri sulla base del tuo browser locale",
"delete": "Elimina", "delete": "Elimina",
"delete_action_confirmation_message": "Vuoi davvero eliminare questa risorsa? Questa azione sposterà la risorsa nel cestino del server e ti chiederà se desideri eliminarla dal dispositivo", "delete_action_confirmation_message": "Vuoi davvero eliminare questa risorsa? Questa azione sposterà la risorsa nel cestino del server e ti chiederà se desideri eliminarla dal dispositivo",
"delete_action_prompt": "{count} elementi eliminati", "delete_action_prompt": "{count} elementi eliminati",
@@ -1388,6 +1393,7 @@
"like": "Mi piace", "like": "Mi piace",
"like_deleted": "Mi piace rimosso", "like_deleted": "Mi piace rimosso",
"link_motion_video": "Collega video in movimento", "link_motion_video": "Collega video in movimento",
"link_to_docs": "Per maggiori informazioni, riferirsi al <link>documentazione</link>.",
"link_to_oauth": "Collegamento a OAuth", "link_to_oauth": "Collegamento a OAuth",
"linked_oauth_account": "Account OAuth collegato", "linked_oauth_account": "Account OAuth collegato",
"list": "Lista", "list": "Lista",
@@ -2211,6 +2217,7 @@
"tag": "Tag", "tag": "Tag",
"tag_assets": "Tagga risorse", "tag_assets": "Tagga risorse",
"tag_created": "Tag creato: {tag}", "tag_created": "Tag creato: {tag}",
"tag_face": "Tagga la faccia",
"tag_feature_description": "Navigazione foto e video raggruppati per argomenti tag logici", "tag_feature_description": "Navigazione foto e video raggruppati per argomenti tag logici",
"tag_not_found_question": "Non riesci a trovare un tag? <link>Creane uno nuovo.</link>", "tag_not_found_question": "Non riesci a trovare un tag? <link>Creane uno nuovo.</link>",
"tag_people": "Tagga persone", "tag_people": "Tagga persone",
+38 -24
View File
@@ -798,7 +798,7 @@
"command_palette_to_close": "닫기", "command_palette_to_close": "닫기",
"command_palette_to_navigate": "들어가기", "command_palette_to_navigate": "들어가기",
"command_palette_to_select": "선택하기", "command_palette_to_select": "선택하기",
"command_palette_to_show_all": "다 보여주기", "command_palette_to_show_all": "모두 보기",
"comment_deleted": "댓글이 삭제되었습니다.", "comment_deleted": "댓글이 삭제되었습니다.",
"comment_options": "댓글 옵션", "comment_options": "댓글 옵션",
"comments_and_likes": "댓글 및 좋아요", "comments_and_likes": "댓글 및 좋아요",
@@ -849,9 +849,12 @@
"create_link_to_share": "공유 링크 생성", "create_link_to_share": "공유 링크 생성",
"create_link_to_share_description": "링크가 있는 경우 누구나 선택한 사진을 볼 수 있습니다.", "create_link_to_share_description": "링크가 있는 경우 누구나 선택한 사진을 볼 수 있습니다.",
"create_new": "새로 만들기", "create_new": "새로 만들기",
"create_new_face": "새 얼굴 생성",
"create_new_person": "인물 생성", "create_new_person": "인물 생성",
"create_new_person_hint": "선택한 항목의 인물을 새 인물로 변경", "create_new_person_hint": "선택한 항목의 인물을 새 인물로 변경",
"create_new_user": "새 사용자 생성", "create_new_user": "새 사용자 생성",
"create_person": "인물 생성",
"create_person_subtitle": "선택한 얼굴에 이름을 추가해 신규 인물을 생성하고 태그 지정",
"create_shared_album_page_share_add_assets": "항목 추가", "create_shared_album_page_share_add_assets": "항목 추가",
"create_shared_album_page_share_select_photos": "사진 선택", "create_shared_album_page_share_select_photos": "사진 선택",
"create_shared_link": "공유 링크 생성", "create_shared_link": "공유 링크 생성",
@@ -866,6 +869,7 @@
"crop_aspect_ratio_fixed": "고정", "crop_aspect_ratio_fixed": "고정",
"crop_aspect_ratio_free": "직접 조절", "crop_aspect_ratio_free": "직접 조절",
"crop_aspect_ratio_original": "원본", "crop_aspect_ratio_original": "원본",
"crop_aspect_ratio_square": "정사각형",
"curated_object_page_title": "사물", "curated_object_page_title": "사물",
"current_device": "현재 기기", "current_device": "현재 기기",
"current_pin_code": "현재 PIN 코드", "current_pin_code": "현재 PIN 코드",
@@ -880,7 +884,7 @@
"daily_title_text_date": "M월 d일 EEEE", "daily_title_text_date": "M월 d일 EEEE",
"daily_title_text_date_year": "yyyy년 M월 d일 EEEE", "daily_title_text_date_year": "yyyy년 M월 d일 EEEE",
"dark": "다크", "dark": "다크",
"dark_theme": "다크 테마 토글", "dark_theme": "다크 테마 전환",
"date": "날짜", "date": "날짜",
"date_after": "다음 날짜 이후", "date_after": "다음 날짜 이후",
"date_and_time": "날짜 및 시간", "date_and_time": "날짜 및 시간",
@@ -891,6 +895,8 @@
"day": "일", "day": "일",
"days": "일", "days": "일",
"deduplicate_all": "모두 삭제", "deduplicate_all": "모두 삭제",
"default_locale": "기본 로케일",
"default_locale_description": "브라우저 로케일 설정에 따라 날짜 및 숫자 형식을 지정합니다",
"delete": "삭제", "delete": "삭제",
"delete_action_confirmation_message": "이 항목을 삭제하시겠습니까? 서버에서는 항목을 휴지통으로 이동시키며, 로컬에서도 삭제할 것인지 확인 메시지가 표시됩니다.", "delete_action_confirmation_message": "이 항목을 삭제하시겠습니까? 서버에서는 항목을 휴지통으로 이동시키며, 로컬에서도 삭제할 것인지 확인 메시지가 표시됩니다.",
"delete_action_prompt": "{count}개 항목 삭제됨", "delete_action_prompt": "{count}개 항목 삭제됨",
@@ -1003,6 +1009,8 @@
"editor_edits_applied_success": "편집이 적용되었습니다.", "editor_edits_applied_success": "편집이 적용되었습니다.",
"editor_flip_horizontal": "좌우반전", "editor_flip_horizontal": "좌우반전",
"editor_flip_vertical": "상하반전", "editor_flip_vertical": "상하반전",
"editor_handle_corner": "{corner, select, top_left {좌상단} top_right {우상단} bottom_left {좌하단} bottom_right {우하단} other {A}} 코너 핸들",
"editor_handle_edge": "{edge, select, top {위} bottom {아래} left {왼쪽} right {오른쪽} other {An}} 모서리 핸들",
"editor_orientation": "방향", "editor_orientation": "방향",
"editor_reset_all_changes": "편집내용 초기화", "editor_reset_all_changes": "편집내용 초기화",
"editor_rotate_left": "반시계 방향으로 90° 회전", "editor_rotate_left": "반시계 방향으로 90° 회전",
@@ -1061,26 +1069,26 @@
"failed_to_load_assets": "항목 로드 실패", "failed_to_load_assets": "항목 로드 실패",
"failed_to_load_notifications": "알림 로드 실패", "failed_to_load_notifications": "알림 로드 실패",
"failed_to_load_people": "인물 로드 실패", "failed_to_load_people": "인물 로드 실패",
"failed_to_remove_product_key": "제품 키 제거에 실패", "failed_to_remove_product_key": "제품 키 제거에 실패했습니다.",
"failed_to_reset_pin_code": "PIN 코드 초기화 실패", "failed_to_reset_pin_code": "PIN 코드 초기화 실패",
"failed_to_stack_assets": "항목 스택에 실패", "failed_to_stack_assets": "항목 스택에 실패했습니다.",
"failed_to_unstack_assets": "항목 스택 풀기에 실패", "failed_to_unstack_assets": "항목 스택 풀기에 실패했습니다.",
"failed_to_update_notification_status": "알림 상태 업데이트 실패", "failed_to_update_notification_status": "알림 상태 업데이트 실패",
"incorrect_email_or_password": "잘못된 이메일 또는 비밀번호", "incorrect_email_or_password": "잘못된 이메일 또는 비밀번호",
"library_folder_already_exists": "가져올 경로가 이미 존재합니다.", "library_folder_already_exists": "가져올 경로가 이미 존재합니다.",
"page_not_found": "페이지를 찾을 수 없음 :/", "page_not_found": "페이지를 찾을 수 없음",
"paths_validation_failed": "{paths, plural, one {경로 #개} other {경로 #개}}가 유효성 검사에 실패했습니다.", "paths_validation_failed": "{paths, plural, one {경로 #개} other {경로 #개}}가 유효성 검사에 실패했습니다.",
"profile_picture_transparent_pixels": "프로필 사진에 투명 픽셀을 사용할 수 없습니다. 사진을 확대하거나 이동하세요.", "profile_picture_transparent_pixels": "프로필 사진에 투명 픽셀을 사용할 수 없습니다. 사진을 확대하거나 이동하세요.",
"quota_higher_than_disk_size": "할당량은 디스크 크기보다 작아야 합니다.", "quota_higher_than_disk_size": "할당량은 디스크 크기보다 작아야 합니다.",
"something_went_wrong": "문제가 발생했습니다.", "something_went_wrong": "문제가 발생했습니다.",
"unable_to_add_album_users": "앨범에 사용자를 추가할 수 없", "unable_to_add_album_users": "앨범에 사용자를 추가할 수 없습니다.",
"unable_to_add_assets_to_shared_link": "항목을 공유 링크에 추가할 수 없", "unable_to_add_assets_to_shared_link": "항목을 공유 링크에 추가할 수 없습니다.",
"unable_to_add_comment": "댓글을 추가할 수 없", "unable_to_add_comment": "댓글을 추가할 수 없습니다.",
"unable_to_add_exclusion_pattern": "제외 규칙을 추가할 수 없", "unable_to_add_exclusion_pattern": "제외 규칙을 추가할 수 없습니다.",
"unable_to_add_partners": "파트너를 추가할 수 없", "unable_to_add_partners": "파트너를 추가할 수 없습니다.",
"unable_to_add_remove_archive": "{archived, select, true {보관함에서 항목을 제거할} other {보관함으로 항목을 이동할}} 수 없", "unable_to_add_remove_archive": "{archived, select, true {보관함에서 항목을 제거할} other {보관함으로 항목을 이동할}} 수 없습니다.",
"unable_to_add_remove_favorites": "즐겨찾기에 항목을 {favorite, select, true {추가} other {제거}}할 수 없", "unable_to_add_remove_favorites": "즐겨찾기에 항목을 {favorite, select, true {추가} other {제거}}할 수 없습니다",
"unable_to_archive_unarchive": "항목을 {archived, select, true {보관} other {보관 해제}}할 수 없", "unable_to_archive_unarchive": "항목을 {archived, select, true {보관} other {보관 해제}}할 수 없습니다",
"unable_to_change_album_user_role": "앨범 사용자의 역할을 변경할 수 없습니다.", "unable_to_change_album_user_role": "앨범 사용자의 역할을 변경할 수 없습니다.",
"unable_to_change_date": "날짜를 변경할 수 없습니다.", "unable_to_change_date": "날짜를 변경할 수 없습니다.",
"unable_to_change_description": "설명을 변경할 수 없습니다.", "unable_to_change_description": "설명을 변경할 수 없습니다.",
@@ -1126,10 +1134,10 @@
"unable_to_remove_library": "라이브러리를 제거할 수 없습니다.", "unable_to_remove_library": "라이브러리를 제거할 수 없습니다.",
"unable_to_remove_partner": "파트너를 제거할 수 없습니다.", "unable_to_remove_partner": "파트너를 제거할 수 없습니다.",
"unable_to_remove_reaction": "반응을 제거할 수 없습니다.", "unable_to_remove_reaction": "반응을 제거할 수 없습니다.",
"unable_to_reset_password": "비밀번호를 초기화할 수 없", "unable_to_reset_password": "비밀번호를 초기화할 수 없습니다.",
"unable_to_reset_pin_code": "PIN 코드를 초기화할 수 없음", "unable_to_reset_pin_code": "PIN 코드를 초기화할 수 없음",
"unable_to_resolve_duplicate": "비슷한 항목을 처리할 수 없음", "unable_to_resolve_duplicate": "비슷한 항목을 처리할 수 없음",
"unable_to_restore_assets": "항목을 복원할 수 없", "unable_to_restore_assets": "항목을 복원할 수 없습니다.",
"unable_to_restore_trash": "휴지통을 복원할 수 없습니다.", "unable_to_restore_trash": "휴지통을 복원할 수 없습니다.",
"unable_to_restore_user": "사용자를 복원할 수 없습니다.", "unable_to_restore_user": "사용자를 복원할 수 없습니다.",
"unable_to_save_album": "앨범을 저장할 수 없습니다.", "unable_to_save_album": "앨범을 저장할 수 없습니다.",
@@ -1142,7 +1150,7 @@
"unable_to_scan_library": "라이브러리를 스캔할 수 없습니다.", "unable_to_scan_library": "라이브러리를 스캔할 수 없습니다.",
"unable_to_set_feature_photo": "대표 사진을 설정할 수 없습니다.", "unable_to_set_feature_photo": "대표 사진을 설정할 수 없습니다.",
"unable_to_set_profile_picture": "프로필 사진을 설정할 수 없습니다.", "unable_to_set_profile_picture": "프로필 사진을 설정할 수 없습니다.",
"unable_to_set_rating": "점을 정할 수 없", "unable_to_set_rating": "점을 정할 수 없습니다.",
"unable_to_submit_job": "작업을 수행할 수 없습니다.", "unable_to_submit_job": "작업을 수행할 수 없습니다.",
"unable_to_trash_asset": "휴지통으로 이동할 수 없습니다.", "unable_to_trash_asset": "휴지통으로 이동할 수 없습니다.",
"unable_to_unlink_account": "계정 연결을 해제할 수 없습니다.", "unable_to_unlink_account": "계정 연결을 해제할 수 없습니다.",
@@ -1381,9 +1389,11 @@
"library_page_sort_title": "앨범명", "library_page_sort_title": "앨범명",
"licenses": "라이선스", "licenses": "라이선스",
"light": "라이트", "light": "라이트",
"light_theme": "라이트 테마로 전환",
"like": "좋아요", "like": "좋아요",
"like_deleted": "좋아요가 삭제되었습니다.", "like_deleted": "좋아요가 삭제되었습니다.",
"link_motion_video": "모션 비디오 링크", "link_motion_video": "모션 비디오 링크",
"link_to_docs": "자세한 내용은 <link>문서</link>를 참조하십시오.",
"link_to_oauth": "OAuth에 연결", "link_to_oauth": "OAuth에 연결",
"linked_oauth_account": "OAuth 계정이 연결되었습니다.", "linked_oauth_account": "OAuth 계정이 연결되었습니다.",
"list": "목록", "list": "목록",
@@ -1645,6 +1655,7 @@
"only_favorites": "즐겨찾기만", "only_favorites": "즐겨찾기만",
"open": "열기", "open": "열기",
"open_calendar": "캘린더 열기", "open_calendar": "캘린더 열기",
"open_in_browser": "브라우저에서 열기",
"open_in_map_view": "지도 보기에서 열기", "open_in_map_view": "지도 보기에서 열기",
"open_in_openstreetmap": "OpenStreetMap에서 열기", "open_in_openstreetmap": "OpenStreetMap에서 열기",
"open_the_search_filters": "검색 필터 열기", "open_the_search_filters": "검색 필터 열기",
@@ -1801,11 +1812,11 @@
"purchase_settings_server_activated": "서버 제품 키는 관리자가 제어합니다.", "purchase_settings_server_activated": "서버 제품 키는 관리자가 제어합니다.",
"query_asset_id": "쿼리 항목 ID", "query_asset_id": "쿼리 항목 ID",
"queue_status": "전체 {total}, {count} 대기 중", "queue_status": "전체 {total}, {count} 대기 중",
"rate_asset": "항목 점", "rate_asset": "항목 점",
"rating": "별점", "rating": "별점",
"rating_clear": "점 초기화", "rating_clear": "점 초기화",
"rating_count": "{count, plural, =0 {점 없음} one {#점} other {#점}}", "rating_count": "{count, plural, =0 {점 없음} one {#점} other {#점}}",
"rating_description": "상세 정보 패널에 EXIF 등급 태그 표시", "rating_description": "상세 정보 패널에 EXIF 별점 태그 표시",
"reaction_options": "반응 옵션", "reaction_options": "반응 옵션",
"read_changelog": "변경 내역 보기", "read_changelog": "변경 내역 보기",
"readonly_mode_disabled": "읽기 전용 모드 비활성화", "readonly_mode_disabled": "읽기 전용 모드 비활성화",
@@ -1923,6 +1934,7 @@
"search_by_filename": "파일명 또는 확장자로 검색", "search_by_filename": "파일명 또는 확장자로 검색",
"search_by_filename_example": "예: IMG_1234.JPG 또는 PNG", "search_by_filename_example": "예: IMG_1234.JPG 또는 PNG",
"search_by_ocr": "OCR로 검색", "search_by_ocr": "OCR로 검색",
"search_by_ocr_example": "라떼",
"search_camera_lens_model": "렌즈 모델 검색...", "search_camera_lens_model": "렌즈 모델 검색...",
"search_camera_make": "카메라 제조사 검색...", "search_camera_make": "카메라 제조사 검색...",
"search_camera_model": "카메라 모델명 검색...", "search_camera_model": "카메라 모델명 검색...",
@@ -1942,7 +1954,7 @@
"search_filter_media_type_title": "미디어 종류 선택", "search_filter_media_type_title": "미디어 종류 선택",
"search_filter_ocr": "OCR 검색", "search_filter_ocr": "OCR 검색",
"search_filter_people_title": "인물 선택", "search_filter_people_title": "인물 선택",
"search_filter_star_rating": "점", "search_filter_star_rating": "점",
"search_filter_tags_title": "태그 선택", "search_filter_tags_title": "태그 선택",
"search_for": "검색", "search_for": "검색",
"search_for_existing_person": "존재하는 인물 검색", "search_for_existing_person": "존재하는 인물 검색",
@@ -1964,7 +1976,7 @@
"search_page_your_map": "나의 지도", "search_page_your_map": "나의 지도",
"search_people": "인물 검색", "search_people": "인물 검색",
"search_places": "장소 검색", "search_places": "장소 검색",
"search_rating": "등급으로 검색...", "search_rating": "별점으로 검색...",
"search_result_page_new_search_hint": "새 검색", "search_result_page_new_search_hint": "새 검색",
"search_settings": "설정 검색", "search_settings": "설정 검색",
"search_state": "지역 검색...", "search_state": "지역 검색...",
@@ -1986,6 +1998,7 @@
"select_all_in": "{group}의 모든 항목 선택", "select_all_in": "{group}의 모든 항목 선택",
"select_avatar_color": "아바타 색상 선택", "select_avatar_color": "아바타 색상 선택",
"select_count": "{count, plural, one {# 선택중} other {# 선택중}}", "select_count": "{count, plural, one {# 선택중} other {# 선택중}}",
"select_cutoff_date": "유지 기간 설정",
"select_face": "얼굴 선택", "select_face": "얼굴 선택",
"select_featured_photo": "대표 사진 선택", "select_featured_photo": "대표 사진 선택",
"select_from_computer": "컴퓨터에서 선택", "select_from_computer": "컴퓨터에서 선택",
@@ -2384,6 +2397,7 @@
"viewer_remove_from_stack": "스택에서 제거", "viewer_remove_from_stack": "스택에서 제거",
"viewer_stack_use_as_main_asset": "대표 항목으로 설정", "viewer_stack_use_as_main_asset": "대표 항목으로 설정",
"viewer_unstack": "스택 풀기", "viewer_unstack": "스택 풀기",
"visibility": "표시 설정",
"visibility_changed": "인물 {count, plural, one {#명} other {#명}}의 표시 여부가 변경됨", "visibility_changed": "인물 {count, plural, one {#명} other {#명}}의 표시 여부가 변경됨",
"visual": "비주얼", "visual": "비주얼",
"visual_builder": "비주얼 빌더", "visual_builder": "비주얼 빌더",
@@ -2414,7 +2428,7 @@
"yes": "네", "yes": "네",
"you_dont_have_any_shared_links": "공유 링크가 없습니다.", "you_dont_have_any_shared_links": "공유 링크가 없습니다.",
"your_wifi_name": "Wi-Fi 네트워크 이름", "your_wifi_name": "Wi-Fi 네트워크 이름",
"zero_to_clear_rating": "0을 눌러 항목 점 초기화", "zero_to_clear_rating": "0을 눌러 항목 점 초기화",
"zoom_image": "이미지 확대", "zoom_image": "이미지 확대",
"zoom_to_bounds": "화면에 맞춰 확대" "zoom_to_bounds": "화면에 맞춰 확대"
} }
+7 -3
View File
@@ -849,9 +849,12 @@
"create_link_to_share": "Sukurti bendrinimo nuorodą", "create_link_to_share": "Sukurti bendrinimo nuorodą",
"create_link_to_share_description": "Leisti bet kam su nuoroda matyti pažymėtą(-as) nuotrauką(-as)", "create_link_to_share_description": "Leisti bet kam su nuoroda matyti pažymėtą(-as) nuotrauką(-as)",
"create_new": "SUKURTI NAUJĄ", "create_new": "SUKURTI NAUJĄ",
"create_new_face": "Sukurti naują veidą",
"create_new_person": "Sukurti naują žmogų", "create_new_person": "Sukurti naują žmogų",
"create_new_person_hint": "Priskirti pasirinktus elementus naujam žmogui", "create_new_person_hint": "Priskirti pasirinktus elementus naujam žmogui",
"create_new_user": "Sukurti naują varotoją", "create_new_user": "Sukurti naują varotoją",
"create_person": "Sukurti asmenį",
"create_person_subtitle": "Pridėkite vardą prie pasirinkto veido, kad sukurtumėte ir pažymėtumėte naują asmenį",
"create_shared_album_page_share_add_assets": "PRIDĖTI ELEMENTŲ", "create_shared_album_page_share_add_assets": "PRIDĖTI ELEMENTŲ",
"create_shared_album_page_share_select_photos": "Pažymėti nuotraukas", "create_shared_album_page_share_select_photos": "Pažymėti nuotraukas",
"create_shared_link": "Sukurti dalijimosi nuorodą", "create_shared_link": "Sukurti dalijimosi nuorodą",
@@ -1212,7 +1215,7 @@
"file_name_text": "Failo pavadinimas", "file_name_text": "Failo pavadinimas",
"file_name_with_value": "Failo pavadinimas: {file_name}", "file_name_with_value": "Failo pavadinimas: {file_name}",
"file_size": "Failo dydis", "file_size": "Failo dydis",
"filename": "Failopavadinimas", "filename": "Failo pavadinimas",
"filetype": "Failo tipas", "filetype": "Failo tipas",
"filter": "Filtras", "filter": "Filtras",
"filter_description": "Tikslinių elementų filtravimo sąlygos", "filter_description": "Tikslinių elementų filtravimo sąlygos",
@@ -1387,8 +1390,8 @@
"licenses": "Licencijos", "licenses": "Licencijos",
"light": "Šviesi", "light": "Šviesi",
"light_theme": "Perjungti į šviesią temą", "light_theme": "Perjungti į šviesią temą",
"like": "Kaip", "like": "Patinka",
"like_deleted": "Kaip ištrintas", "like_deleted": "Patinka panaikintas",
"link_motion_video": "Susieti judesio vaizdo įrašą", "link_motion_video": "Susieti judesio vaizdo įrašą",
"link_to_docs": "Daugiau informacijos rasite <link>dokumentacijoje</link>.", "link_to_docs": "Daugiau informacijos rasite <link>dokumentacijoje</link>.",
"link_to_oauth": "Susieti su OAuth", "link_to_oauth": "Susieti su OAuth",
@@ -2214,6 +2217,7 @@
"tag": "Žyma", "tag": "Žyma",
"tag_assets": "Pažymėti", "tag_assets": "Pažymėti",
"tag_created": "Sukurta žyma: {tag}", "tag_created": "Sukurta žyma: {tag}",
"tag_face": "Pažymėti veidą",
"tag_feature_description": "Peržiūrėkite nuotraukas ir vaizdo įrašus sugrupuotus pagal sužymėtas temas", "tag_feature_description": "Peržiūrėkite nuotraukas ir vaizdo įrašus sugrupuotus pagal sužymėtas temas",
"tag_not_found_question": "Nerandate žymos? <link>Sukurti naują žymą.</link>", "tag_not_found_question": "Nerandate žymos? <link>Sukurti naują žymą.</link>",
"tag_people": "Pažymėti Žmones", "tag_people": "Pažymėti Žmones",
+7
View File
@@ -713,9 +713,11 @@
"create_link": "Izveidot saiti", "create_link": "Izveidot saiti",
"create_link_to_share": "Izveidot kopīgošanas saiti", "create_link_to_share": "Izveidot kopīgošanas saiti",
"create_new": "IZVEIDOT JAUNU", "create_new": "IZVEIDOT JAUNU",
"create_new_face": "Izveidot jaunu seju",
"create_new_person": "Izveidot jaunu personu", "create_new_person": "Izveidot jaunu personu",
"create_new_person_hint": "Piesaistīt izvēlētos failus jaunai personai", "create_new_person_hint": "Piesaistīt izvēlētos failus jaunai personai",
"create_new_user": "Izveidot jaunu lietotāju", "create_new_user": "Izveidot jaunu lietotāju",
"create_person": "Izveidot personu",
"create_shared_album_page_share_add_assets": "PIEVIENOT AKTĪVUS", "create_shared_album_page_share_add_assets": "PIEVIENOT AKTĪVUS",
"create_shared_album_page_share_select_photos": "Fotoattēlu Izvēle", "create_shared_album_page_share_select_photos": "Fotoattēlu Izvēle",
"create_user": "Izveidot lietotāju", "create_user": "Izveidot lietotāju",
@@ -879,6 +881,7 @@
"failed_to_update_notification_status": "Neizdevās mainīt paziņojuma statusu", "failed_to_update_notification_status": "Neizdevās mainīt paziņojuma statusu",
"incorrect_email_or_password": "Nepareizs e-pasts vai parole", "incorrect_email_or_password": "Nepareizs e-pasts vai parole",
"library_folder_already_exists": "Šis importa ceļš jau pastāv.", "library_folder_already_exists": "Šis importa ceļš jau pastāv.",
"page_not_found": "Lapa nav atrasta",
"profile_picture_transparent_pixels": "Profila attēlos nevar būt caurspīdīgi pikseļi. Lūdzu, palielini un/vai pārvieto attēlu.", "profile_picture_transparent_pixels": "Profila attēlos nevar būt caurspīdīgi pikseļi. Lūdzu, palielini un/vai pārvieto attēlu.",
"quota_higher_than_disk_size": "Tu esi iestatījis kvotu, kas pārsniedz diska izmēru", "quota_higher_than_disk_size": "Tu esi iestatījis kvotu, kas pārsniedz diska izmēru",
"something_went_wrong": "Kaut kas nogāja greizi", "something_went_wrong": "Kaut kas nogāja greizi",
@@ -1295,6 +1298,7 @@
"only_favorites": "Tikai izlase", "only_favorites": "Tikai izlase",
"open": "Atvērt", "open": "Atvērt",
"open_calendar": "Atvērt kalendāru", "open_calendar": "Atvērt kalendāru",
"open_in_browser": "Atvērt pārlūkprogrammā",
"open_in_map_view": "Atvērt kartes skatā", "open_in_map_view": "Atvērt kartes skatā",
"open_in_openstreetmap": "Atvērt OpenStreetMap", "open_in_openstreetmap": "Atvērt OpenStreetMap",
"open_the_search_filters": "Atvērt meklēšanas filtrus", "open_the_search_filters": "Atvērt meklēšanas filtrus",
@@ -1455,6 +1459,7 @@
"reset_people_visibility": "Atiestatīt personu redzamību", "reset_people_visibility": "Atiestatīt personu redzamību",
"reset_pin_code": "Atiestatīt PIN kodu", "reset_pin_code": "Atiestatīt PIN kodu",
"reset_sqlite": "Atiestatīt SQLite datubāzi", "reset_sqlite": "Atiestatīt SQLite datubāzi",
"reset_sqlite_clear_app_data": "Notīrīt datus",
"reset_to_default": "Atiestatīt noklusējuma iestatījumus", "reset_to_default": "Atiestatīt noklusējuma iestatījumus",
"resolve_duplicates": "Atrisināt dublēšanās gadījumus", "resolve_duplicates": "Atrisināt dublēšanās gadījumus",
"resolved_all_duplicates": "Visi dublikāti ir atrisināti", "resolved_all_duplicates": "Visi dublikāti ir atrisināti",
@@ -1705,6 +1710,7 @@
"sync_local": "Sinhronizēt lokāli", "sync_local": "Sinhronizēt lokāli",
"sync_status": "Sinhronizācijas statuss", "sync_status": "Sinhronizācijas statuss",
"sync_status_subtitle": "Skatīt un pārvaldīt sinhronizācijas sistēmu", "sync_status_subtitle": "Skatīt un pārvaldīt sinhronizācijas sistēmu",
"tag_face": "Atzīmēt seju",
"text_recognition": "Teksta atpazīšana", "text_recognition": "Teksta atpazīšana",
"theme": "Dizains", "theme": "Dizains",
"theme_setting_asset_list_storage_indicator_title": "Rādīt krātuves indikatoru uz attēliem režga skatā", "theme_setting_asset_list_storage_indicator_title": "Rādīt krātuves indikatoru uz attēliem režga skatā",
@@ -1833,6 +1839,7 @@
"viewer_remove_from_stack": "Noņemt no Steka", "viewer_remove_from_stack": "Noņemt no Steka",
"viewer_stack_use_as_main_asset": "Izmantot kā Galveno Aktīvu", "viewer_stack_use_as_main_asset": "Izmantot kā Galveno Aktīvu",
"viewer_unstack": "At-Stekot", "viewer_unstack": "At-Stekot",
"visibility": "Redzamība",
"visual": "Vizuāli", "visual": "Vizuāli",
"visual_builder": "Vizuālais veidotājs", "visual_builder": "Vizuālais veidotājs",
"waiting": "Gaida", "waiting": "Gaida",
+10 -6
View File
@@ -5,7 +5,7 @@
"acknowledge": "Bekreft", "acknowledge": "Bekreft",
"action": "Handling", "action": "Handling",
"action_common_update": "Oppdater", "action_common_update": "Oppdater",
"action_description": "Ett sett med handlinger som skal utføres på de filtrerede objekter", "action_description": "Ett sett handlinger som skal utføres på de filtrerte mediefilene",
"actions": "Handlinger", "actions": "Handlinger",
"active": "Aktiv", "active": "Aktiv",
"active_count": "Aktiv: {count}", "active_count": "Aktiv: {count}",
@@ -18,7 +18,7 @@
"add_a_title": "Legg til tittel", "add_a_title": "Legg til tittel",
"add_action": "Legg til hendelse", "add_action": "Legg til hendelse",
"add_action_description": "Trykk for å legge til en hendelse å utføre", "add_action_description": "Trykk for å legge til en hendelse å utføre",
"add_assets": "Legg til objekter", "add_assets": "Legg til mediefiler",
"add_birthday": "Legg til bursdag", "add_birthday": "Legg til bursdag",
"add_endpoint": "Legg til endepunkt", "add_endpoint": "Legg til endepunkt",
"add_exclusion_pattern": "Legg til ekskluderingsmønster", "add_exclusion_pattern": "Legg til ekskluderingsmønster",
@@ -34,7 +34,7 @@
"add_to_album": "Legg til album", "add_to_album": "Legg til album",
"add_to_album_bottom_sheet_added": "Lagt til i {album}", "add_to_album_bottom_sheet_added": "Lagt til i {album}",
"add_to_album_bottom_sheet_already_exists": "Allerede i {album}", "add_to_album_bottom_sheet_already_exists": "Allerede i {album}",
"add_to_album_bottom_sheet_some_local_assets": "Noen lokale elementer kunne ikke legges til i albumet", "add_to_album_bottom_sheet_some_local_assets": "Noen lokale filer kunne ikke legges til i albumet",
"add_to_album_toggle": "Avhuking for {album}", "add_to_album_toggle": "Avhuking for {album}",
"add_to_albums": "Legg til i album", "add_to_albums": "Legg til i album",
"add_to_albums_count": "Legg til i album ({count})", "add_to_albums_count": "Legg til i album ({count})",
@@ -50,8 +50,8 @@
"add_exclusion_pattern_description": "Legg til ekskluderingsmønstre. Globbing med *, ** og ? støttes. For å ignorere alle filer i en hvilken som helst mappe som heter \"Raw\", bruk \"**/Raw/**\". For å ignorere alle filer som slutter på \".tif\", bruk \"**/*.tif\". For å ignorere en absolutt filplassering, bruk \"/filsti/til/ignorer/**\".", "add_exclusion_pattern_description": "Legg til ekskluderingsmønstre. Globbing med *, ** og ? støttes. For å ignorere alle filer i en hvilken som helst mappe som heter \"Raw\", bruk \"**/Raw/**\". For å ignorere alle filer som slutter på \".tif\", bruk \"**/*.tif\". For å ignorere en absolutt filplassering, bruk \"/filsti/til/ignorer/**\".",
"admin_user": "Administrasjonsbruker", "admin_user": "Administrasjonsbruker",
"asset_offline_description": "Dette eksterne bibliotekselementet finnes ikke lenger på disk og har blitt flyttet til papirkurven. Hvis filen ble flyttet innad i biblioteket, se etter det tilsvarende elementet i tidslinjen din. For å gjenopprette elementet, vennligst sørg for at filstien under er tilgjengelig for Immich og skann biblioteket.", "asset_offline_description": "Dette eksterne bibliotekselementet finnes ikke lenger på disk og har blitt flyttet til papirkurven. Hvis filen ble flyttet innad i biblioteket, se etter det tilsvarende elementet i tidslinjen din. For å gjenopprette elementet, vennligst sørg for at filstien under er tilgjengelig for Immich og skann biblioteket.",
"authentication_settings": "Godkjenninger", "authentication_settings": "Godkjenings Instillinger",
"authentication_settings_description": "Administrer passord, OAuth, og andre innstillinger for autentisering", "authentication_settings_description": "Administrer passord, OAuth, og andre innstillinger for autentiserings Instilinger",
"authentication_settings_disable_all": "Er du sikker på at du ønsker å deaktivere alle innloggingsmetoder? Innlogging vil bli fullstendig deaktivert.", "authentication_settings_disable_all": "Er du sikker på at du ønsker å deaktivere alle innloggingsmetoder? Innlogging vil bli fullstendig deaktivert.",
"authentication_settings_reenable": "For å aktivere på nytt, bruk en <link>Server Command</link>.", "authentication_settings_reenable": "For å aktivere på nytt, bruk en <link>Server Command</link>.",
"background_task_job": "Bakgrunnsjobber", "background_task_job": "Bakgrunnsjobber",
@@ -81,7 +81,7 @@
"cron_expression_description": "Still inn skanneintervallet med cron-formatet. For mer informasjon henvises til f.eks. <link>Crontab Guru</link>", "cron_expression_description": "Still inn skanneintervallet med cron-formatet. For mer informasjon henvises til f.eks. <link>Crontab Guru</link>",
"cron_expression_presets": "Forhåndsinnstillinger for Cron-uttrykk", "cron_expression_presets": "Forhåndsinnstillinger for Cron-uttrykk",
"disable_login": "Deaktiver innlogging", "disable_login": "Deaktiver innlogging",
"duplicate_detection_job_description": "Kjør maskinlæring på filer for å oppdage lignende bilder. Krever bruk av Smart Search", "duplicate_detection_job_description": "Kjør maskinlæring på filer for å oppdage lignende bilder. Krever bruk av Smart Søk",
"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.", "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.",
"export_config_as_json_description": "Last ned nåværende systemkonfigurasjon som en JSON fil", "export_config_as_json_description": "Last ned nåværende systemkonfigurasjon som en JSON fil",
"external_libraries_page_description": "Administrering for eksterne bibliotek", "external_libraries_page_description": "Administrering for eksterne bibliotek",
@@ -849,9 +849,12 @@
"create_link_to_share": "Opprett delelink", "create_link_to_share": "Opprett delelink",
"create_link_to_share_description": "La alle med lenken se de(t) valgte bildet/bildene", "create_link_to_share_description": "La alle med lenken se de(t) valgte bildet/bildene",
"create_new": "LAG NY", "create_new": "LAG NY",
"create_new_face": "Opprett nytt ansikt",
"create_new_person": "Opprett ny person", "create_new_person": "Opprett ny person",
"create_new_person_hint": "Tildel valgte eiendeler til en ny person", "create_new_person_hint": "Tildel valgte eiendeler til en ny person",
"create_new_user": "Opprett ny bruker", "create_new_user": "Opprett ny bruker",
"create_person": "Opprett person",
"create_person_subtitle": "Gi det valgte ansiktet et navn for å opprette og tagge den nye personen",
"create_shared_album_page_share_add_assets": "LEGG TIL OBJEKTER", "create_shared_album_page_share_add_assets": "LEGG TIL OBJEKTER",
"create_shared_album_page_share_select_photos": "Velg bilder", "create_shared_album_page_share_select_photos": "Velg bilder",
"create_shared_link": "Opprett delt lenke", "create_shared_link": "Opprett delt lenke",
@@ -2214,6 +2217,7 @@
"tag": "Tagg", "tag": "Tagg",
"tag_assets": "Merk ressurser", "tag_assets": "Merk ressurser",
"tag_created": "Lag merke: {tag}", "tag_created": "Lag merke: {tag}",
"tag_face": "Tagg ansikt",
"tag_feature_description": "Bla gjennom bilder og videoer gruppert etter logiske merke-emner", "tag_feature_description": "Bla gjennom bilder og videoer gruppert etter logiske merke-emner",
"tag_not_found_question": "Finner du ikke en merke? <link>Opprett en nytt merke.</link>", "tag_not_found_question": "Finner du ikke en merke? <link>Opprett en nytt merke.</link>",
"tag_people": "Tag personer", "tag_people": "Tag personer",
+30 -26
View File
@@ -544,7 +544,7 @@
"appears_in": "Komt voor in", "appears_in": "Komt voor in",
"apply_count": "Toepassen ({count, number})", "apply_count": "Toepassen ({count, number})",
"archive": "Archief", "archive": "Archief",
"archive_action_prompt": "{count} item(s) toegevoegd aan het archief", "archive_action_prompt": "{count, plural, one {# item} other {# items}} toegevoegd aan het archief",
"archive_or_unarchive_photo": "Foto archiveren of uit het archief halen", "archive_or_unarchive_photo": "Foto archiveren of uit het archief halen",
"archive_page_no_archived_assets": "Geen gearchiveerde items gevonden", "archive_page_no_archived_assets": "Geen gearchiveerde items gevonden",
"archive_page_title": "Archief ({count})", "archive_page_title": "Archief ({count})",
@@ -593,20 +593,20 @@
"assets_cannot_be_added_to_album_count": "{count, plural, one {# item} other {# items}} konden niet aan album toegevoegd worden", "assets_cannot_be_added_to_album_count": "{count, plural, one {# item} other {# items}} konden niet aan album toegevoegd worden",
"assets_cannot_be_added_to_albums": "{count, plural, one {Item kan} other {Items kunnen}} niet toegevoegd worden aan de albums", "assets_cannot_be_added_to_albums": "{count, plural, one {Item kan} other {Items kunnen}} niet toegevoegd worden aan de albums",
"assets_count": "{count, plural, one {# item} other {# items}}", "assets_count": "{count, plural, one {# item} other {# items}}",
"assets_deleted_permanently": "{count} item(s) permanent verwijderd", "assets_deleted_permanently": "{count, plural, one {# item} other {# items}} permanent verwijderd",
"assets_deleted_permanently_from_server": "{count} item(s) permanent verwijderd van de Immich server", "assets_deleted_permanently_from_server": "{count, plural, one {# item} other {# items}} permanent verwijderd van de Immich server",
"assets_downloaded_failed": "{count, plural, one {# bestand gedownload - {error} bestand mislukt} other {# bestanden gedownload - {error} bestanden mislukt}}", "assets_downloaded_failed": "{count, plural, one {# bestand gedownload - {error} bestand mislukt} other {# bestanden gedownload - {error} bestanden mislukt}}",
"assets_downloaded_successfully": "{count, plural, one {# bestand succesvol gedownload} other {# bestanden succesvol gedownload}}", "assets_downloaded_successfully": "{count, plural, one {# bestand succesvol gedownload} other {# bestanden succesvol gedownload}}",
"assets_moved_to_trash_count": "{count, plural, one {# item} other {# items}} verplaatst naar prullenbak", "assets_moved_to_trash_count": "{count, plural, one {# item} other {# items}} verplaatst naar prullenbak",
"assets_permanently_deleted_count": "{count, plural, one {# item} other {# items}} permanent verwijderd", "assets_permanently_deleted_count": "{count, plural, one {# item} other {# items}} permanent verwijderd",
"assets_removed_count": "{count, plural, one {# item} other {# items}} verwijderd", "assets_removed_count": "{count, plural, one {# item} other {# items}} verwijderd",
"assets_removed_permanently_from_device": "{count} item(s) permanent verwijderd van je apparaat", "assets_removed_permanently_from_device": "{count, plural, one {# item} other {# items}} permanent verwijderd van je apparaat",
"assets_restore_confirmation": "Weet je zeker dat je alle verwijderde items wilt herstellen? Je kunt deze actie niet ongedaan maken! Offline items kunnen op deze manier niet worden hersteld.", "assets_restore_confirmation": "Weet je zeker dat je alle verwijderde items wilt herstellen? Je kunt deze actie niet ongedaan maken! Offline items kunnen op deze manier niet worden hersteld.",
"assets_restored_count": "{count, plural, one {# item} other {# items}} hersteld", "assets_restored_count": "{count, plural, one {# item} other {# items}} hersteld",
"assets_restored_successfully": "{count} item(s) succesvol hersteld", "assets_restored_successfully": "{count, plural, one {# item} other {# items}} succesvol hersteld",
"assets_trashed": "{count} item(s) naar de prullenbak verplaatst", "assets_trashed": "{count, plural, one {# item} other {# items}} naar de prullenbak verplaatst",
"assets_trashed_count": "{count, plural, one {# item} other {# items}} naar prullenbak verplaatst", "assets_trashed_count": "{count, plural, one {# item} other {# items}} naar prullenbak verplaatst",
"assets_trashed_from_server": "{count} item(s) naar de prullenbak verplaatst op de Immich server", "assets_trashed_from_server": "{count, plural, one {# item} other {# items}} naar de prullenbak verplaatst op de Immich server",
"assets_were_part_of_album_count": "{count, plural, one {Item was} other {Items waren}} al onderdeel van het album", "assets_were_part_of_album_count": "{count, plural, one {Item was} other {Items waren}} al onderdeel van het album",
"assets_were_part_of_albums_count": "{count, plural, one {Item is} other {Items zijn}} al onderdeel van de albums", "assets_were_part_of_albums_count": "{count, plural, one {Item is} other {Items zijn}} al onderdeel van de albums",
"authorized_devices": "Geautoriseerde apparaten", "authorized_devices": "Geautoriseerde apparaten",
@@ -849,9 +849,12 @@
"create_link_to_share": "Gedeelde link maken", "create_link_to_share": "Gedeelde link maken",
"create_link_to_share_description": "Laat iedereen met de link de geselecteerde foto(s) zien", "create_link_to_share_description": "Laat iedereen met de link de geselecteerde foto(s) zien",
"create_new": "MAAK NIEUW", "create_new": "MAAK NIEUW",
"create_new_face": "Nieuw gezicht aanmaken",
"create_new_person": "Nieuwe persoon aanmaken", "create_new_person": "Nieuwe persoon aanmaken",
"create_new_person_hint": "Geselecteerde items toewijzen aan een nieuwe persoon", "create_new_person_hint": "Geselecteerde items toewijzen aan een nieuwe persoon",
"create_new_user": "Nieuwe gebruiker aanmaken", "create_new_user": "Nieuwe gebruiker aanmaken",
"create_person": "Persoon aanmaken",
"create_person_subtitle": "Voeg een naam toe aan het geselecteerde gezicht om de nieuwe persoon aan te maken en te taggen",
"create_shared_album_page_share_add_assets": "ITEMS TOEVOEGEN", "create_shared_album_page_share_add_assets": "ITEMS TOEVOEGEN",
"create_shared_album_page_share_select_photos": "Selecteer foto's", "create_shared_album_page_share_select_photos": "Selecteer foto's",
"create_shared_link": "Gedeelde link maken", "create_shared_link": "Gedeelde link maken",
@@ -873,7 +876,7 @@
"current_server_address": "Huidig serveradres", "current_server_address": "Huidig serveradres",
"custom_date": "Aangepaste datum", "custom_date": "Aangepaste datum",
"custom_locale": "Aangepaste landinstelling", "custom_locale": "Aangepaste landinstelling",
"custom_locale_description": "Formatteer datums, tijden en getallen op basis van de geselecteerde taal en de regio", "custom_locale_description": "Formatteer datums, tijden, en getallen op basis van de geselecteerde taal en regio",
"custom_url": "Aangepaste URL", "custom_url": "Aangepaste URL",
"cutoff_date_description": "Bewaar foto's van de laatste…", "cutoff_date_description": "Bewaar foto's van de laatste…",
"cutoff_day": "{count, plural, one {dag} other {dagen}}", "cutoff_day": "{count, plural, one {dag} other {dagen}}",
@@ -893,10 +896,10 @@
"days": "Dagen", "days": "Dagen",
"deduplicate_all": "Alles dedupliceren", "deduplicate_all": "Alles dedupliceren",
"default_locale": "Standaard landinstelling", "default_locale": "Standaard landinstelling",
"default_locale_description": "Formatteer datums en getallen op basis van de taalinstellingen van uw browser", "default_locale_description": "Formatteer datums en getallen op basis van de taalinstellingen van je browser",
"delete": "Verwijderen", "delete": "Verwijderen",
"delete_action_confirmation_message": "Weet je zeker dat je dit item wilt verwijderen? Deze actie zorgt ervoor dat het item naar de prullenbak van de server wordt verplaatst en je wordt gevraagd of je deze ook lokaal wilt verwijderen", "delete_action_confirmation_message": "Weet je zeker dat je dit item wilt verwijderen? Deze actie zorgt ervoor dat het item naar de prullenbak van de server wordt verplaatst en je wordt gevraagd of je deze ook lokaal wilt verwijderen",
"delete_action_prompt": "{count} item(s) verwijderd", "delete_action_prompt": "{count} verwijderd",
"delete_album": "Album verwijderen", "delete_album": "Album verwijderen",
"delete_api_key_prompt": "Weet je zeker dat je deze API-sleutel wilt verwijderen?", "delete_api_key_prompt": "Weet je zeker dat je deze API-sleutel wilt verwijderen?",
"delete_dialog_alert": "Deze items zullen permanent verwijderd worden van Immich en je apparaat", "delete_dialog_alert": "Deze items zullen permanent verwijderd worden van Immich en je apparaat",
@@ -910,12 +913,12 @@
"delete_key": "Verwijder key", "delete_key": "Verwijder key",
"delete_library": "Verwijder bibliotheek", "delete_library": "Verwijder bibliotheek",
"delete_link": "Verwijder link", "delete_link": "Verwijder link",
"delete_local_action_prompt": "{count} item(s) lokaal verwijderd", "delete_local_action_prompt": "{count} lokaal verwijderd",
"delete_local_dialog_ok_backed_up_only": "Verwijder alleen met back-up", "delete_local_dialog_ok_backed_up_only": "Verwijder alleen met back-up",
"delete_local_dialog_ok_force": "Toch verwijderen", "delete_local_dialog_ok_force": "Toch verwijderen",
"delete_others": "Andere verwijderen", "delete_others": "Andere verwijderen",
"delete_permanently": "Permanent verwijderen", "delete_permanently": "Permanent verwijderen",
"delete_permanently_action_prompt": "{count} item(s) permanent verwijderd", "delete_permanently_action_prompt": "{count} permanent verwijderd",
"delete_shared_link": "Verwijder gedeelde link", "delete_shared_link": "Verwijder gedeelde link",
"delete_shared_link_dialog_title": "Verwijder gedeelde link", "delete_shared_link_dialog_title": "Verwijder gedeelde link",
"delete_tag": "Tag verwijderen", "delete_tag": "Tag verwijderen",
@@ -945,7 +948,7 @@
"documentation": "Documentatie", "documentation": "Documentatie",
"done": "Klaar", "done": "Klaar",
"download": "Downloaden", "download": "Downloaden",
"download_action_prompt": "{count} item(s) aan het downloaden", "download_action_prompt": "{count, plural, one {# item} other {# items}} aan het downloaden",
"download_canceled": "Download geannuleerd", "download_canceled": "Download geannuleerd",
"download_complete": "Download voltooid", "download_complete": "Download voltooid",
"download_enqueue": "Download in wachtrij", "download_enqueue": "Download in wachtrij",
@@ -977,7 +980,7 @@
"edit_birthday": "Wijzig verjaardag", "edit_birthday": "Wijzig verjaardag",
"edit_date": "Datum bewerken", "edit_date": "Datum bewerken",
"edit_date_and_time": "Datum en tijd bewerken", "edit_date_and_time": "Datum en tijd bewerken",
"edit_date_and_time_action_prompt": "Datum en tijd bijgewerkt van {count} item(s)", "edit_date_and_time_action_prompt": "Datum en tijd bijgewerkt van {count, plural, one {# item} other {# items}}",
"edit_date_and_time_by_offset": "Wijzigen datum door verschuiving", "edit_date_and_time_by_offset": "Wijzigen datum door verschuiving",
"edit_date_and_time_by_offset_interval": "Nieuw datuminterval: {from}-{to}", "edit_date_and_time_by_offset_interval": "Nieuw datuminterval: {from}-{to}",
"edit_description": "Beschrijving bewerken", "edit_description": "Beschrijving bewerken",
@@ -987,7 +990,7 @@
"edit_key": "Key bewerken", "edit_key": "Key bewerken",
"edit_link": "Link bewerken", "edit_link": "Link bewerken",
"edit_location": "Locatie bewerken", "edit_location": "Locatie bewerken",
"edit_location_action_prompt": "Locatie bijgewerkt van {count} item(s)", "edit_location_action_prompt": "Locatie bijgewerkt van {count, plural, one {# item} other {# items}}",
"edit_location_dialog_title": "Locatie", "edit_location_dialog_title": "Locatie",
"edit_name": "Naam bewerken", "edit_name": "Naam bewerken",
"edit_people": "Mensen bewerken", "edit_people": "Mensen bewerken",
@@ -1200,7 +1203,7 @@
"failed_to_load_assets": "Kan items niet laden", "failed_to_load_assets": "Kan items niet laden",
"failed_to_load_folder": "Laden van map mislukt", "failed_to_load_folder": "Laden van map mislukt",
"favorite": "Favoriet", "favorite": "Favoriet",
"favorite_action_prompt": "{count} item(s) toegevoegd aan je favorieten", "favorite_action_prompt": "{count, plural, one {# item} other {# items}} toegevoegd aan je favorieten",
"favorite_or_unfavorite_photo": "Foto markeren als of verwijderen uit favorieten", "favorite_or_unfavorite_photo": "Foto markeren als of verwijderen uit favorieten",
"favorites": "Favorieten", "favorites": "Favorieten",
"favorites_page_no_favorites": "Geen favoriete items gevonden", "favorites_page_no_favorites": "Geen favoriete items gevonden",
@@ -1386,7 +1389,7 @@
"library_page_sort_title": "Albumtitel", "library_page_sort_title": "Albumtitel",
"licenses": "Licenties", "licenses": "Licenties",
"light": "Licht", "light": "Licht",
"light_theme": "Wissel naar lichte thema", "light_theme": "Wissel naar licht thema",
"like": "Vind ik leuk", "like": "Vind ik leuk",
"like_deleted": "Like verwijderd", "like_deleted": "Like verwijderd",
"link_motion_video": "Koppel bewegende video", "link_motion_video": "Koppel bewegende video",
@@ -1548,7 +1551,7 @@
"move_off_locked_folder": "Verplaats uit vergrendelde map", "move_off_locked_folder": "Verplaats uit vergrendelde map",
"move_to": "Verplaatsen naar", "move_to": "Verplaatsen naar",
"move_to_device_trash": "Naar prullenbak van apparaat", "move_to_device_trash": "Naar prullenbak van apparaat",
"move_to_lock_folder_action_prompt": "{count} item(s) toegevoegd aan de vergrendelde map", "move_to_lock_folder_action_prompt": "{count, plural, one {# item} other {# items}} toegevoegd aan de vergrendelde map",
"move_to_locked_folder": "Verplaats naar vergrendelde map", "move_to_locked_folder": "Verplaats naar vergrendelde map",
"move_to_locked_folder_confirmation": "Deze fotos en videos worden uit alle albums verwijderd en zijn alleen te bekijken in de vergrendelde map", "move_to_locked_folder_confirmation": "Deze fotos en videos worden uit alle albums verwijderd en zijn alleen te bekijken in de vergrendelde map",
"move_up": "Naar boven verplaatsen", "move_up": "Naar boven verplaatsen",
@@ -1851,9 +1854,9 @@
"remove_custom_date_range": "Aangepast datumbereik verwijderen", "remove_custom_date_range": "Aangepast datumbereik verwijderen",
"remove_deleted_assets": "Verwijder offline bestanden", "remove_deleted_assets": "Verwijder offline bestanden",
"remove_from_album": "Verwijderen uit album", "remove_from_album": "Verwijderen uit album",
"remove_from_album_action_prompt": "{count} item(s) verwijderd uit het album", "remove_from_album_action_prompt": "{count, plural, one {# item} other {# items}} verwijderd uit het album",
"remove_from_favorites": "Verwijderen uit favorieten", "remove_from_favorites": "Verwijderen uit favorieten",
"remove_from_lock_folder_action_prompt": "{count} item(s) verwijderd uit de vergrendelde map", "remove_from_lock_folder_action_prompt": "{count, plural, one {# item} other {# items}} verwijderd uit de vergrendelde map",
"remove_from_locked_folder": "Verwijder uit de vergrendelde map", "remove_from_locked_folder": "Verwijder uit de vergrendelde map",
"remove_from_locked_folder_confirmation": "Weet je zeker dat je deze foto's en video's uit de vergrendelde map wilt verplaatsen? Ze zijn dan weer zichtbaar in je bibliotheek.", "remove_from_locked_folder_confirmation": "Weet je zeker dat je deze foto's en video's uit de vergrendelde map wilt verplaatsen? Ze zijn dan weer zichtbaar in je bibliotheek.",
"remove_from_shared_link": "Verwijderen uit gedeelde link", "remove_from_shared_link": "Verwijderen uit gedeelde link",
@@ -1896,7 +1899,7 @@
"resolved_all_duplicates": "Alle duplicaten opgelost", "resolved_all_duplicates": "Alle duplicaten opgelost",
"restore": "Herstellen", "restore": "Herstellen",
"restore_all": "Herstel alle", "restore_all": "Herstel alle",
"restore_trash_action_prompt": "{count} item(s) teruggehaald uit de prullenbak", "restore_trash_action_prompt": "{count, plural, one {# item} other {# items}} teruggehaald uit de prullenbak",
"restore_user": "Gebruiker herstellen", "restore_user": "Gebruiker herstellen",
"restored_asset": "Item hersteld", "restored_asset": "Item hersteld",
"resume": "Hervatten", "resume": "Hervatten",
@@ -2064,9 +2067,9 @@
"settings_saved": "Instellingen opgeslagen", "settings_saved": "Instellingen opgeslagen",
"setup_pin_code": "Stel een pincode in", "setup_pin_code": "Stel een pincode in",
"share": "Delen", "share": "Delen",
"share_action_prompt": "{count} item(s) gedeeld", "share_action_prompt": "{count, plural, one {# item} other {# items}} gedeeld",
"share_add_photos": "Foto's toevoegen", "share_add_photos": "Foto's toevoegen",
"share_assets_selected": "{count} item(s) geselecteerd", "share_assets_selected": "{count, plural, one {# item} other {# items}} geselecteerd",
"share_dialog_preparing": "Voorbereiden...", "share_dialog_preparing": "Voorbereiden...",
"share_link": "Link delen", "share_link": "Link delen",
"shared": "Gedeeld", "shared": "Gedeeld",
@@ -2174,7 +2177,7 @@
"sort_title": "Titel", "sort_title": "Titel",
"source": "Bron", "source": "Bron",
"stack": "Stapel", "stack": "Stapel",
"stack_action_prompt": "{count} item(s) gestapeld", "stack_action_prompt": "{count} items gestapeld",
"stack_duplicates": "Stapel duplicaten", "stack_duplicates": "Stapel duplicaten",
"stack_select_one_photo": "Selecteer één primaire foto voor de stapel", "stack_select_one_photo": "Selecteer één primaire foto voor de stapel",
"stack_selected_photos": "Geselecteerde foto's stapelen", "stack_selected_photos": "Geselecteerde foto's stapelen",
@@ -2214,6 +2217,7 @@
"tag": "Tag", "tag": "Tag",
"tag_assets": "Items taggen", "tag_assets": "Items taggen",
"tag_created": "Tag aangemaakt: {tag}", "tag_created": "Tag aangemaakt: {tag}",
"tag_face": "Gezicht labelen",
"tag_feature_description": "Bladeren door foto's en video's gegroepeerd op tags", "tag_feature_description": "Bladeren door foto's en video's gegroepeerd op tags",
"tag_not_found_question": "Kun je een tag niet vinden? <link>Maak een nieuwe tag.</link>", "tag_not_found_question": "Kun je een tag niet vinden? <link>Maak een nieuwe tag.</link>",
"tag_people": "Mensen taggen", "tag_people": "Mensen taggen",
@@ -2260,7 +2264,7 @@
"total": "Totaal", "total": "Totaal",
"total_usage": "Totaal gebruik", "total_usage": "Totaal gebruik",
"trash": "Prullenbak", "trash": "Prullenbak",
"trash_action_prompt": "{count} item(s) verplaatst naar de prullenbak", "trash_action_prompt": "{count, plural, one {# item} other {# items}} verplaatst naar de prullenbak",
"trash_all": "Verplaats alle naar prullenbak", "trash_all": "Verplaats alle naar prullenbak",
"trash_count": "{count, number} naar prullenbak", "trash_count": "{count, number} naar prullenbak",
"trash_delete_asset": "Items naar prullenbak verplaatsen of verwijderen", "trash_delete_asset": "Items naar prullenbak verplaatsen of verwijderen",
@@ -2310,7 +2314,7 @@
"unselect_all_duplicates": "Deselecteer alle duplicaten", "unselect_all_duplicates": "Deselecteer alle duplicaten",
"unselect_all_in": "Deselecteer alles in {group}", "unselect_all_in": "Deselecteer alles in {group}",
"unstack": "Ontstapelen", "unstack": "Ontstapelen",
"unstack_action_prompt": "{count} item(s) ontstapeld", "unstack_action_prompt": "{count} items ontstapeld",
"unstacked_assets_count": "{count, plural, one {# item} other {# items}} ontstapeld", "unstacked_assets_count": "{count, plural, one {# item} other {# items}} ontstapeld",
"unsupported_field_type": "Veldtype niet ondersteund", "unsupported_field_type": "Veldtype niet ondersteund",
"unsupported_file_type": "Bestand {file} kan niet worden geüpload omdat het bestandstype {type} niet wordt ondersteund.", "unsupported_file_type": "Bestand {file} kan niet worden geüpload omdat het bestandstype {type} niet wordt ondersteund.",
+18
View File
@@ -37,8 +37,10 @@
"add_to_album_bottom_sheet_some_local_assets": "Somme lokale eigedelar kunne ikkje leggjast til i album", "add_to_album_bottom_sheet_some_local_assets": "Somme lokale eigedelar kunne ikkje leggjast til i album",
"add_to_albums": "Legg til i album", "add_to_albums": "Legg til i album",
"add_to_albums_count": "Legg til i album ({count})", "add_to_albums_count": "Legg til i album ({count})",
"add_to_bottom_bar": "Legg til i",
"add_to_shared_album": "Legg til i delt album", "add_to_shared_album": "Legg til i delt album",
"add_url": "Legg til URL", "add_url": "Legg til URL",
"add_workflow_step": "Legg til steg i arbeidsflyt",
"added_to_archive": "Lagt til i arkiv", "added_to_archive": "Lagt til i arkiv",
"added_to_favorites": "Lagt til i favorittar", "added_to_favorites": "Lagt til i favorittar",
"added_to_favorites_count": "La til {count, number} i favorittar", "added_to_favorites_count": "La til {count, number} i favorittar",
@@ -71,6 +73,7 @@
"confirm_reprocess_all_faces": "Er du sikker på at du vil behandle alle ansikt på nytt? Det vil òg fjerne namngjevne personar.", "confirm_reprocess_all_faces": "Er du sikker på at du vil behandle alle ansikt på nytt? Det vil òg fjerne namngjevne personar.",
"confirm_user_password_reset": "Er du sikker at du vil tilbakestille passordet til {user}?", "confirm_user_password_reset": "Er du sikker at du vil tilbakestille passordet til {user}?",
"confirm_user_pin_code_reset": "Er du sikker på at du vil tilbakestille {user} sin PIN-kode?", "confirm_user_pin_code_reset": "Er du sikker på at du vil tilbakestille {user} sin PIN-kode?",
"copy_config_to_clipboard_description": "Kopier systemkonfigurasjonen som eit JSON-objekt til utklippstavla",
"create_job": "Lag jobb", "create_job": "Lag jobb",
"cron_expression": "Cron uttrykk", "cron_expression": "Cron uttrykk",
"cron_expression_description": "Set inn skanningsintervall med cron-formatet. For meir informasjon sjå t.d. <link>Crontab Guru</link>", "cron_expression_description": "Set inn skanningsintervall med cron-formatet. For meir informasjon sjå t.d. <link>Crontab Guru</link>",
@@ -78,6 +81,7 @@
"disable_login": "Deaktiver innlogging", "disable_login": "Deaktiver innlogging",
"duplicate_detection_job_description": "Kjør maskinlæring på filer for å oppdage liknande bilete. Krev bruk av Smart Search", "duplicate_detection_job_description": "Kjør maskinlæring på filer for å oppdage liknande bilete. Krev bruk av Smart Search",
"exclusion_pattern_description": "Utelatingsmønster let deg utelate filer og mapper når du skannar biblioteket ditt. Det er nyttig om du har mapper som inneheld filer du ikkje ynskjer å importere, til dømes RAW-filer.", "exclusion_pattern_description": "Utelatingsmønster let deg utelate filer og mapper når du skannar biblioteket ditt. Det er nyttig om du har mapper som inneheld filer du ikkje ynskjer å importere, til dømes RAW-filer.",
"export_config_as_json_description": "Last ned nåverande systemkonfigurasjon som ei JSON-fil",
"face_detection": "Ansiktssøk", "face_detection": "Ansiktssøk",
"face_detection_description": "Finn ansikt i bilete ved hjelp av maskinlæring. For videoar vert berre miniatyrbilete bruka. \"Alle\" søkjer (opp att) gjennom alle bilete. \"Tilbakestill\" fjernar all gjeldande ansiktsdata. \"Manglande\" legg filer som ikkje vert behandla til i køa for ansiktssøk. Oppdaga ansikt vert lagt i køa for ansiktsattkjenning, og kopla til eksisterande eller nye personar.", "face_detection_description": "Finn ansikt i bilete ved hjelp av maskinlæring. For videoar vert berre miniatyrbilete bruka. \"Alle\" søkjer (opp att) gjennom alle bilete. \"Tilbakestill\" fjernar all gjeldande ansiktsdata. \"Manglande\" legg filer som ikkje vert behandla til i køa for ansiktssøk. Oppdaga ansikt vert lagt i køa for ansiktsattkjenning, og kopla til eksisterande eller nye personar.",
"facial_recognition_job_description": "Koplar attkjende ansikt til personar. Det skjer fyrst når anskiktssøkjet er ferdig. \"Tilbakestill\" fjernar alle koplingar til personar, og tilbakestiller ansiktsgrupper. \"Manglande\" legg ansikt som ikkje er oppkopla til i køa.", "facial_recognition_job_description": "Koplar attkjende ansikt til personar. Det skjer fyrst når anskiktssøkjet er ferdig. \"Tilbakestill\" fjernar alle koplingar til personar, og tilbakestiller ansiktsgrupper. \"Manglande\" legg ansikt som ikkje er oppkopla til i køa.",
@@ -105,6 +109,7 @@
"image_thumbnail_description": "Lite miniatyrbilete med fjerna metadata, brukt når ein ser på grupper av bilete som hovudtidslinja", "image_thumbnail_description": "Lite miniatyrbilete med fjerna metadata, brukt når ein ser på grupper av bilete som hovudtidslinja",
"image_thumbnail_quality_description": "Kvalitet på miniatyrbilete frå 1-100. Høgare er betre, men gjev større filstorleik, og kan senkje appresposen.", "image_thumbnail_quality_description": "Kvalitet på miniatyrbilete frå 1-100. Høgare er betre, men gjev større filstorleik, og kan senkje appresposen.",
"image_thumbnail_title": "Innstillingar for miniatyrbilete", "image_thumbnail_title": "Innstillingar for miniatyrbilete",
"import_config_from_json_description": "Importer systemkonfigurasjon ved å laste opp ei JSON konfigurasjonsfil",
"job_concurrency": "{job} samstundes utføring", "job_concurrency": "{job} samstundes utføring",
"job_created": "Jobb laga", "job_created": "Jobb laga",
"job_not_concurrency_safe": "Kan ikke trygt utføre jobben samstundes.", "job_not_concurrency_safe": "Kan ikke trygt utføre jobben samstundes.",
@@ -112,22 +117,30 @@
"job_settings_description": "Handsam samstundes utføring av jobber", "job_settings_description": "Handsam samstundes utføring av jobber",
"jobs_delayed": "{jobCount, plural, other {# forsinka}}", "jobs_delayed": "{jobCount, plural, other {# forsinka}}",
"jobs_failed": "{jobCount, plural, other {# mislykkast}}", "jobs_failed": "{jobCount, plural, other {# mislykkast}}",
"jobs_over_time": "Jobbar over tid",
"library_created": "Opprett bibliotek: {library}", "library_created": "Opprett bibliotek: {library}",
"library_deleted": "Bibliotek sletta", "library_deleted": "Bibliotek sletta",
"library_details": "Bibliotekdetaljar",
"library_folder_description": "Vel ei mappe å importere. Denne mappa, inkludert undermappar, vil bli skanna for biletar og videoar.",
"library_remove_exclusion_pattern_prompt": "Er du sikker på at du vil fjerne dette unntaksmønsteret?",
"library_scanning": "Regelbunden skanning", "library_scanning": "Regelbunden skanning",
"library_scanning_description": "Sett opp regelbunden skanning av biblioteket", "library_scanning_description": "Sett opp regelbunden skanning av biblioteket",
"library_scanning_enable_description": "Aktiver regelbunden skanning av biblioteket", "library_scanning_enable_description": "Aktiver regelbunden skanning av biblioteket",
"library_settings": "Eksternt Bibliotek", "library_settings": "Eksternt Bibliotek",
"library_settings_description": "Handsam eksterne biblioteksinnstillingar", "library_settings_description": "Handsam eksterne biblioteksinnstillingar",
"library_tasks_description": "Utfør bibliotekstoppgåver", "library_tasks_description": "Utfør bibliotekstoppgåver",
"library_updated": "Oppdatert bibliotek",
"library_watching_enable_description": "Sjekk eksterne bibliotek for forandringar", "library_watching_enable_description": "Sjekk eksterne bibliotek for forandringar",
"library_watching_settings": "Biblioteksovervåking (EKSPERIMENTELL)", "library_watching_settings": "Biblioteksovervåking (EKSPERIMENTELL)",
"library_watching_settings_description": "Sjekk automatisk for forandringar", "library_watching_settings_description": "Sjekk automatisk for forandringar",
"logging_enable_description": "Aktiver loggføring", "logging_enable_description": "Aktiver loggføring",
"logging_level_description": "Når aktivert, kva loggnivå å bruke.", "logging_level_description": "Når aktivert, kva loggnivå å bruke.",
"logging_settings": "Logging", "logging_settings": "Logging",
"machine_learning_availability_checks": "Tilgjengelegheitssjekkar",
"machine_learning_availability_checks_description": "Automatiser oppdaging og prioritet av tilgjengelege maskinlærings-serverar", "machine_learning_availability_checks_description": "Automatiser oppdaging og prioritet av tilgjengelege maskinlærings-serverar",
"machine_learning_availability_checks_enabled": "Slå på tilgjengelegheitssjekkar",
"machine_learning_availability_checks_interval": "Sjekk intervall", "machine_learning_availability_checks_interval": "Sjekk intervall",
"machine_learning_availability_checks_timeout": "Tidsavbrot på forespørsel",
"machine_learning_availability_checks_timeout_description": "Utløpstid i millisekund for tilgjengelegheitssjekk", "machine_learning_availability_checks_timeout_description": "Utløpstid i millisekund for tilgjengelegheitssjekk",
"machine_learning_clip_model": "CLIP modell", "machine_learning_clip_model": "CLIP modell",
"machine_learning_clip_model_description": "Namnet på ein CLIP modell finst <link>her</link>. Merk at du må køyre 'Smart Søk'-jobben på nytt for alle bilete etter du har forandra modell.", "machine_learning_clip_model_description": "Namnet på ein CLIP modell finst <link>her</link>. Merk at du må køyre 'Smart Søk'-jobben på nytt for alle bilete etter du har forandra modell.",
@@ -151,6 +164,11 @@
"machine_learning_min_detection_score_description": "Minimum tillitspoeng for at eit ansikt skal bli oppdaga, på ein skala frå 0 til 1. Lågare verdiar vil oppdage fleire ansikt, men kan føre til feilaktige treff.", "machine_learning_min_detection_score_description": "Minimum tillitspoeng for at eit ansikt skal bli oppdaga, på ein skala frå 0 til 1. Lågare verdiar vil oppdage fleire ansikt, men kan føre til feilaktige treff.",
"machine_learning_min_recognized_faces": "Minimum gjenkjende ansikt", "machine_learning_min_recognized_faces": "Minimum gjenkjende ansikt",
"machine_learning_min_recognized_faces_description": "Minste tal på gjenkjende fjes for å opprette ein person. Aukar ein dette, vert ansiktsgjenkjenninga meir presis, på bekostning av auka sjanse for at ansikt ikkje vert tileigna ein person.", "machine_learning_min_recognized_faces_description": "Minste tal på gjenkjende fjes for å opprette ein person. Aukar ein dette, vert ansiktsgjenkjenninga meir presis, på bekostning av auka sjanse for at ansikt ikkje vert tileigna ein person.",
"machine_learning_ocr": "OCR",
"machine_learning_ocr_description": "Bruk maskinlæring for å gjenkjenne tekst i bilete",
"machine_learning_ocr_enabled": "Slå på OCR",
"machine_learning_ocr_max_resolution": "Maksimal oppløysing",
"machine_learning_ocr_model": "OCR-modell",
"machine_learning_settings": "Innstillingar for maskinlæring", "machine_learning_settings": "Innstillingar for maskinlæring",
"machine_learning_settings_description": "Administrer maskinlæringsfunksjonar og innstillingar", "machine_learning_settings_description": "Administrer maskinlæringsfunksjonar og innstillingar",
"machine_learning_smart_search": "Smart Søk", "machine_learning_smart_search": "Smart Søk",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-i18n", "name": "immich-i18n",
"version": "2.6.3", "version": "2.7.5",
"private": true, "private": true,
"scripts": { "scripts": {
"format": "prettier --cache --check .", "format": "prettier --cache --check .",
+4 -4
View File
@@ -1009,8 +1009,8 @@
"editor_edits_applied_success": "Zmiany zostały pomyślnie zastosowane", "editor_edits_applied_success": "Zmiany zostały pomyślnie zastosowane",
"editor_flip_horizontal": "Odwróć poziomo", "editor_flip_horizontal": "Odwróć poziomo",
"editor_flip_vertical": "Odwróć pionowo", "editor_flip_vertical": "Odwróć pionowo",
"editor_handle_corner": "{corner, select, top_left {Top-left} top_right {Top-right} bottom_left {Bottom-left} bottom_right {Bottom-right} other {A}} uchwyt narożny", "editor_handle_corner": "{corner, select, top_left {Górny lewy} top_right {Górny prawy} bottom_left {Dolny lewy} bottom_right {Dolny prawy} other {Jakiś}} uchwyt narożny",
"editor_handle_edge": "{edge, select, top {Top} bottom {Bottom} left {Left} right {Right} other {An}} uchwyt krawędziowy", "editor_handle_edge": "{edge, select, top {Górny} bottom {Dolny} left {Lewy} right {Prawy} other {Jakiś}} uchwyt krawędziowy",
"editor_orientation": "Orientacja", "editor_orientation": "Orientacja",
"editor_reset_all_changes": "Zresetuj zmiany", "editor_reset_all_changes": "Zresetuj zmiany",
"editor_rotate_left": "Obróć o 90° przeciwnie do ruchu wskazówek zegara", "editor_rotate_left": "Obróć o 90° przeciwnie do ruchu wskazówek zegara",
@@ -2224,12 +2224,12 @@
"tag_updated": "Uaktualniono etykietę: {tag}", "tag_updated": "Uaktualniono etykietę: {tag}",
"tagged_assets": "Przypisano etykietę {count, plural, one {# zasobowi} other {# zasobom}}", "tagged_assets": "Przypisano etykietę {count, plural, one {# zasobowi} other {# zasobom}}",
"tags": "Etykiety", "tags": "Etykiety",
"tap_to_run_job": "Uruchom zadanie", "tap_to_run_job": "Naciśnij, żeby uruchom zadanie",
"template": "Szablon", "template": "Szablon",
"text_recognition": "Rozpoznawanie tekstu", "text_recognition": "Rozpoznawanie tekstu",
"theme": "Motyw", "theme": "Motyw",
"theme_selection": "Wybór motywu", "theme_selection": "Wybór motywu",
"theme_selection_description": "Automatycznie zmień motyw na jasny lub ciemny zależnie od ustawień przeglądarki", "theme_selection_description": "Automatycznie zmień motyw na jasny lub ciemny zależnie od ustawień systemu",
"theme_setting_asset_list_storage_indicator_title": "Pokaż wskaźnik przechowywania na kafelkach zasobów", "theme_setting_asset_list_storage_indicator_title": "Pokaż wskaźnik przechowywania na kafelkach zasobów",
"theme_setting_asset_list_tiles_per_row_title": "Liczba zasobów w wierszu ({count})", "theme_setting_asset_list_tiles_per_row_title": "Liczba zasobów w wierszu ({count})",
"theme_setting_colorful_interface_subtitle": "Zastosuj kolor podstawowy do powierzchni tła.", "theme_setting_colorful_interface_subtitle": "Zastosuj kolor podstawowy do powierzchni tła.",
+4
View File
@@ -849,9 +849,12 @@
"create_link_to_share": "Criar link para partilhar", "create_link_to_share": "Criar link para partilhar",
"create_link_to_share_description": "Permitir a visualização desta(s) imagem(s) a qualquer pessoa com o link", "create_link_to_share_description": "Permitir a visualização desta(s) imagem(s) a qualquer pessoa com o link",
"create_new": "CRIAR NOVO", "create_new": "CRIAR NOVO",
"create_new_face": "Criar novo rosto",
"create_new_person": "Criar nova pessoa", "create_new_person": "Criar nova pessoa",
"create_new_person_hint": "Associe os ficheiros a uma nova pessoa", "create_new_person_hint": "Associe os ficheiros a uma nova pessoa",
"create_new_user": "Criar novo utilizador", "create_new_user": "Criar novo utilizador",
"create_person": "Criar pessoa",
"create_person_subtitle": "Adicione um nome ao rosto selecionado para criar e etiquetar a nova pessoa",
"create_shared_album_page_share_add_assets": "ADICIONAR FICHEIROS", "create_shared_album_page_share_add_assets": "ADICIONAR FICHEIROS",
"create_shared_album_page_share_select_photos": "Selecionar Fotos", "create_shared_album_page_share_select_photos": "Selecionar Fotos",
"create_shared_link": "Criar link partilhado", "create_shared_link": "Criar link partilhado",
@@ -2214,6 +2217,7 @@
"tag": "Etiqueta", "tag": "Etiqueta",
"tag_assets": "Etiquetar ficheiros", "tag_assets": "Etiquetar ficheiros",
"tag_created": "Criada a etiqueta {tag}", "tag_created": "Criada a etiqueta {tag}",
"tag_face": "Etiquetar rosto",
"tag_feature_description": "A mostrar fotos e videos agrupados por tópicos lógicos de etiquetas", "tag_feature_description": "A mostrar fotos e videos agrupados por tópicos lógicos de etiquetas",
"tag_not_found_question": "Não consegue encontrar a etiqueta? <link>Crie uma nova etiqueta.</link>", "tag_not_found_question": "Não consegue encontrar a etiqueta? <link>Crie uma nova etiqueta.</link>",
"tag_people": "Etiquetar Pessoas", "tag_people": "Etiquetar Pessoas",
+4
View File
@@ -849,9 +849,12 @@
"create_link_to_share": "Создать ссылку общего доступа", "create_link_to_share": "Создать ссылку общего доступа",
"create_link_to_share_description": "Разрешить всем, у кого есть ссылка, просматривать выбранные фотографии", "create_link_to_share_description": "Разрешить всем, у кого есть ссылка, просматривать выбранные фотографии",
"create_new": "СОЗДАТЬ НОВЫЙ", "create_new": "СОЗДАТЬ НОВЫЙ",
"create_new_face": "Создать новое лицо",
"create_new_person": "Добавить нового человека", "create_new_person": "Добавить нового человека",
"create_new_person_hint": "Назначить выбранные объекты на нового человека", "create_new_person_hint": "Назначить выбранные объекты на нового человека",
"create_new_user": "Создать нового пользователя", "create_new_user": "Создать нового пользователя",
"create_person": "Создать человека",
"create_person_subtitle": "Укажите имя для создания нового человека",
"create_shared_album_page_share_add_assets": "ДОБАВИТЬ ОБЪЕКТЫ", "create_shared_album_page_share_add_assets": "ДОБАВИТЬ ОБЪЕКТЫ",
"create_shared_album_page_share_select_photos": "Выбрать фотографии", "create_shared_album_page_share_select_photos": "Выбрать фотографии",
"create_shared_link": "Создать общую ссылку", "create_shared_link": "Создать общую ссылку",
@@ -2214,6 +2217,7 @@
"tag": "Тег", "tag": "Тег",
"tag_assets": "Добавить теги", "tag_assets": "Добавить теги",
"tag_created": "Тег {tag} создан", "tag_created": "Тег {tag} создан",
"tag_face": "Отметить человека",
"tag_feature_description": "Просмотр фотографий и видео, сгруппированных по тегам", "tag_feature_description": "Просмотр фотографий и видео, сгруппированных по тегам",
"tag_not_found_question": "Не удается найти тег? <link>Создайте новый тег.</link>", "tag_not_found_question": "Не удается найти тег? <link>Создайте новый тег.</link>",
"tag_people": "Отметить человека", "tag_people": "Отметить человека",
+4
View File
@@ -849,9 +849,12 @@
"create_link_to_share": "Ustvari povezavo za skupno rabo", "create_link_to_share": "Ustvari povezavo za skupno rabo",
"create_link_to_share_description": "Omogoči vsem s povezavo ogled izbranih fotografij", "create_link_to_share_description": "Omogoči vsem s povezavo ogled izbranih fotografij",
"create_new": "USTVARI NOVEGA", "create_new": "USTVARI NOVEGA",
"create_new_face": "Ustvari nov obraz",
"create_new_person": "Ustvari novo osebo", "create_new_person": "Ustvari novo osebo",
"create_new_person_hint": "Dodeli izbrana sredstva novi osebi", "create_new_person_hint": "Dodeli izbrana sredstva novi osebi",
"create_new_user": "Ustvari novega uporabnika", "create_new_user": "Ustvari novega uporabnika",
"create_person": "Ustvari osebo",
"create_person_subtitle": "Dodajte ime izbranemu obrazu, da ustvarite in označite novo osebo",
"create_shared_album_page_share_add_assets": "DODAJ SREDSTVA", "create_shared_album_page_share_add_assets": "DODAJ SREDSTVA",
"create_shared_album_page_share_select_photos": "Izberi fotografije", "create_shared_album_page_share_select_photos": "Izberi fotografije",
"create_shared_link": "Ustvari deljeno povezavo", "create_shared_link": "Ustvari deljeno povezavo",
@@ -2214,6 +2217,7 @@
"tag": "Oznaka", "tag": "Oznaka",
"tag_assets": "Označi sredstva", "tag_assets": "Označi sredstva",
"tag_created": "Ustvarjena oznaka: {tag}", "tag_created": "Ustvarjena oznaka: {tag}",
"tag_face": "Označi obraz",
"tag_feature_description": "Brskanje po fotografijah in videoposnetkih, razvrščenih po temah logičnih oznak", "tag_feature_description": "Brskanje po fotografijah in videoposnetkih, razvrščenih po temah logičnih oznak",
"tag_not_found_question": "Ne najdete oznake? <link>Ustvarite novo oznako.</link>", "tag_not_found_question": "Ne najdete oznake? <link>Ustvarite novo oznako.</link>",
"tag_people": "Označi osebe", "tag_people": "Označi osebe",
+4
View File
@@ -849,9 +849,12 @@
"create_link_to_share": "Skapa länk att dela", "create_link_to_share": "Skapa länk att dela",
"create_link_to_share_description": "Låt alla med länken se de valda fotona", "create_link_to_share_description": "Låt alla med länken se de valda fotona",
"create_new": "SKAPA NY", "create_new": "SKAPA NY",
"create_new_face": "Skapa nytt ansikte",
"create_new_person": "Skapa ny person", "create_new_person": "Skapa ny person",
"create_new_person_hint": "Tilldela valda objekt till en ny person", "create_new_person_hint": "Tilldela valda objekt till en ny person",
"create_new_user": "Skapa en ny användare", "create_new_user": "Skapa en ny användare",
"create_person": "Skapa person",
"create_person_subtitle": "Lägg till ett namn till det valda ansiktet för att skapa och tagga den nya personen",
"create_shared_album_page_share_add_assets": "LÄGG TILL OBJEKT", "create_shared_album_page_share_add_assets": "LÄGG TILL OBJEKT",
"create_shared_album_page_share_select_photos": "Välj bilder", "create_shared_album_page_share_select_photos": "Välj bilder",
"create_shared_link": "Skapa delad länk", "create_shared_link": "Skapa delad länk",
@@ -2214,6 +2217,7 @@
"tag": "Tagg", "tag": "Tagg",
"tag_assets": "Tagga objekt", "tag_assets": "Tagga objekt",
"tag_created": "Skapade tagg: {tag}", "tag_created": "Skapade tagg: {tag}",
"tag_face": "Tagga ansikte",
"tag_feature_description": "Bläddra bland foton och videor grupperade efter logiska taggar", "tag_feature_description": "Bläddra bland foton och videor grupperade efter logiska taggar",
"tag_not_found_question": "Kan du inte hitta en tagg? <link>Skapa en ny tagg.</link>", "tag_not_found_question": "Kan du inte hitta en tagg? <link>Skapa en ny tagg.</link>",
"tag_people": "Tagga Personer", "tag_people": "Tagga Personer",

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