Compare commits

..

105 Commits

Author SHA1 Message Date
mertalev
03c820931e commit json 2025-07-30 19:53:55 -04:00
mertalev
6cea779b2d optimizations 2025-07-30 19:30:42 -04:00
wuzihao051119
196f2a72f4 temporary change for stress test 2025-07-30 11:37:38 -04:00
wuzihao051119
7f9bc092ac remove locked and favorite 2025-07-30 11:37:38 -04:00
wuzihao051119
dc807777d6 clean code 2025-07-30 11:37:38 -04:00
wuzihao051119
6ad2e8e155 remove ref listen and global key 2025-07-30 11:37:38 -04:00
wuzihao051119
e6c46fe0bf chore: rename 2025-07-30 11:37:38 -04:00
wuzihao051119
749582b6d8 fix: refresh timeline by key 2025-07-30 11:37:38 -04:00
wuzihao051119
94c073e58f perf: do not filter markers 2025-07-30 11:37:38 -04:00
wuzihao051119
5d722eef98 refactor: map query 2025-07-30 11:37:38 -04:00
wuzihao051119
d308d023c2 feat(mobile): drift map page 2025-07-30 11:37:38 -04:00
shenlong
da5deffd03 fix: exclude assets from excluded albumbs on main timeline (#20425)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-07-30 10:16:23 -05:00
shenlong
9f20522df5 chore: add isFavorite to PlatformAsset in duplicate check (#20427)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-07-30 08:44:19 -05:00
Ben
baadf9db20 fix(web): timeline date group width (#19964)
Fix the calculation for the date group width, so there's never a scenario where photos will be hidden. On mobile devices, photos in the second row can sometimes have a top of <100px, which throws off the calculation of the date group width.
2025-07-30 08:39:19 -04:00
xCJPECKOVERx
4ea4ee40af fix(web): Search chip key value heights don't match (#20312)
- add flex items-stretch to stretch chip key height to match value height
2025-07-30 08:31:16 -04:00
renovate[bot]
d8a6552811 chore(deps): update ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0 docker digest to 32324a2 (#20381)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-30 08:29:37 -04:00
renovate[bot]
444133a72b chore(deps): update ghcr.io/immich-app/postgres:14-vectorchord0.3.0 docker digest to 0e763a2 (#20380)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-30 10:36:50 +00:00
Alex
29f16c6a47 feat: people page/sheet/detail (#20309) 2025-07-30 03:07:53 +00:00
shenlong
268b411a6f fix: sync is_favorite from native (#20412)
* feat: sync is_favorite from native

* handle favorite during upload

* Update mobile/ios/Runner/Sync/MessagesImpl.swift

Co-authored-by: Alex <alex.tran1502@gmail.com>

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-07-29 21:57:04 -05:00
Brandon Wees
07ed060c32 feat: 3-2-1 backup onboarding card (#20374)
* feat: 3-2-1 backup onboarding card

* chore: format i18n

* fix: lint

* Update onboarding-backup.svelte

* fix: e2e onboarding test
2025-07-29 21:55:21 -05:00
Jason Rasmussen
2f5d543ad9 fix: tweak error docs (#20417) 2025-07-29 21:33:24 +00:00
Jason Rasmussen
9b65cd4d7b feat!: remove typeorm (#20366)
feat: remove typeorm
2025-07-29 17:28:02 -04:00
Brandon Wees
290e325c5c feat: drift description editor (#20383)
* feat: drift description editor

* chore: use focus node

* chore: code review fixes

* chore: move description update to action.service

* refactor

* refactor

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-07-29 21:17:33 +00:00
Jason Rasmussen
58521c9efb feat: change default media location to /data (#20367)
* feat!: change default media location to /data

* feat: dynamically detect media location
2025-07-29 16:58:50 -04:00
Andrew Marshall
4cae15f28d feat: support config via systemd credentials (#20406)
feat: Support config via Systemd Credentials

See https://systemd.io/CREDENTIALS/. This is used as a fallback, so will
only be used if the `$*_FILE` var is empty. This could also be used to
implicitly use Docker Secrets by settings
`CREDENTIALS_DIRECTORY=/run/secrets` rather than setting individual
`$_*FILE` environment variables.
2025-07-29 16:43:11 -04:00
shenlong
e6ec019852 fix: show missing local assets only in timeline with partner sharing (#20298)
fix: show missing local assets in timeline with partner sharing

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-07-29 10:05:04 -05:00
Brandon Wees
3b5e00131b fix: android widget periodic updates (#20389)
* fix: android widget updates

* ensure periodic work is queued when we receive an update

This will not "reset the clock" on the periodic work since we are using         ExistingPeriodicWorkPolicy.UPDATE. This is needed since existing widgets have already been asked to queue their workers. If those periodic workers were overwritten by a widget update request from the app, there is no way to queue them again. onReceive gets run when the app requests a widget update so the periodic workers will get queued again.
2025-07-29 08:59:10 -05:00
Zack Pollard
a0fa7318ed fix: handle cleanup of new backups alongside old backups (#20402) 2025-07-29 13:28:10 +01:00
Zack Pollard
2a005629a0 chore: bump minimum eslint-config-prettier version due to MAL-2025-6022 (#20400) 2025-07-29 11:23:52 +00:00
renovate[bot]
59a50b8697 chore(deps): update github-actions (#20384)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-29 12:07:14 +01:00
renovate[bot]
90eac40e02 chore(deps): update terraform cloudflare to v4.52.1 (#20387)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-29 12:06:52 +01:00
Andreas Petersson
ad6f7f8089 docs: add immich_ml_balancer to community projects (#20399) 2025-07-29 12:02:37 +01:00
renovate[bot]
056b262cba chore(deps): update dependency @types/node to ^22.16.5 (#20385)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-29 11:48:26 +02:00
renovate[bot]
cfae134ecf fix(deps): update typescript-projects (#20388)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-07-29 09:47:30 +00:00
Jason Rasmussen
fbbb6af27a chore: update open-api (#20376) 2025-07-28 19:56:22 -05:00
Brandon Wees
1804a8fe58 fix: openapi spec (#20378) 2025-07-28 23:46:34 +00:00
Alwin Lohrie
ae1d60e259 feat: find large files utility (#18040)
feat: large asset utility

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2025-07-28 18:48:39 -04:00
Jason Rasmussen
7d759edfcc chore: add permission metadata to open-api document (#20373) 2025-07-28 18:40:34 -04:00
shenlong
34974b036c fix: handle back gesture in multi selection mode (#20356)
* fix: handle back gesture in multi selection mode

# Conflicts:
#	mobile/lib/presentation/widgets/timeline/timeline.widget.dart

* remove null-aware element because Isar

* chore: set sqlite busy_timeout to 500ms (#20358)

fix: add busy_timeout pragma

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

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-07-28 19:22:50 +00:00
shenlong
e52b9d15b5 chore: bump dart sdk to 3.8 (#20355)
* chore: bump dart sdk to 3.8

* chore: make build

* make pigeon

* chore: format files

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-07-28 14:04:03 -05:00
Jed-Giblin
9b3718120b feat: shared links custom URL (#19999)
* feat: custom url for shared links

* feat: use a separate route and query param

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2025-07-28 14:16:55 -04:00
Jason Rasmussen
16b14b390f fix: file samples (#20364) 2025-07-28 13:30:49 -04:00
Jason Rasmussen
7e7b8da128 fix: debug source maps (#20363) 2025-07-28 12:41:22 -04:00
Dag Stuan
66ea75072d feat(web): auto fit bounds for map modal (#20345) 2025-07-28 17:36:37 +01:00
Sergey Katsubo
d34670bae6 feat(server): include reason in null reverse geocoding logs (#20347)
* Clarify in log why reverse geocoding may return nothing

* Decrease log level for empty reverse geocoding response from warn to log

* Use a named constant for 25km

* Mention fallback to countries in the message

* Improve natural earth log message

* Decrease log level for empty reverse geocoding response from natural earth countries
2025-07-28 08:51:00 -05:00
shenlong
1e1c2ea627 fix: backup indicator in beta app bar (#20354)
fix: beta - app bar backup indicator

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-07-28 08:50:50 -05:00
Sergey Katsubo
c7fcb23a23 chore(docs): add links to custom scan interval from other sections in external library doc (#20353) 2025-07-28 13:42:59 +01:00
shenlong
708e42d8a3 fix: change translation key for archive action button (#20336)
* chore: change translation key for archive action button

* chore: await navigation before emitting event

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-07-27 17:43:23 +00:00
Alex
d15f67da5d feat: scroll to top & view in timeline (#20274)
* feat: scroll to top & view in timeline

* use EventStream

* refactor: event invocation and listerner

* fix: correct parent routing
2025-07-27 21:48:32 +05:30
Matthew Momjian
6becf409da feat(docs): reorganize DB queries, add tags (#20303)
* reorganize

* fix checksum
2025-07-27 10:03:58 -05:00
xCJPECKOVERx
ee4ae40d61 fix(web): Album picker recent albums don't match sidebar (#20313)
- update album picker recent albums to show most recent 'updatedAt' instead of 'createdAt'. Matches sidebar.
2025-07-27 10:03:39 -05:00
Matthew Momjian
ebd644eedd fix(docs): update SQL queries with schema changes (#20297)
* sql queries with table changes

* fix linting
2025-07-26 17:38:12 -05:00
shenlong
7c36cbaf0f fix: error on navigating back from backup selection page (#20299)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-07-26 13:58:27 -05:00
Alex
3a5d82f790 chore: delete action button (#20261) 2025-07-26 13:51:18 -05:00
Alex
b14c768208 fix: share to app upload (#20271) 2025-07-26 11:42:24 -05:00
Alex
07cb2fb04e fix: remainder backup count (#20270) 2025-07-26 09:02:26 -05:00
Alexandre Garnier
9bbad45990 fix(mobile): remove unused translation and pluralize existing one for map photos count (#20282) 2025-07-26 09:00:35 -05:00
shenlong
e85655d34c fix: no hero animation after tab change (#20285)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-07-26 08:59:26 -05:00
shenlong
d0576697c3 chore: upgrade flutter to 3.32.8 (#20287)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-07-26 08:58:48 -05:00
Yaros
f9847bee51 fix(mobile): places search not working in beta version (#20284)
fix(mobile): places search not working
2025-07-26 17:00:57 +05:30
beposec
f2141de5bb fix(mobile): Fix a typo in SyncStreamService Logging (#20283)
Fix typo in logging
2025-07-26 11:29:42 +00:00
Alexandre Garnier
cb344cb014 fix(mobile): distinguish backup settings title from 'backup_controller_page_backup' translation entry (#20237)
Different translation in some languages like in French ('Sauvegarde' for title, otherwise 'Sauvegardé')
2025-07-25 18:07:59 -05:00
Jason Rasmussen
c6b25ef111 feat: automatically detect media location changes (#20256) 2025-07-25 15:25:36 -04:00
Jason Rasmussen
0fdeac0417 feat!: more permissions (#20250)
feat: more api key permissions
2025-07-25 15:25:23 -04:00
Jason Rasmussen
153bb70f6e feat(web): api key permission search (#20248) 2025-07-25 13:39:48 -04:00
Brandon Wees
da80b69062 fix(mobile): beta search page improvements (#20247)
search page improvements
2025-07-25 12:24:19 -05:00
Brandon Wees
f9292c9c96 fix(mobile): deep links when using the beta timeline (#20111)
* fix: deep links when using the beta timeline

* Update remote_asset.repository.dart

* Update mobile/lib/domain/services/asset.service.dart

Co-authored-by: Alex <alex.tran1502@gmail.com>

* return optional from album get

* do not include trashed assets in album asset count

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

* formatting

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
Co-authored-by: shenlong <139912620+shenlong-tanwen@users.noreply.github.com>
2025-07-25 17:02:49 +00:00
Zack Pollard
2e0ee6ec05 feat: don't wait for network on app startup (#20232) 2025-07-25 11:19:50 -05:00
Zack Pollard
7f2e4f85f8 fix: lookup the primary key constraint name before dropping it (#20221) 2025-07-25 16:51:22 +01:00
Brandon Wees
c63f805cb4 fix: word wrapping on action buttons (#20231) 2025-07-25 15:10:01 +00:00
Alex
03a13828e1 chore: refactor upload service (#20130)
* chore: refactor upload service

* fix: cancel upload queue on logout (#20131)

* fix: cancel upload on logout

* fix: test

---------

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

---------

Co-authored-by: shenlong <139912620+shenlong-tanwen@users.noreply.github.com>
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-07-25 10:09:32 -05:00
Jeremy Fleischman
e5ee1c8db6 chore: add missing 'make pigeon' instruction (#20202)
* chore: fix typo 'make_build' -> 'make build'

* chore: add missing 'make pigeon' instruction

Turns out I was getting bit by forgetting to run `make pigeon`, which
also generates files. Perhaps it would be better to make it part of
`make build`?

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-07-25 15:05:40 +00:00
Daimolean
25e2d37490 fix(server): use UserMetadataKey enum instead of string (#20209)
* fix(server): use UserMetadataKey enum instead of string

* fix: mobile
2025-07-25 11:04:28 -04:00
Alex
ed5759fe07 fix: character width calculation (#20201) 2025-07-25 09:56:25 -05:00
Daniel Dietzler
edefed56ae fix: optional number inputs (#20218) 2025-07-25 09:06:12 -04:00
shenlong
13281f8531 docs: more make commands and add mobile checks to pr-checklist (#20211)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-07-25 13:24:08 +01:00
Zack Pollard
b48406bd20 fix: android backup album migration losing selection (#20220) 2025-07-25 12:57:31 +01:00
Brandon Wees
06c78dfa91 feat: add to album on new beta timeline (#20119)
* feat: add to album on new beta timeline

* handle add album button

* tune

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-07-25 01:10:33 -05:00
Jeremy Fleischman
de67d22bc0 fix: add missing awaits when changing client certificate (#20189)
I ran into this while testing out
<https://github.com/immich-app/immich/pull/19830>. When I add, change,
or remove a client certificate under Immich's advanced settings, the
change wouldn't take effect until some mysterious point in the future.
For example:

1. Add a client certificate. It doesn't get used.
2. Remove certificate. *Now* the client certificate from step 1) is used.
3. Restart application. Now no client certificate is used.

This all boils down to some missing `await`s. The user would change the
cert, and we'd start asynchronously saving it to the store, and while
the save is still happening, [`HttpSSLOptions` pulls the "old" value out of
`SSLClientCertStoreVal`](https://github.com/immich-app/immich/blob/v1.136.0/mobile/lib/utils/http_ssl_options.dart#L30).

With the appropriate `await`s, this behaves much more sanely.
2025-07-25 00:28:33 -05:00
shenlong
b4780e89af fix: add beta toggle in landscape mode (#20187)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-07-25 00:28:21 -05:00
shenlong
ad65e9011a chore: bump line length to 120 (#20191) 2025-07-25 02:37:22 +00:00
Zack Pollard
977c9b96ba fix: geodata_places pkey migration failing for certain upgrade paths (#20177) 2025-07-24 22:50:36 +01:00
John Stef
aa2828ab33 fix(mobile): hide video thumbnail when video is ready (#19328) 2025-07-24 22:38:08 +01:00
Daimolean
a36840d7cc fix(web): multi-select in ascending asset order (#19461) 2025-07-24 22:37:44 +01:00
Pablo Lluch
e34f46fa0d fix: send correct includeArchived parameter to API when showing markers in map (#20117)
Co-authored-by: Pablo Lluch <pablo.lluch@gmail.com>
Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2025-07-24 22:34:06 +02:00
Alex
6170a3843c chore: remove build flavor Android (#20161) 2025-07-24 14:55:49 -05:00
shenlong
563e2ab503 fix: update trash action i18n keys (#20164)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-07-24 14:55:35 -05:00
shenlong
79157e1043 fix: fallback auto grouping to day grouping (#20156)
fallback auto grouping to day grouping

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-07-24 19:28:40 +00:00
shenlong
02688a2a03 fix: override SSL options inside isolates (#20142)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-07-24 14:26:00 -05:00
Brandon Wees
3b9bfceef0 fix: album deleted toast message (#20121)
* fix: album deleted toast message

* Update mobile/lib/presentation/pages/drift_remote_album.page.dart

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

* Fix lint

---------

Co-authored-by: shenlong <139912620+shenlong-tanwen@users.noreply.github.com>
2025-07-24 14:09:17 -05:00
github-actions
089085fcdb chore: version v1.136.0 2025-07-24 14:24:38 +00:00
Jason Rasmussen
fc68cf4f32 chore: remove migration (#20129) 2025-07-24 14:11:53 +00:00
Alex
0051a9bba5 fix(web): Revert prevent flashing white background in dark mode on page load/reload (#19934) (#20122)
Revert "fix(web): prevent flashing white background in dark mode on page load/reload (#19934)"

This reverts commit 32f23b8d38.
2025-07-24 09:45:38 +02:00
Daniel Dietzler
f27bdf7523 chore: migrate to UI modal manager (#20116) 2025-07-23 17:27:09 -04:00
Daniel Dietzler
c1c9f30ea4 chore: migrate to immich/ui confirm modal (#20114) 2025-07-23 22:56:56 +02:00
Jason Rasmussen
bc8cb9b671 fix: default route permission (#20113) 2025-07-23 16:56:38 -04:00
Jason Rasmussen
a675922172 fix: unset prewarn param (#20109) 2025-07-23 16:52:59 -04:00
Jason Rasmussen
2bead445bd docs: remove outdated note (#20110) 2025-07-23 16:00:19 -04:00
Daniel Dietzler
0e1c8c2b80 fix: ML recognition distance UI form validation (#20107) 2025-07-23 19:12:06 +02:00
Alex
0174de19dd feat: export database option (#20098) 2025-07-23 14:40:16 +00:00
Alex
1a35a01149 feat: drift manual upload (#20101) 2025-07-23 09:20:52 -05:00
shenlong
08122d6871 fix: show only local assets from albums selected for backup (#20050)
* show only local assets from albums selected for backup

# Conflicts:
#	mobile/lib/infrastructure/repositories/db.repository.drift.dart

* ignore backup selection

* fix: backup album ownerId

* fix: backup album ownerId

* only show local only assets that are selected for backup

* add index on visibility and backup selection

* fix: video not playing in search view

* remove safe area from bottom bar

* refactor stack count with a CTE and local asset with a SELECT

* fix lint

* remove redundant COALESCE

* remove stack count from main timeline query

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-07-23 09:13:15 -05:00
Jason Rasmussen
92384c28de feat: sync auth user (#20067) 2025-07-23 09:59:33 -04:00
Jason Rasmussen
ab597155fa fix: immich-dev live reload (#20104) 2025-07-23 09:59:21 -04:00
Weblate (bot)
1d9cc4ca5f chore(web): update translations (#20082)
Co-authored-by: Alberto Serluca <alberto.ser11@gmail.com>
Co-authored-by: DevServs <bonov@mail.ru>
Co-authored-by: Florian Ostertag <florian.kuepper@gmail.com>
Co-authored-by: Gilbert <thengilbert@gmail.com>
Co-authored-by: Indrek Haav <indrek.haav@hotmail.com>
Co-authored-by: Jozef Gaal <preklady@mayday.sk>
Co-authored-by: KecskeTech <teonyitas@gmail.com>
Co-authored-by: Kuno Claes <kuno@icodes.dev>
Co-authored-by: MSDNicrosoft <wang3311835119@hotmail.com>
Co-authored-by: Matjaž T <matjaz@moj-svet.si>
Co-authored-by: Mārtiņš Bruņenieks <martinsb@gmail.com>
Co-authored-by: Vegard Fladby <vegard@fladby.org>
Co-authored-by: adri1m64 <adrien.melle@laposte.net>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
2025-07-23 14:38:29 +01:00
1250 changed files with 49555 additions and 50151 deletions

View File

@@ -11,8 +11,8 @@ services:
- open_api_node_modules:/workspaces/immich/open-api/typescript-sdk/node_modules
- server_node_modules:/workspaces/immich/server/node_modules
- web_node_modules:/workspaces/immich/web/node_modules
- ${UPLOAD_LOCATION}/photos:/usr/src/app/upload
- ${UPLOAD_LOCATION}/photos/upload:/usr/src/app/upload
- ${UPLOAD_LOCATION}/photos:/data
- ${UPLOAD_LOCATION}/photos/upload:/data/upload
- /etc/localtime:/etc/localtime:ro
database:

View File

@@ -13,8 +13,8 @@ services:
- open_api_node_modules:/workspaces/immich/open-api/typescript-sdk/node_modules
- server_node_modules:/workspaces/immich/server/node_modules
- web_node_modules:/workspaces/immich/web/node_modules
- ${UPLOAD_LOCATION:-upload1-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/usr/src/app/upload
- ${UPLOAD_LOCATION:-upload2-devcontainer-volume}${UPLOAD_LOCATION:+/photos/upload}:/usr/src/app/upload/upload
- ${UPLOAD_LOCATION:-upload1-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data
- ${UPLOAD_LOCATION:-upload2-devcontainer-volume}${UPLOAD_LOCATION:+/photos/upload}:/data/upload
- /etc/localtime:/etc/localtime:ro
immich-web:

View File

@@ -122,17 +122,17 @@ jobs:
IS_MAIN: ${{ github.ref == 'refs/heads/main' }}
run: |
if [[ $IS_MAIN == 'true' ]]; then
flutter build apk --release --flavor production
flutter build apk --release --flavor production --split-per-abi --target-platform android-arm,android-arm64,android-x64
flutter build apk --release
flutter build apk --release --split-per-abi --target-platform android-arm,android-arm64,android-x64
else
flutter build apk --debug --flavor production --split-per-abi --target-platform android-arm64
flutter build apk --debug --split-per-abi --target-platform android-arm64
fi
- name: Publish Android Artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: release-apk-signed
path: mobile/build/app/outputs/flutter-apk/**/*.apk
path: mobile/build/app/outputs/flutter-apk/*.apk
- name: Save Gradle Cache
id: cache-gradle-save

View File

@@ -50,7 +50,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
uses: github/codeql-action/init@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -63,7 +63,7 @@ jobs:
# 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)
- name: Autobuild
uses: github/codeql-action/autobuild@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
uses: github/codeql-action/autobuild@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
# 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
@@ -76,6 +76,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
uses: github/codeql-action/analyze@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
with:
category: '/language:${{matrix.language}}'

View File

@@ -90,7 +90,7 @@ jobs:
env:
CHANGED_FILES: ${{ steps.verify-changed-files.outputs.changed_files }}
run: |
echo "ERROR: Generated files not up to date! Run make_build inside the mobile directory"
echo "ERROR: Generated files not up to date! Run 'make build' and 'make pigeon' inside the mobile directory"
echo "Changed files: ${CHANGED_FILES}"
exit 1
@@ -98,7 +98,7 @@ jobs:
run: dart analyze --fatal-infos
- name: Run dart format
run: dart format lib/ --set-exit-if-changed
run: make format
- name: Run dart custom_lint
run: dart run custom_lint
@@ -129,7 +129,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
uses: github/codeql-action/upload-sarif@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
with:
sarif_file: results.sarif
category: zizmor

View File

@@ -668,7 +668,7 @@ jobs:
contents: read
services:
postgres:
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3@sha256:1f5583fe3397210a0fbc7f11b0cec18bacc4a99e3e8ea0548e9bd6bcf26ec37a
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3@sha256:ec713143dca1a426eba2e03707c319e2ec3cc9d304ef767f777f8e297dee820c
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres

View File

@@ -38,7 +38,7 @@ jobs:
exit 1
fi
- name: Find Pull Request
uses: juliangruber/find-pull-request-action@48b6133aa6c826f267ebd33aa2d29470f9d9e7d0 # v1.9.0
uses: juliangruber/find-pull-request-action@952b3bb1ddb2dcc0aa3479e98bb1c2d1a922f096 # v1.10.0
id: find-pr
with:
branch: chore/translations

23
.vscode/launch.json vendored
View File

@@ -7,7 +7,7 @@
"restart": true,
"port": 9231,
"name": "Immich API Server",
"remoteRoot": "/usr/src/app",
"remoteRoot": "/usr/src/app/server",
"localRoot": "${workspaceFolder}/server"
},
{
@@ -16,27 +16,8 @@
"restart": true,
"port": 9230,
"name": "Immich Workers",
"remoteRoot": "/usr/src/app",
"remoteRoot": "/usr/src/app/server",
"localRoot": "${workspaceFolder}/server"
},
{
"name": "Flavor - Production",
"request": "launch",
"type": "dart",
"codeLens": {
"for": [
"run-test",
"run-test-file",
"run-file",
"debug-test",
"debug-test-file",
"debug-file",
],
"title": "${debugType}",
},
"args": [
"--flavor", "production"
],
}
]
}

154
cli/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@immich/cli",
"version": "2.2.72",
"version": "2.2.73",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@immich/cli",
"version": "2.2.72",
"version": "2.2.73",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"chokidar": "^4.0.3",
@@ -27,13 +27,13 @@
"@types/lodash-es": "^4.17.12",
"@types/micromatch": "^4.0.9",
"@types/mock-fs": "^4.13.1",
"@types/node": "^22.16.4",
"@types/node": "^22.16.5",
"@vitest/coverage-v8": "^3.0.0",
"byte-size": "^9.0.0",
"cli-progress": "^3.12.0",
"commander": "^12.0.0",
"eslint": "^9.14.0",
"eslint-config-prettier": "^10.0.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^59.0.0",
"globals": "^16.0.0",
@@ -54,14 +54,14 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.135.3",
"version": "1.136.0",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@oazapfts/runtime": "^1.0.2"
},
"devDependencies": {
"@types/node": "^22.16.4",
"@types/node": "^22.16.5",
"typescript": "^5.3.3"
}
},
@@ -1365,17 +1365,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz",
"integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz",
"integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/type-utils": "8.37.0",
"@typescript-eslint/utils": "8.37.0",
"@typescript-eslint/visitor-keys": "8.37.0",
"@typescript-eslint/scope-manager": "8.38.0",
"@typescript-eslint/type-utils": "8.38.0",
"@typescript-eslint/utils": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -1389,7 +1389,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.37.0",
"@typescript-eslint/parser": "^8.38.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
@@ -1405,16 +1405,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz",
"integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz",
"integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/visitor-keys": "8.37.0",
"@typescript-eslint/scope-manager": "8.38.0",
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0",
"debug": "^4.3.4"
},
"engines": {
@@ -1430,14 +1430,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz",
"integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz",
"integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.37.0",
"@typescript-eslint/types": "^8.37.0",
"@typescript-eslint/tsconfig-utils": "^8.38.0",
"@typescript-eslint/types": "^8.38.0",
"debug": "^4.3.4"
},
"engines": {
@@ -1452,14 +1452,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz",
"integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz",
"integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/visitor-keys": "8.37.0"
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1470,9 +1470,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz",
"integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz",
"integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1487,15 +1487,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz",
"integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz",
"integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/utils": "8.37.0",
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0",
"@typescript-eslint/utils": "8.38.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -1512,9 +1512,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz",
"integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz",
"integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1526,16 +1526,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz",
"integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz",
"integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.37.0",
"@typescript-eslint/tsconfig-utils": "8.37.0",
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/visitor-keys": "8.37.0",
"@typescript-eslint/project-service": "8.38.0",
"@typescript-eslint/tsconfig-utils": "8.38.0",
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -1581,16 +1581,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz",
"integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz",
"integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.37.0"
"@typescript-eslint/scope-manager": "8.38.0",
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1605,13 +1605,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz",
"integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz",
"integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/types": "8.38.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -2367,9 +2367,9 @@
}
},
"node_modules/eslint-config-prettier": {
"version": "10.1.5",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz",
"integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==",
"version": "10.1.8",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
"bin": {
@@ -2383,9 +2383,9 @@
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz",
"integrity": "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==",
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.3.tgz",
"integrity": "sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3548,15 +3548,15 @@
}
},
"node_modules/prettier-plugin-organize-imports": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz",
"integrity": "sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.2.0.tgz",
"integrity": "sha512-Zdy27UhlmyvATZi67BTnLcKTo8fm6Oik59Sz6H64PgZJVs6NJpPD1mT240mmJn62c98/QaL+r3kx9Q3gRpDajg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"prettier": ">=2.0",
"typescript": ">=2.9",
"vue-tsc": "^2.1.0"
"vue-tsc": "^2.1.0 || 3"
},
"peerDependenciesMeta": {
"vue-tsc": {
@@ -4139,16 +4139,16 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.37.0.tgz",
"integrity": "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.38.0.tgz",
"integrity": "sha512-FsZlrYK6bPDGoLeZRuvx2v6qrM03I0U0SnfCLPs/XCCPCFD80xU9Pg09H/K+XFa68uJuZo7l/Xhs+eDRg2l3hg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.37.0",
"@typescript-eslint/parser": "8.37.0",
"@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/utils": "8.37.0"
"@typescript-eslint/eslint-plugin": "8.38.0",
"@typescript-eslint/parser": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0",
"@typescript-eslint/utils": "8.38.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"

View File

@@ -1,6 +1,6 @@
{
"name": "@immich/cli",
"version": "2.2.72",
"version": "2.2.73",
"description": "Command Line Interface (CLI) for Immich",
"type": "module",
"exports": "./dist/index.js",
@@ -21,13 +21,13 @@
"@types/lodash-es": "^4.17.12",
"@types/micromatch": "^4.0.9",
"@types/mock-fs": "^4.13.1",
"@types/node": "^22.16.4",
"@types/node": "^22.16.5",
"@vitest/coverage-v8": "^3.0.0",
"byte-size": "^9.0.0",
"cli-progress": "^3.12.0",
"commander": "^12.0.0",
"eslint": "^9.14.0",
"eslint-config-prettier": "^10.0.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^59.0.0",
"globals": "^16.0.0",

View File

@@ -2,37 +2,37 @@
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/cloudflare/cloudflare" {
version = "4.52.0"
constraints = "4.52.0"
version = "4.52.1"
constraints = "4.52.1"
hashes = [
"h1:2BEJyXJtYC4B4nda/WCYUmuJYDaYk88F8t1pwPzr0iQ=",
"h1:4IASk5SESeWKQ7JU0+M7KApuF5mZyklvwMXPBabim3c=",
"h1:5ImZxxALSnWfH/4EXw/wFirSmk5Tr0ACmcysy51AafE=",
"h1:6TJ3dxLSin4ZKBJLsZDn95H2ZYnGm8S7GGHvvXuuMQU=",
"h1:IzTUjg9kQ4N3qizP9CjYLeHwjsuGgtxwXvfUQWyOLcA=",
"h1:NTaOQfYINA0YTG/V1/9+SYtgX1it63+cBugj4WK4FWc=",
"h1:PXH48LuJn329sCfMXprdMDk51EZaWFyajVvS03qhQLs=",
"h1:Pi5M+GeoMSN2eJ6QnIeXjBf19O+rby/74CfB2ocpv20=",
"h1:ShXZ2ZjBvm3thfoPPzPT8+OhyismnydQVkUAfI8X12w=",
"h1:WQ9hu0Wge2msBbODfottCSKgu8oKUrw4Opz+fDPVVHk=",
"h1:Z5yXML2DE0uH9UU+M0ut9JMQAORcwVZz1CxBHzeBmao=",
"h1:jqI2qKknpleS3JDSplyGYHMu0u9K/tor1ZOjFwDgEMk=",
"h1:kgfutDh14Q5nw4eg6qGFamFxIiY8Ae0FPKRBLDOzpcI=",
"h1:zCAO7GZmfYhWb+i6TfqlqhMeDyPZWGio2IzEzAh3YTs=",
"zh:19be1a91c982b902c42aba47766860dfa5dc151eed1e95fd39ca642229381ef0",
"zh:1de451c4d1ecf7efbe67b6dace3426ba810711afdd644b0f1b870364c8ae91f8",
"zh:352b4a2120173298622e669258744554339d959ac3a95607b117a48ee4a83238",
"zh:3c6f1346d9154afbd2d558fabb4b0150fc8d559aa961254144fe1bc17fe6032f",
"zh:4c4c92d53fb535b1e0eff26f222bbd627b97d3b4c891ec9c321268676d06152f",
"zh:53276f68006c9ceb7cdb10a6ccf91a5c1eadd1407a28edb5741e84e88d7e29e8",
"zh:7925a97773948171a63d4f65bb81ee92fd6d07a447e36012977313293a5435c9",
"zh:7dfb0a4496cfe032437386d0a2cd9229a1956e9c30bd920923c141b0f0440060",
"h1:2lHvafwGbLdmc9lYkuJFw3nsInaQjRpjX/JfIRKmq/M=",
"h1:596JomwjrtUrOSreq9NNCS+rj70+jOV+0pfja5MXiTI=",
"h1:7mBOA5TVAIt3qAwPXKCtE0RSYeqij9v30mnksuBbpEg=",
"h1:ELVgzh4kHKBCYdL+2A8JjWS0E1snLUN3Mmz3Vo6qSfw=",
"h1:FGGM5yLFf72g3kSXM3LAN64Gf/AkXr5WCmhixgnP+l4=",
"h1:JupkJbQALcIVoMhHImrLeLDsQR1ET7VJLGC7ONxjqGU=",
"h1:KsaE4JNq+1uV1nJsuTcYar/8lyY6zKS5UBEpfYg3wvc=",
"h1:NHZ5RJIzQDLhie/ykl3uI6UPfNQR9Lu5Ti7JPR6X904=",
"h1:NfAuMbn6LQPLDtJhbzO1MX9JMIGLMa8K6CpekvtsuX8=",
"h1:e+vNKokamDsp/kJvFr2pRudzwEz2r49iZ/oSggw+1LY=",
"h1:jnb4VdfNZ79I3yj7Q8x+JmOT+FxbfjjRfrF0dL0yCW8=",
"h1:kmF//O539d7NuHU7qIxDj7Wz4eJmLKFiI5glwQivldU=",
"h1:s6XriaKwOgV4jvKAGPXkrxhhOQxpNU5dceZwi9Z/1k8=",
"h1:wt3WBEBAeSGTlC9OlnTlAALxRiK4SQgLy0KgBIS7qzs=",
"zh:2fb95e1d3229b9b6c704e1a413c7481c60f139780d9641f657b6eb9b633b90f2",
"zh:379c7680983383862236e9e6e720c3114195c40526172188e88d0ffcf50dfe2e",
"zh:55533beb6cfc02d22ffda8cba8027bc2c841bb172cd637ed0d28323d41395f8f",
"zh:5abd70760e4eb1f37a1c307cbd2989ea7c9ba0afb93818c67c1d363a31f75703",
"zh:699f1c8cd66129176fe659ebf0e6337632a8967a28d2630b6ae5948665c0c2ae",
"zh:69c15acd73c451e89de6477059cda2f3ec200b48ae4b9ff3646c4d389fd3205e",
"zh:6e02b687de21b844f8266dff99e93e7c61fc8eb688f4bbb23803caceb251839e",
"zh:7a51d17b87ed87b7bebf2ad9fc7c3a74f16a1b44eee92c779c08eb89258c0496",
"zh:88ad84436837b0f55302f22748505972634e87400d6902260fd6b7ba1610f937",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:8d4aa79f0a414bb4163d771063c70cd991c8fac6c766e685bac2ee12903c5bd6",
"zh:a67540c13565616a7e7e51ee9366e88b0dc60046e1d75c72680e150bd02725bb",
"zh:a936383a4767f5393f38f622e92bf2d0c03fe04b69c284951f27345766c7b31b",
"zh:d4887d73c466ff036eecf50ad6404ba38fd82ea4855296b1846d244b0f13c380",
"zh:e9093c8bd5b6cd99c81666e315197791781b8f93afa14fc2e0f732d1bb2a44b7",
"zh:efd3b3f1ec59a37f635aa1d4efcf178734c2fcf8ddb0d56ea690bec342da8672",
"zh:8d46c3d9f4f7ad20ac6ef01daa63f4e30a2d16dcb1bb5c7c7ee3dc6be38e9ca1",
"zh:913d64e72a4929dae1d4793e2004f4f9a58b138ea337d9d94fa35cafbf06550a",
"zh:c8d93cf86e2e49f6cec665cfe78b82c144cce15a8b2e30f343385fadd1251849",
"zh:cc4f69397d9bc34a528a5609a024c3a48f54f21616c0008792dd417297add955",
"zh:df99cdb8b064aad35ffea77e645cf6541d0b1b2ebc51b6d26c42031de60ab69e",
]
}

View File

@@ -5,7 +5,7 @@ terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "4.52.0"
version = "4.52.1"
}
}
}

View File

@@ -2,37 +2,37 @@
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/cloudflare/cloudflare" {
version = "4.52.0"
constraints = "4.52.0"
version = "4.52.1"
constraints = "4.52.1"
hashes = [
"h1:2BEJyXJtYC4B4nda/WCYUmuJYDaYk88F8t1pwPzr0iQ=",
"h1:4IASk5SESeWKQ7JU0+M7KApuF5mZyklvwMXPBabim3c=",
"h1:5ImZxxALSnWfH/4EXw/wFirSmk5Tr0ACmcysy51AafE=",
"h1:6TJ3dxLSin4ZKBJLsZDn95H2ZYnGm8S7GGHvvXuuMQU=",
"h1:IzTUjg9kQ4N3qizP9CjYLeHwjsuGgtxwXvfUQWyOLcA=",
"h1:NTaOQfYINA0YTG/V1/9+SYtgX1it63+cBugj4WK4FWc=",
"h1:PXH48LuJn329sCfMXprdMDk51EZaWFyajVvS03qhQLs=",
"h1:Pi5M+GeoMSN2eJ6QnIeXjBf19O+rby/74CfB2ocpv20=",
"h1:ShXZ2ZjBvm3thfoPPzPT8+OhyismnydQVkUAfI8X12w=",
"h1:WQ9hu0Wge2msBbODfottCSKgu8oKUrw4Opz+fDPVVHk=",
"h1:Z5yXML2DE0uH9UU+M0ut9JMQAORcwVZz1CxBHzeBmao=",
"h1:jqI2qKknpleS3JDSplyGYHMu0u9K/tor1ZOjFwDgEMk=",
"h1:kgfutDh14Q5nw4eg6qGFamFxIiY8Ae0FPKRBLDOzpcI=",
"h1:zCAO7GZmfYhWb+i6TfqlqhMeDyPZWGio2IzEzAh3YTs=",
"zh:19be1a91c982b902c42aba47766860dfa5dc151eed1e95fd39ca642229381ef0",
"zh:1de451c4d1ecf7efbe67b6dace3426ba810711afdd644b0f1b870364c8ae91f8",
"zh:352b4a2120173298622e669258744554339d959ac3a95607b117a48ee4a83238",
"zh:3c6f1346d9154afbd2d558fabb4b0150fc8d559aa961254144fe1bc17fe6032f",
"zh:4c4c92d53fb535b1e0eff26f222bbd627b97d3b4c891ec9c321268676d06152f",
"zh:53276f68006c9ceb7cdb10a6ccf91a5c1eadd1407a28edb5741e84e88d7e29e8",
"zh:7925a97773948171a63d4f65bb81ee92fd6d07a447e36012977313293a5435c9",
"zh:7dfb0a4496cfe032437386d0a2cd9229a1956e9c30bd920923c141b0f0440060",
"h1:2lHvafwGbLdmc9lYkuJFw3nsInaQjRpjX/JfIRKmq/M=",
"h1:596JomwjrtUrOSreq9NNCS+rj70+jOV+0pfja5MXiTI=",
"h1:7mBOA5TVAIt3qAwPXKCtE0RSYeqij9v30mnksuBbpEg=",
"h1:ELVgzh4kHKBCYdL+2A8JjWS0E1snLUN3Mmz3Vo6qSfw=",
"h1:FGGM5yLFf72g3kSXM3LAN64Gf/AkXr5WCmhixgnP+l4=",
"h1:JupkJbQALcIVoMhHImrLeLDsQR1ET7VJLGC7ONxjqGU=",
"h1:KsaE4JNq+1uV1nJsuTcYar/8lyY6zKS5UBEpfYg3wvc=",
"h1:NHZ5RJIzQDLhie/ykl3uI6UPfNQR9Lu5Ti7JPR6X904=",
"h1:NfAuMbn6LQPLDtJhbzO1MX9JMIGLMa8K6CpekvtsuX8=",
"h1:e+vNKokamDsp/kJvFr2pRudzwEz2r49iZ/oSggw+1LY=",
"h1:jnb4VdfNZ79I3yj7Q8x+JmOT+FxbfjjRfrF0dL0yCW8=",
"h1:kmF//O539d7NuHU7qIxDj7Wz4eJmLKFiI5glwQivldU=",
"h1:s6XriaKwOgV4jvKAGPXkrxhhOQxpNU5dceZwi9Z/1k8=",
"h1:wt3WBEBAeSGTlC9OlnTlAALxRiK4SQgLy0KgBIS7qzs=",
"zh:2fb95e1d3229b9b6c704e1a413c7481c60f139780d9641f657b6eb9b633b90f2",
"zh:379c7680983383862236e9e6e720c3114195c40526172188e88d0ffcf50dfe2e",
"zh:55533beb6cfc02d22ffda8cba8027bc2c841bb172cd637ed0d28323d41395f8f",
"zh:5abd70760e4eb1f37a1c307cbd2989ea7c9ba0afb93818c67c1d363a31f75703",
"zh:699f1c8cd66129176fe659ebf0e6337632a8967a28d2630b6ae5948665c0c2ae",
"zh:69c15acd73c451e89de6477059cda2f3ec200b48ae4b9ff3646c4d389fd3205e",
"zh:6e02b687de21b844f8266dff99e93e7c61fc8eb688f4bbb23803caceb251839e",
"zh:7a51d17b87ed87b7bebf2ad9fc7c3a74f16a1b44eee92c779c08eb89258c0496",
"zh:88ad84436837b0f55302f22748505972634e87400d6902260fd6b7ba1610f937",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:8d4aa79f0a414bb4163d771063c70cd991c8fac6c766e685bac2ee12903c5bd6",
"zh:a67540c13565616a7e7e51ee9366e88b0dc60046e1d75c72680e150bd02725bb",
"zh:a936383a4767f5393f38f622e92bf2d0c03fe04b69c284951f27345766c7b31b",
"zh:d4887d73c466ff036eecf50ad6404ba38fd82ea4855296b1846d244b0f13c380",
"zh:e9093c8bd5b6cd99c81666e315197791781b8f93afa14fc2e0f732d1bb2a44b7",
"zh:efd3b3f1ec59a37f635aa1d4efcf178734c2fcf8ddb0d56ea690bec342da8672",
"zh:8d46c3d9f4f7ad20ac6ef01daa63f4e30a2d16dcb1bb5c7c7ee3dc6be38e9ca1",
"zh:913d64e72a4929dae1d4793e2004f4f9a58b138ea337d9d94fa35cafbf06550a",
"zh:c8d93cf86e2e49f6cec665cfe78b82c144cce15a8b2e30f343385fadd1251849",
"zh:cc4f69397d9bc34a528a5609a024c3a48f54f21616c0008792dd417297add955",
"zh:df99cdb8b064aad35ffea77e645cf6541d0b1b2ebc51b6d26c42031de60ab69e",
]
}

View File

@@ -5,7 +5,7 @@ terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "4.52.0"
version = "4.52.1"
}
}
}

View File

@@ -29,8 +29,8 @@ services:
volumes:
- ../server:/usr/src/app/server
- ../open-api:/usr/src/app/open-api
- ${UPLOAD_LOCATION}/photos:/usr/src/app/upload
- ${UPLOAD_LOCATION}/photos/upload:/usr/src/app/upload/upload
- ${UPLOAD_LOCATION}/photos:/data
- ${UPLOAD_LOCATION}/photos/upload:/data/upload
- /usr/src/app/server/node_modules
- /etc/localtime:/etc/localtime:ro
env_file:
@@ -123,7 +123,7 @@ services:
database:
container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:5f6a838e4e44c8e0e019d0ebfe3ee8952b69afc2809b2c25f7b0119641978e91
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:32324a2f41df5de9efe1af166b7008c3f55646f8d0e00d9550c16c9822366b4a
env_file:
- .env
environment:

View File

@@ -20,7 +20,7 @@ services:
context: ../
dockerfile: server/Dockerfile
volumes:
- ${UPLOAD_LOCATION}/photos:/usr/src/app/upload
- ${UPLOAD_LOCATION}/photos:/data
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
@@ -63,7 +63,7 @@ services:
database:
container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:5f6a838e4e44c8e0e019d0ebfe3ee8952b69afc2809b2c25f7b0119641978e91
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:32324a2f41df5de9efe1af166b7008c3f55646f8d0e00d9550c16c9822366b4a
env_file:
- .env
environment:

View File

@@ -18,7 +18,7 @@ services:
# service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
volumes:
# Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- ${UPLOAD_LOCATION}:/data
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
@@ -56,7 +56,7 @@ services:
database:
container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:5f6a838e4e44c8e0e019d0ebfe3ee8952b69afc2809b2c25f7b0119641978e91
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:32324a2f41df5de9efe1af166b7008c3f55646f8d0e00d9550c16c9822366b4a
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}

View File

@@ -180,7 +180,7 @@ services:
...
volumes:
# Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- ${UPLOAD_LOCATION}:/data
- /etc/localtime:/etc/localtime:ro
+ - originals:/usr/src/app/originals
...

View File

@@ -94,19 +94,16 @@ Change media location
```
immich-admin change-media-location
? Enter the previous value of IMMICH_MEDIA_LOCATION: /usr/src/app/upload
? Enter the new value of IMMICH_MEDIA_LOCATION: /data
? Enter the previous value of IMMICH_MEDIA_LOCATION: /data
? Enter the new value of IMMICH_MEDIA_LOCATION: /my-data
...
Previous value: /data
Current value: /my-data
Previous value: /usr/src/app/upload
Current value: /data
Changing database paths from "/usr/src/app/upload/*" to "/data/*"
Changing database paths from "/data/*" to "/my-data/*"
? Do you want to proceed? [Y/n] y
Database file paths updated successfully! 🎉
You may now set IMMICH_MEDIA_LOCATION=/data and restart!
(please remember to update applicable volume mounts e.g. ${UPLOAD_LOCATION}:/data)
...
```

View File

@@ -38,6 +38,19 @@ Run all server checks with `npm run check:all`
You can use `npm run __:fix` to potentially correct some issues automatically for `npm run format` and `lint`.
:::
## Mobile Checks
The following commands must be executed from within the mobile app directory of the codebase.
- [ ] `make build` (auto-generate files using build_runner)
- [ ] `make analyze` (static analysis via Dart Analyzer and DCM)
- [ ] `make format` (formatting via Dart Formatter)
- [ ] `make test` (unit tests)
:::info Auto Fix
You can use `dart fix --apply` and `dcm fix lib` to potentially correct some issues automatically for `make analyze`.
:::
## OpenAPI
The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/docs/developer/open-api.md) for more details.

View File

@@ -58,7 +58,7 @@ Internally, Immich uses the [glob](https://www.npmjs.com/package/glob) package t
This feature is considered experimental and for advanced users only. If enabled, it will allow automatic watching of the filesystem which means new assets are automatically imported to Immich without needing to rescan.
If your photos are on a network drive, automatic file watching likely won't work. In that case, you will have to rely on a periodic library refresh to pull in your changes.
If your photos are on a network drive, automatic file watching likely won't work. In that case, you will have to rely on a [periodic library refresh](#set-custom-scan-interval) to pull in your changes.
#### Troubleshooting
@@ -72,7 +72,9 @@ In rare cases, the library watcher can hang, preventing Immich from starting up.
### Nightly job
There is an automatic scan job that is scheduled to run once a day. This job also cleans up any libraries stuck in deletion. It is possible to trigger the cleanup by clicking "Scan all libraries" in the library management page.
There is an automatic scan job that is scheduled to run once a day. Its schedule is configurable, see [Set Custom Scan Interval](#set-custom-scan-interval).
This job also cleans up any libraries stuck in deletion. It is possible to trigger the cleanup by clicking "Scan all libraries" in the library management page.
## Usage
@@ -91,7 +93,7 @@ The `immich-server` container will need access to the gallery. Modify your docke
```diff title="docker-compose.yml"
immich-server:
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- ${UPLOAD_LOCATION}:/data
+ - /mnt/nas/christmas-trip:/mnt/media/christmas-trip:ro
+ - /home/user/old-pics:/mnt/media/old-pics:ro
+ - /mnt/media/videos:/mnt/media/videos:ro

View File

@@ -27,11 +27,11 @@ After defining the locations of these files, we will edit the `docker-compose.ym
services:
immich-server:
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
+ - ${THUMB_LOCATION}:/usr/src/app/upload/thumbs
+ - ${ENCODED_VIDEO_LOCATION}:/usr/src/app/upload/encoded-video
+ - ${PROFILE_LOCATION}:/usr/src/app/upload/profile
+ - ${BACKUP_LOCATION}:/usr/src/app/upload/backups
- ${UPLOAD_LOCATION}:/data
+ - ${THUMB_LOCATION}:/data/thumbs
+ - ${ENCODED_VIDEO_LOCATION}:/data/encoded-video
+ - ${PROFILE_LOCATION}:/data/profile
+ - ${BACKUP_LOCATION}:/data/backups
- /etc/localtime:/etc/localtime:ro
```
@@ -44,7 +44,7 @@ docker compose up -d
:::note
Because of the underlying properties of docker bind mounts, it is not recommended to mount the `upload/` and `library/` folders as separate bind mounts if they are on the same device.
For this reason, we mount the HDD or the network storage (NAS) to `/usr/src/app/upload` and then mount the folders we want to access under that folder.
For this reason, we mount the HDD or the network storage (NAS) to `/data` and then mount the folders we want to access under that folder.
The `thumbs/` folder contains both the small thumbnails displayed in the timeline and the larger previews shown when clicking into an image. These cannot be separated.

View File

@@ -12,118 +12,148 @@ Run `docker exec -it immich_postgres psql --dbname=<DB_DATABASE_NAME> --username
## Assets
### Name
:::note
The `"originalFileName"` column is the name of the file at time of upload, including the extension.
:::
```sql title="Find by original filename"
SELECT * FROM "assets" WHERE "originalFileName" = 'PXL_20230903_232542848.jpg';
SELECT * FROM "assets" WHERE "originalFileName" LIKE 'PXL_%'; -- all files starting with PXL_
SELECT * FROM "assets" WHERE "originalFileName" LIKE '%_2023_%'; -- all files with _2023_ in the middle
SELECT * FROM "asset" WHERE "originalFileName" = 'PXL_20230903_232542848.jpg';
SELECT * FROM "asset" WHERE "originalFileName" LIKE 'PXL_%'; -- all files starting with PXL_
SELECT * FROM "asset" WHERE "originalFileName" LIKE '%_2023_%'; -- all files with _2023_ in the middle
```
```sql title="Find by path"
SELECT * FROM "assets" WHERE "originalPath" = 'upload/library/admin/2023/2023-09-03/PXL_2023.jpg';
SELECT * FROM "assets" WHERE "originalPath" LIKE 'upload/library/admin/2023/%';
SELECT * FROM "asset" WHERE "originalPath" = 'upload/library/admin/2023/2023-09-03/PXL_2023.jpg';
SELECT * FROM "asset" WHERE "originalPath" LIKE 'upload/library/admin/2023/%';
```
### ID
```sql title="Find by ID"
SELECT * FROM "assets" WHERE "id" = '9f94e60f-65b6-47b7-ae44-a4df7b57f0e9';
SELECT * FROM "asset" WHERE "id" = '9f94e60f-65b6-47b7-ae44-a4df7b57f0e9';
```
```sql title="Find by partial ID"
SELECT * FROM "assets" WHERE "id"::text LIKE '%ab431d3a%';
SELECT * FROM "asset" WHERE "id"::text LIKE '%ab431d3a%';
```
### Checksum
:::note
You can calculate the checksum for a particular file by using the command `sha1sum <filename>`.
:::
```sql title="Find by checksum (SHA-1)"
SELECT encode("checksum", 'hex') FROM "assets";
SELECT * FROM "assets" WHERE "checksum" = decode('69de19c87658c4c15d9cacb9967b8e033bf74dd1', 'hex');
SELECT * FROM "assets" WHERE "checksum" = '\x69de19c87658c4c15d9cacb9967b8e033bf74dd1'; -- alternate notation
SELECT encode("checksum", 'hex') FROM "asset";
SELECT * FROM "asset" WHERE "checksum" = decode('69de19c87658c4c15d9cacb9967b8e033bf74dd1', 'hex');
SELECT * FROM "asset" WHERE "checksum" = '\x69de19c87658c4c15d9cacb9967b8e033bf74dd1'; -- alternate notation
```
```sql title="Find duplicate assets with identical checksum (SHA-1) (excluding trashed files)"
SELECT T1."checksum", array_agg(T2."id") ids FROM "assets" T1
INNER JOIN "assets" T2 ON T1."checksum" = T2."checksum" AND T1."id" != T2."id" AND T2."deletedAt" IS NULL
SELECT T1."checksum", array_agg(T2."id") ids FROM "asset" T1
INNER JOIN "asset" T2 ON T1."checksum" = T2."checksum" AND T1."id" != T2."id" AND T2."deletedAt" IS NULL
WHERE T1."deletedAt" IS NULL GROUP BY T1."checksum";
```
### Metadata
```sql title="Live photos"
SELECT * FROM "assets" WHERE "livePhotoVideoId" IS NOT NULL;
SELECT * FROM "asset" WHERE "livePhotoVideoId" IS NOT NULL;
```
```sql title="By description"
SELECT "assets".*, "exif"."description" FROM "exif"
JOIN "assets" ON "assets"."id" = "exif"."assetId"
WHERE TRIM("exif"."description") <> ''; -- all files with a description
SELECT "assets".*, "exif"."description" FROM "exif"
JOIN "assets" ON "assets"."id" = "exif"."assetId"
WHERE "exif"."description" ILIKE '%string to match%'; -- search by string
SELECT "asset".*, "asset_exif"."description" FROM "asset_exif"
JOIN "asset" ON "asset"."id" = "asset_exif"."assetId"
WHERE TRIM("asset_exif"."description") <> ''; -- all files with a description
SELECT "asset".*, "asset_exif"."description" FROM "asset_exif"
JOIN "asset" ON "asset"."id" = "asset_exif"."assetId"
WHERE "asset_exif"."description" ILIKE '%string to match%'; -- search by string
```
```sql title="Without metadata"
SELECT "assets".* FROM "exif"
LEFT JOIN "assets" ON "assets"."id" = "exif"."assetId"
WHERE "exif"."assetId" IS NULL;
SELECT "asset".* FROM "asset_exif"
LEFT JOIN "asset" ON "asset"."id" = "asset_exif"."assetId"
WHERE "asset_exif"."assetId" IS NULL;
```
```sql title="size < 100,000 bytes, smallest to largest"
SELECT * FROM "assets"
JOIN "exif" ON "assets"."id" = "exif"."assetId"
WHERE "exif"."fileSizeInByte" < 100000
ORDER BY "exif"."fileSizeInByte" ASC;
SELECT * FROM "asset"
JOIN "asset_exif" ON "asset"."id" = "asset_exif"."assetId"
WHERE "asset_exif"."fileSizeInByte" < 100000
ORDER BY "asset_exif"."fileSizeInByte" ASC;
```
```sql title="Without thumbnails"
SELECT * FROM "assets" WHERE "assets"."previewPath" IS NULL OR "assets"."thumbnailPath" IS NULL;
```
### Type
```sql title="By type"
SELECT * FROM "assets" WHERE "assets"."type" = 'VIDEO';
SELECT * FROM "assets" WHERE "assets"."type" = 'IMAGE';
SELECT * FROM "asset" WHERE "asset"."type" = 'VIDEO';
SELECT * FROM "asset" WHERE "asset"."type" = 'IMAGE';
```
```sql title="Count by type"
SELECT "assets"."type", COUNT(*) FROM "assets" GROUP BY "assets"."type";
SELECT "asset"."type", COUNT(*) FROM "asset" GROUP BY "asset"."type";
```
```sql title="Count by type (per user)"
SELECT "users"."email", "assets"."type", COUNT(*) FROM "assets"
JOIN "users" ON "assets"."ownerId" = "users"."id"
GROUP BY "assets"."type", "users"."email" ORDER BY "users"."email";
SELECT "user"."email", "asset"."type", COUNT(*) FROM "asset"
JOIN "user" ON "asset"."ownerId" = "user"."id"
GROUP BY "asset"."type", "user"."email" ORDER BY "user"."email";
```
```sql title="Failed file movements"
SELECT * FROM "move_history";
## Tags
```sql title="Count by tag"
SELECT "t"."value" AS "tag_name", COUNT(*) AS "number_assets" FROM "tag" "t"
JOIN "tag_asset" "ta" ON "t"."id" = "ta"."tagsId" JOIN "asset" "a" ON "ta"."assetsId" = "a"."id"
WHERE "a"."visibility" != 'hidden'
GROUP BY "t"."value" ORDER BY "number_assets" DESC;
```
```sql title="Count by tag (per user)"
SELECT "t"."value" AS "tag_name", "u"."email" as "user_email", COUNT(*) AS "number_assets" FROM "tag" "t"
JOIN "tag_asset" "ta" ON "t"."id" = "ta"."tagsId" JOIN "asset" "a" ON "ta"."assetsId" = "a"."id" JOIN "user" "u" ON "a"."ownerId" = "u"."id"
WHERE "a"."visibility" != 'hidden'
GROUP BY "t"."value", "u"."email" ORDER BY "number_assets" DESC;
```
## Users
```sql title="List all users"
SELECT * FROM "users";
SELECT * FROM "user";
```
```sql title="Get owner info from asset ID"
SELECT "users".* FROM "users" JOIN "assets" ON "users"."id" = "assets"."ownerId" WHERE "assets"."id" = 'fa310b01-2f26-4b7a-9042-d578226e021f';
SELECT "user".* FROM "user" JOIN "asset" ON "user"."id" = "asset"."ownerId" WHERE "asset"."id" = 'fa310b01-2f26-4b7a-9042-d578226e021f';
```
## System Config
```sql title="Custom settings"
SELECT "key", "value" FROM "system_metadata" WHERE "key" = 'system-config';
```
(Only used when not using the [config file](/docs/install/config-file))
## Persons
```sql title="Delete person and unset it for the faces it was associated with"
DELETE FROM "person" WHERE "name" = 'PersonNameHere';
```
## System
### Config
```sql title="Custom settings"
SELECT "key", "value" FROM "system_metadata" WHERE "key" = 'system-config';
```
(Only used when not using the [config file](/docs/install/config-file))
### File properties
```sql title="Without thumbnails"
SELECT * FROM "asset" WHERE "asset"."previewPath" IS NULL OR "asset"."thumbnailPath" IS NULL;
```
```sql title="Failed file movements"
SELECT * FROM "move_history";
```
## Postgres internal
```sql title="Change DB_PASSWORD"

View File

@@ -12,7 +12,7 @@ If you want Immich to be able to delete the images in the external library or ad
```diff
immich-server:
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- ${UPLOAD_LOCATION}:/data
+ - /home/user/photos1:/home/user/photos1:ro
+ - /mnt/photos2:/mnt/photos2:ro # you can delete this line if you only have one mount point, or you can add more lines if you have more than two
```

View File

@@ -29,29 +29,26 @@ These environment variables are used by the `docker-compose.yml` file and do **N
## General
| Variable | Description | Default | Containers | Workers |
| :---------------------------------- | :---------------------------------------------------------------------------------------- | :---------------------------------: | :----------------------- | :----------------- |
| `TZ` | Timezone | <sup>\*1</sup> | server | microservices |
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
| `IMMICH_LOG_LEVEL` | Log level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
| `IMMICH_MEDIA_LOCATION` | Media location inside the container ⚠️**You probably shouldn't set this**<sup>\*2</sup>⚠️ | `/usr/src/app/upload`<sup>\*3</sup> | server | api, microservices |
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
| `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 | |
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices |
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
| `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api |
| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/docs/administration/system-integrity) | | server | api, microservices |
| Variable | Description | Default | Containers | Workers |
| :---------------------------------- | :---------------------------------------------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- |
| `TZ` | Timezone | <sup>\*1</sup> | server | microservices |
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
| `IMMICH_LOG_LEVEL` | Log level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
| `IMMICH_MEDIA_LOCATION` | Media location 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 |
| `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 | |
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices |
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
| `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api |
| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/docs/administration/system-integrity) | | server | api, microservices |
\*1: `TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`.
`TZ` is used by `exiftool` as a fallback in case the timezone cannot be determined from the image metadata. It is also used for logfile timestamps and cron job execution.
\*2: This path is where the Immich code looks for the files, which is internal to the docker container. Setting it to a path on your host will certainly break things, you should use the `UPLOAD_LOCATION` variable instead.
\*3: With the default `WORKDIR` of `/usr/src/app`, this path will resolve to `/usr/src/app/upload`.
It only needs to be set if the Immich deployment method is changing.
## Workers
| Variable | Description | Default | Containers |
@@ -202,12 +199,11 @@ Additional machine learning parameters can be tuned from the admin UI.
| `IMMICH_TELEMETRY_INCLUDE` | Collect these telemetries. List of `host`, `api`, `io`, `repo`, `job`. Note: You can also specify `all` to enable all | | server | api, microservices |
| `IMMICH_TELEMETRY_EXCLUDE` | Do not collect these telemetries. List of `host`, `api`, `io`, `repo`, `job` | | server | api, microservices |
## Docker Secrets
## Secrets
The following variables support the use of [Docker secrets][docker-secrets] for additional security.
The following variables support reading from files, either via [Systemd Credentials][systemd-creds] or [Docker secrets][docker-secrets] for additional security.
To use any of these, replace the regular environment variable with the equivalent `_FILE` environment variable. The value of
the `_FILE` variable should be set to the path of a file containing the variable value.
To use any of these, either set `CREDENTIALS_DIRECTORY` to a directory that contains files whose name is the regular variable” name, and whose content is the secret. If using Docker Secrets, setting `CREDENTIALS_DIRECTORY=/run/secrets` will cause all secrets present to be used. Alternatively, replace the regular variable with the equivalent `_FILE` environment variable as below. The value of the `_FILE` variable should be set to the path of a file containing the variable value.
| Regular Variable | Equivalent Docker Secrets '\_FILE' Variable |
| :----------------- | :------------------------------------------ |
@@ -229,3 +225,4 @@ to use a Docker secret for the password in the Redis container.
[docker-secrets-docs]: https://github.com/docker-library/docs/tree/master/postgres#docker-secrets
[docker-secrets]: https://docs.docker.com/engine/swarm/secrets/
[ioredis]: https://ioredis.readthedocs.io/en/latest/README/#connect-to-redis
[systemd-creds]: https://systemd.io/CREDENTIALS/

View File

@@ -100,6 +100,11 @@ const projects: CommunityProjectProps[] = [
description: 'Automatically optimize files uploaded to Immich in order to save storage space',
url: 'https://github.com/miguelangel-nubla/immich-upload-optimizer',
},
{
title: 'Immich Machine Learning Load Balancer',
description: 'Speed up your machine learning by load balancing your requests to multiple computers',
url: 'https://github.com/apetersson/immich_ml_balancer',
},
];
function CommunityProject({ title, description, url }: CommunityProjectProps): JSX.Element {

View File

@@ -2,4 +2,23 @@
## TypeORM Upgrade
The upgrade to Immich `v2.x.x` has a required upgrade path to `v1.132.0+`. This means it is required to start up the application at least once on version `1.132.0` (or later). Doing so will complete database schema upgrades that are required for `v2.0.0`. After Immich has successfully booted on this version, shut the system down and try the `v2.x.x` upgrade again.
In order to update to Immich to `v1.137.0` (or above), the application must be started at least once on a version in the range between `1.132.0` and `1.136.0`. Doing so will complete database schema upgrades that are required for `v1.137.0` (and above). After Immich has successfully updated to a version in this range, you can now attempt to update to v1.137.0 (or above). We recommend users upgrade to `1.132.0` since it does not have any other breaking changes.
## Inconsistent Media Location
:::caution
This error is related to the location of media files _inside the container_. Never move files on the host system when you run into this error message.
:::
Immich automatically tries to detect where your Immich data is located. On start up, it compares the detected media location with the file paths in the database and throws an Inconsistent Media Location error when they do not match.
To fix this issue, verify that the `IMMICH_MEDIA_LOCATION` environment variable and `UPLOAD_LOCATION` volume mount are in sync with the database paths.
If you would like to migrate from one media location to another, simply successfully start Immich on `v1.136.0` or later, then do the following steps:
1. Stop Immich
2. Update `IMMICH_MEDIA_LOCATION` to the new location
3. Update the right-hand side of the `UPLOAD_LOCATION` volume mount to the new location
4. Start up Immich
After version `1.136.0`, Immich can detect when a media location has moved and will automatically update the database paths to keep them in sync.

View File

@@ -1,4 +1,8 @@
[
{
"label": "v1.136.0",
"url": "https://v1.136.0.archive.immich.app"
},
{
"label": "v1.135.3",
"url": "https://v1.135.3.archive.immich.app"

View File

@@ -38,7 +38,7 @@ services:
image: redis:6.2-alpine@sha256:7fe72c486b910f6b1a9769c937dad5d63648ddee82e056f47417542dd40825bb
database:
image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:3aef84a0a4fabbda17ef115c3019ba0c914ec73e9f6e59203674322d858b8eea
image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:0e763a2383d56f90364fcd72767ac41400cd30d2627f407f7e7960c9f1923c21
command: -c fsync=off -c shared_preload_libraries=vchord.so -c config_file=/var/lib/postgresql/data/postgresql.conf
environment:
POSTGRES_PASSWORD: postgres

189
e2e/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "immich-e2e",
"version": "1.135.3",
"version": "1.136.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich-e2e",
"version": "1.135.3",
"version": "1.136.0",
"license": "GNU Affero General Public License version 3",
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
@@ -16,14 +16,14 @@
"@playwright/test": "^1.44.1",
"@socket.io/component-emitter": "^3.1.2",
"@types/luxon": "^3.4.2",
"@types/node": "^22.16.4",
"@types/node": "^22.16.5",
"@types/oidc-provider": "^9.0.0",
"@types/pg": "^8.15.1",
"@types/pngjs": "^6.0.4",
"@types/supertest": "^6.0.2",
"@vitest/coverage-v8": "^3.0.0",
"eslint": "^9.14.0",
"eslint-config-prettier": "^10.0.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^59.0.0",
"exiftool-vendored": "^28.3.1",
@@ -46,7 +46,7 @@
},
"../cli": {
"name": "@immich/cli",
"version": "2.2.72",
"version": "2.2.73",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {
@@ -68,13 +68,13 @@
"@types/lodash-es": "^4.17.12",
"@types/micromatch": "^4.0.9",
"@types/mock-fs": "^4.13.1",
"@types/node": "^22.16.4",
"@types/node": "^22.16.5",
"@vitest/coverage-v8": "^3.0.0",
"byte-size": "^9.0.0",
"cli-progress": "^3.12.0",
"commander": "^12.0.0",
"eslint": "^9.14.0",
"eslint-config-prettier": "^10.0.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^59.0.0",
"globals": "^16.0.0",
@@ -95,14 +95,14 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.135.3",
"version": "1.136.0",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@oazapfts/runtime": "^1.0.2"
},
"devDependencies": {
"@types/node": "^22.16.4",
"@types/node": "^22.16.5",
"typescript": "^5.3.3"
}
},
@@ -2125,17 +2125,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz",
"integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz",
"integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/type-utils": "8.37.0",
"@typescript-eslint/utils": "8.37.0",
"@typescript-eslint/visitor-keys": "8.37.0",
"@typescript-eslint/scope-manager": "8.38.0",
"@typescript-eslint/type-utils": "8.38.0",
"@typescript-eslint/utils": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -2149,7 +2149,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.37.0",
"@typescript-eslint/parser": "^8.38.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
@@ -2165,16 +2165,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz",
"integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz",
"integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/visitor-keys": "8.37.0",
"@typescript-eslint/scope-manager": "8.38.0",
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2190,14 +2190,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz",
"integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz",
"integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.37.0",
"@typescript-eslint/types": "^8.37.0",
"@typescript-eslint/tsconfig-utils": "^8.38.0",
"@typescript-eslint/types": "^8.38.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2212,14 +2212,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz",
"integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz",
"integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/visitor-keys": "8.37.0"
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2230,9 +2230,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz",
"integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz",
"integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2247,15 +2247,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz",
"integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz",
"integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/utils": "8.37.0",
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0",
"@typescript-eslint/utils": "8.38.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -2272,9 +2272,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz",
"integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz",
"integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2286,16 +2286,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz",
"integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz",
"integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.37.0",
"@typescript-eslint/tsconfig-utils": "8.37.0",
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/visitor-keys": "8.37.0",
"@typescript-eslint/project-service": "8.38.0",
"@typescript-eslint/tsconfig-utils": "8.38.0",
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -2341,16 +2341,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz",
"integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz",
"integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.37.0"
"@typescript-eslint/scope-manager": "8.38.0",
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2365,13 +2365,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz",
"integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz",
"integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/types": "8.38.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -3525,9 +3525,9 @@
}
},
"node_modules/eslint-config-prettier": {
"version": "10.1.5",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz",
"integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==",
"version": "10.1.8",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
"bin": {
@@ -3541,9 +3541,9 @@
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz",
"integrity": "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==",
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.3.tgz",
"integrity": "sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3983,15 +3983,16 @@
}
},
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -5708,15 +5709,15 @@
}
},
"node_modules/prettier-plugin-organize-imports": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz",
"integrity": "sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.2.0.tgz",
"integrity": "sha512-Zdy27UhlmyvATZi67BTnLcKTo8fm6Oik59Sz6H64PgZJVs6NJpPD1mT240mmJn62c98/QaL+r3kx9Q3gRpDajg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"prettier": ">=2.0",
"typescript": ">=2.9",
"vue-tsc": "^2.1.0"
"vue-tsc": "^2.1.0 || 3"
},
"peerDependenciesMeta": {
"vue-tsc": {
@@ -6469,35 +6470,35 @@
}
},
"node_modules/superagent": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.2.tgz",
"integrity": "sha512-vWMq11OwWCC84pQaFPzF/VO3BrjkCeewuvJgt1jfV0499Z1QSAWN4EqfMM5WlFDDX9/oP8JjlDKpblrmEoyu4Q==",
"version": "10.2.3",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz",
"integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==",
"dev": true,
"license": "MIT",
"dependencies": {
"component-emitter": "^1.3.0",
"component-emitter": "^1.3.1",
"cookiejar": "^2.1.4",
"debug": "^4.3.4",
"debug": "^4.3.7",
"fast-safe-stringify": "^2.1.1",
"form-data": "^4.0.0",
"form-data": "^4.0.4",
"formidable": "^3.5.4",
"methods": "^1.1.2",
"mime": "2.6.0",
"qs": "^6.11.0"
"qs": "^6.11.2"
},
"engines": {
"node": ">=14.18.0"
}
},
"node_modules/supertest": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.3.tgz",
"integrity": "sha512-ORY0gPa6ojmg/C74P/bDoS21WL6FMXq5I8mawkEz30/zkwdu0gOeqstFy316vHG6OKxqQ+IbGneRemHI8WraEw==",
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz",
"integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==",
"dev": true,
"license": "MIT",
"dependencies": {
"methods": "^1.1.2",
"superagent": "^10.2.2"
"superagent": "^10.2.3"
},
"engines": {
"node": ">=14.18.0"
@@ -6817,16 +6818,16 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.37.0.tgz",
"integrity": "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==",
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.38.0.tgz",
"integrity": "sha512-FsZlrYK6bPDGoLeZRuvx2v6qrM03I0U0SnfCLPs/XCCPCFD80xU9Pg09H/K+XFa68uJuZo7l/Xhs+eDRg2l3hg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.37.0",
"@typescript-eslint/parser": "8.37.0",
"@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/utils": "8.37.0"
"@typescript-eslint/eslint-plugin": "8.38.0",
"@typescript-eslint/parser": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0",
"@typescript-eslint/utils": "8.38.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"

View File

@@ -1,6 +1,6 @@
{
"name": "immich-e2e",
"version": "1.135.3",
"version": "1.136.0",
"description": "",
"main": "index.js",
"type": "module",
@@ -26,14 +26,14 @@
"@playwright/test": "^1.44.1",
"@socket.io/component-emitter": "^3.1.2",
"@types/luxon": "^3.4.2",
"@types/node": "^22.16.4",
"@types/node": "^22.16.5",
"@types/oidc-provider": "^9.0.0",
"@types/pg": "^8.15.1",
"@types/pngjs": "^6.0.4",
"@types/supertest": "^6.0.2",
"@vitest/coverage-v8": "^3.0.0",
"eslint": "^9.14.0",
"eslint-config-prettier": "^10.0.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^59.0.0",
"exiftool-vendored": "^28.3.1",

View File

@@ -470,7 +470,7 @@ describe('/albums', () => {
.send({ ids: [asset.id] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no album.addAsset access'));
expect(body).toEqual(errorDto.badRequest('Not found or no albumAsset.create access'));
});
it('should add duplicate assets only once', async () => {
@@ -599,7 +599,7 @@ describe('/albums', () => {
.send({ ids: [user1Asset1.id] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no album.removeAsset access'));
expect(body).toEqual(errorDto.badRequest('Not found or no albumAsset.delete access'));
});
it('should remove duplicate assets only once', async () => {

View File

@@ -186,18 +186,6 @@ export const utils = {
}
},
resetFilesystem: async () => {
const mediaInternal = '/usr/src/app/upload';
const dirs = [
`"${mediaInternal}/thumbs"`,
`"${mediaInternal}/upload"`,
`"${mediaInternal}/library"`,
`"${mediaInternal}/encoded-video"`,
].join(' ');
await execPromise(`docker exec -i "immich-e2e-server" /bin/bash -c "rm -rf ${dirs} && mkdir ${dirs}"`);
},
unzip: async (input: string, output: string) => {
await execPromise(`unzip -o -d "${output}" "${input}"`);
},

View File

@@ -37,6 +37,7 @@ test.describe('Registration', () => {
await page.getByRole('button', { name: 'Server Privacy' }).click();
await page.getByRole('button', { name: 'User Privacy' }).click();
await page.getByRole('button', { name: 'Storage Template' }).click();
await page.getByRole('button', { name: 'Backups' }).click();
await page.getByRole('button', { name: 'Done' }).click();
// success

View File

@@ -573,6 +573,8 @@
"backup_options_page_title": "Nastavení záloh",
"backup_setting_subtitle": "Správa nastavení zálohování na pozadí a na popředí",
"backward": "Pozpátku",
"beta_sync": "Stav synchronizace beta verze",
"beta_sync_subtitle": "Správa nového systému synchronizace",
"biometric_auth_enabled": "Biometrické ověřování je povoleno",
"biometric_locked_out": "Jste vyloučeni z biometrického ověřování",
"biometric_no_options": "Biometrické možnosti nejsou k dispozici",
@@ -590,7 +592,7 @@
"cache_settings_clear_cache_button": "Vymazat vyrovnávací paměť",
"cache_settings_clear_cache_button_title": "Vymaže vyrovnávací paměť aplikace. To výrazně ovlivní výkon aplikace, dokud se vyrovnávací paměť neobnoví.",
"cache_settings_duplicated_assets_clear_button": "VYMAZAT",
"cache_settings_duplicated_assets_subtitle": "Fotografie a videa, které aplikace zařadila na černou listinu",
"cache_settings_duplicated_assets_subtitle": "Fotografie a videa, které aplikace ignoruje",
"cache_settings_duplicated_assets_title": "Duplicitní položky ({count})",
"cache_settings_statistics_album": "Knihovna náhledů",
"cache_settings_statistics_full": "Kompletní fotografie",
@@ -1051,6 +1053,9 @@
"haptic_feedback_switch": "Povolit dotykovou zpětnou vazbu",
"haptic_feedback_title": "Dotyková zpětná vazba",
"has_quota": "Má kvótu",
"hash_asset": "Hash položky",
"hashed_assets": "Hashované položky",
"hashing": "Hashování",
"header_settings_add_header_tip": "Přidat hlavičku",
"header_settings_field_validator_msg": "Hodnota nemůže být prázdná",
"header_settings_header_name_input": "Název hlavičky",
@@ -1083,6 +1088,7 @@
"host": "Hostitel",
"hour": "Hodina",
"id": "ID",
"idle": "Nečinnost",
"ignore_icloud_photos": "Ignorovat fotografie na iCloudu",
"ignore_icloud_photos_description": "Fotografie uložené na iCloudu se nebudou nahrávat na Immich server",
"image": "Obrázek",
@@ -1165,7 +1171,9 @@
"list": "Seznam",
"loading": "Načítání",
"loading_search_results_failed": "Načítání výsledků vyhledávání se nezdařilo",
"local": "Místní",
"local_asset_cast_failed": "Nelze odeslat položku, která není nahraná na serveru",
"local_assets": "Místní položky",
"local_network": "Místní síť",
"local_network_sheet_info": "Aplikace se při použití zadané sítě Wi-Fi připojí k serveru prostřednictvím tohoto URL",
"location_permission": "Oprávnění polohy",
@@ -1322,6 +1330,7 @@
"no_results": "Žádné výsledky",
"no_results_description": "Zkuste použít synonymum nebo obecnější klíčové slovo",
"no_shared_albums_message": "Vytvořte si album a sdílejte fotografie a videa s lidmi ve své síti",
"no_uploads_in_progress": "Neprobíhá žádné nahrávání",
"not_in_any_album": "Bez alba",
"not_selected": "Není vybráno",
"note_apply_storage_label_to_previously_uploaded assets": "Upozornění: Chcete-li použít štítek úložiště na dříve nahrané položky, spusťte příkaz",
@@ -1359,6 +1368,7 @@
"original": "originál",
"other": "Ostatní",
"other_devices": "Ostatní zařízení",
"other_entities": "Ostatní entity",
"other_variables": "Další proměnné",
"owned": "Vlastní",
"owner": "Vlastník",
@@ -1519,6 +1529,8 @@
"refreshing_faces": "Obnovování obličejů",
"refreshing_metadata": "Obnovování metadat",
"regenerating_thumbnails": "Regenerace miniatur",
"remote": "Vzdálený",
"remote_assets": "Vzdálené položky",
"remove": "Odstranit",
"remove_assets_album_confirmation": "Opravdu chcete z alba odstranit {count, plural, one {# položku} few {# položky} other {# položek}}?",
"remove_assets_shared_link_confirmation": "Opravdu chcete ze sdíleného odkazu odstranit {count, plural, one {# položku} few {# položky} other {# položek}}?",
@@ -1556,11 +1568,15 @@
"reset_password": "Obnovit heslo",
"reset_people_visibility": "Obnovit viditelnost lidí",
"reset_pin_code": "Resetovat PIN kód",
"reset_sqlite": "Obnovit SQLite databázi",
"reset_sqlite_confirmation": "Jste si jisti, že chcete obnovit SQLite databázi? Pro opětovnou synchronizaci dat se budete muset odhlásit a znovu přihlásit",
"reset_sqlite_success": "Obnovení SQLite databáze proběhlo úspěšně",
"reset_to_default": "Obnovit výchozí nastavení",
"resolve_duplicates": "Vyřešit duplicity",
"resolved_all_duplicates": "Vyřešeny všechny duplicity",
"restore": "Obnovit",
"restore_all": "Obnovit vše",
"restore_trash_action_prompt": "{count} obnoveno z koše",
"restore_user": "Obnovit uživatele",
"restored_asset": "Položka obnovena",
"resume": "Pokračovat",
@@ -1569,6 +1585,7 @@
"role": "Role",
"role_editor": "Editor",
"role_viewer": "Divák",
"running": "Probíhá",
"save": "Uložit",
"save_to_gallery": "Uložit do galerie",
"saved_api_key": "API klíč uložen",
@@ -1822,6 +1839,7 @@
"storage_quota": "Kvóta úložiště",
"storage_usage": "Využito {used} z {available}",
"submit": "Odeslat",
"success": "Úspěch",
"suggestions": "Návrhy",
"sunrise_on_the_beach": "Východ slunce na pláži",
"support": "Podpora",
@@ -1831,6 +1849,8 @@
"sync": "Synchronizovat",
"sync_albums": "Synchronizovat alba",
"sync_albums_manual_subtitle": "Synchronizovat všechna nahraná videa a fotografie do vybraných záložních alb",
"sync_local": "Synchronizovat místní",
"sync_remote": "Synchronizovat vzdálené",
"sync_upload_album_setting_subtitle": "Vytvořit a nahrát fotografie a videa do vybraných alb na Immich",
"tag": "Značka",
"tag_assets": "Přiřadit značku",
@@ -1841,6 +1861,7 @@
"tag_updated": "Aktualizována značka: {tag}",
"tagged_assets": "Přiřazena značka {count, plural, one {# položce} other {# položkám}}",
"tags": "Značky",
"tap_to_run_job": "Klepnutím na spustíte úlohu",
"template": "Šablona",
"theme": "Motiv",
"theme_selection": "Výběr motivu",

View File

@@ -573,6 +573,8 @@
"backup_options_page_title": "Sicherungsoptionen",
"backup_setting_subtitle": "Verwaltung der Upload-Einstellungen im Hintergrund und im Vordergrund",
"backward": "Rückwärts",
"beta_sync": "Status des Beta Sync",
"beta_sync_subtitle": "Verwalte das neue Synchronisierungssystem",
"biometric_auth_enabled": "Biometrische Authentifizierung aktiviert",
"biometric_locked_out": "Du bist von der biometrischen Authentifizierung ausgeschlossen",
"biometric_no_options": "Keine biometrischen Optionen verfügbar",
@@ -1051,6 +1053,9 @@
"haptic_feedback_switch": "Haptisches Feedback aktivieren",
"haptic_feedback_title": "Haptisches Feedback",
"has_quota": "Kontingent",
"hash_asset": "Dateihash",
"hashed_assets": "Gehashte Dateien",
"hashing": "Hashen",
"header_settings_add_header_tip": "Header hinzufügen",
"header_settings_field_validator_msg": "Der Wert darf nicht leer sein",
"header_settings_header_name_input": "Header-Name",
@@ -1083,6 +1088,7 @@
"host": "Host",
"hour": "Stunde",
"id": "ID",
"idle": "Untätig",
"ignore_icloud_photos": "iCloud Fotos ignorieren",
"ignore_icloud_photos_description": "Fotos, die in der iCloud gespeichert sind, werden nicht auf den immich Server hochgeladen",
"image": "Bild",
@@ -1165,7 +1171,9 @@
"list": "Liste",
"loading": "Laden",
"loading_search_results_failed": "Laden von Suchergebnissen fehlgeschlagen",
"local": "Lokal",
"local_asset_cast_failed": "Eine Datei, die nicht auf den Server hochgeladen wurde, kann nicht gecastet werden",
"local_assets": "Lokale Dateien",
"local_network": "Lokales Netzwerk",
"local_network_sheet_info": "Die App stellt über diese URL eine Verbindung zum Server her, wenn sie das angegebene WLAN-Netzwerk verwendet",
"location_permission": "Standort Genehmigung",
@@ -1322,6 +1330,7 @@
"no_results": "Keine Ergebnisse",
"no_results_description": "Versuche es mit einem Synonym oder einem allgemeineren Stichwort",
"no_shared_albums_message": "Erstelle ein Album, um Fotos und Videos mit Personen in deinem Netzwerk zu teilen",
"no_uploads_in_progress": "Kein Upload in Bearbeitung",
"not_in_any_album": "In keinem Album",
"not_selected": "Nicht ausgewählt",
"note_apply_storage_label_to_previously_uploaded assets": "Hinweis: Um eine Speicherpfadbezeichnung anzuwenden, starte den",
@@ -1359,6 +1368,7 @@
"original": "Original",
"other": "Sonstiges",
"other_devices": "Andere Geräte",
"other_entities": "Andere Entitäten",
"other_variables": "Sonstige Variablen",
"owned": "Eigenes",
"owner": "Besitzer",
@@ -1519,6 +1529,8 @@
"refreshing_faces": "Gesichter werden aktualisiert",
"refreshing_metadata": "Metadaten werden aktualisiert",
"regenerating_thumbnails": "Miniaturansichten werden neu erstellt",
"remote": "Entfernt",
"remote_assets": "Entfernte Dateien",
"remove": "Entfernen",
"remove_assets_album_confirmation": "Bist du sicher, dass du {count, plural, one {# Datei} other {# Dateien}} aus dem Album entfernen willst?",
"remove_assets_shared_link_confirmation": "Bist du sicher, dass du {count, plural, one {# Datei} other {# Dateien}} von diesem geteilten Link entfernen willst?",
@@ -1556,11 +1568,15 @@
"reset_password": "Passwort zurücksetzen",
"reset_people_visibility": "Sichtbarkeit von Personen zurücksetzen",
"reset_pin_code": "PIN Code zurücksetzen",
"reset_sqlite": "SQLite Datenbank zurücksetzen",
"reset_sqlite_confirmation": "Bist du sicher, dass du die SQLite-Datenbank zurücksetzen willst? Du musst dich ab- und wieder anmelden, um die Daten neu zu synchronisieren",
"reset_sqlite_success": "SQLite Datenbank erfolgreich zurückgesetzt",
"reset_to_default": "Auf Standard zurücksetzen",
"resolve_duplicates": "Duplikate entfernen",
"resolved_all_duplicates": "Alle Duplikate aufgelöst",
"restore": "Wiederherstellen",
"restore_all": "Alle wiederherstellen",
"restore_trash_action_prompt": "{count} aus dem Papierkorb wiederhergestellt",
"restore_user": "Nutzer wiederherstellen",
"restored_asset": "Datei wiederhergestellt",
"resume": "Fortsetzen",
@@ -1569,6 +1585,7 @@
"role": "Rolle",
"role_editor": "Bearbeiter",
"role_viewer": "Betrachter",
"running": "Läuft",
"save": "Speichern",
"save_to_gallery": "In Galerie speichern",
"saved_api_key": "API-Schlüssel wurde gespeichert",
@@ -1822,6 +1839,7 @@
"storage_quota": "Speicherplatz-Kontingent",
"storage_usage": "{used} von {available} verwendet",
"submit": "Bestätigen",
"success": "Erfolgreich",
"suggestions": "Vorschläge",
"sunrise_on_the_beach": "Sonnenaufgang am Strand",
"support": "Unterstützung",
@@ -1831,6 +1849,8 @@
"sync": "Synchronisieren",
"sync_albums": "Alben synchronisieren",
"sync_albums_manual_subtitle": "Synchronisiere alle hochgeladenen Videos und Fotos in die ausgewählten Backup-Alben",
"sync_local": "Lokal synchronisieren",
"sync_remote": "Entfernt synchronisieren",
"sync_upload_album_setting_subtitle": "Erstelle deine ausgewählten Alben in Immich und lade die Fotos und Videos dort hoch",
"tag": "Tag",
"tag_assets": "Dateien taggen",
@@ -1841,6 +1861,7 @@
"tag_updated": "Tag aktualisiert: {tag}",
"tagged_assets": "{count, plural, one {# Datei} other {# Dateien}} getagged",
"tags": "Tags",
"tap_to_run_job": "Tippen um den Job zu starten",
"template": "Vorlage",
"theme": "Theme",
"theme_selection": "Themenauswahl",

View File

@@ -14,6 +14,7 @@
"add_a_location": "Add a location",
"add_a_name": "Add a name",
"add_a_title": "Add a title",
"add_birthday": "Add a birthday",
"add_endpoint": "Add endpoint",
"add_exclusion_pattern": "Add exclusion pattern",
"add_import_path": "Add import path",
@@ -44,6 +45,13 @@
"backup_database": "Create Database Dump",
"backup_database_enable_description": "Enable database dumps",
"backup_keep_last_amount": "Amount of previous dumps to keep",
"backup_onboarding_1_description": "offsite copy in the cloud or at another physical location.",
"backup_onboarding_2_description": "local copies on different devices. This includes the main files and a backup of those files locally.",
"backup_onboarding_3_description": "total copies of your data, including the original files. This includes 1 offsite copy and 2 local copies.",
"backup_onboarding_description": "A <backblaze-link>3-2-1 backup strategy</backblaze-link> is recommended to protect your data. You should keep copies of your uploaded photos/videos as well as the Immich database for a comprehensive backup solution.",
"backup_onboarding_footer": "For more information about backing up Immich, please refer to the <link>documentation</link>.",
"backup_onboarding_parts_title": "A 3-2-1 backup includes:",
"backup_onboarding_title": "Backups",
"backup_settings": "Database Dump Settings",
"backup_settings_description": "Manage database dump settings.",
"cleared_jobs": "Cleared jobs for: {job}",
@@ -397,6 +405,7 @@
"album_cover_updated": "Album cover updated",
"album_delete_confirmation": "Are you sure you want to delete the album {album}?",
"album_delete_confirmation_description": "If this album is shared, other users will not be able to access it anymore.",
"album_deleted": "Album deleted",
"album_info_card_backup_album_excluded": "EXCLUDED",
"album_info_card_backup_album_included": "INCLUDED",
"album_info_updated": "Album info updated",
@@ -510,6 +519,7 @@
"back_close_deselect": "Back, close, or deselect",
"background_location_permission": "Background location permission",
"background_location_permission_content": "In order to switch networks when running in the background, Immich must *always* have precise location access so the app can read the Wi-Fi network's name",
"backup": "Backup",
"backup_album_selection_page_albums_device": "Albums on device ({count})",
"backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude",
"backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.",
@@ -723,6 +733,7 @@
"current_server_address": "Current server address",
"custom_locale": "Custom Locale",
"custom_locale_description": "Format dates and numbers based on the language and the region",
"custom_url": "Custom URL",
"daily_title_text_date": "E, MMM dd",
"daily_title_text_date_year": "E, MMM dd, yyyy",
"dark": "Dark",
@@ -742,7 +753,8 @@
"default_locale": "Default Locale",
"default_locale_description": "Format dates and numbers based on your browser locale",
"delete": "Delete",
"delete_action_prompt": "{count} deleted permanently",
"delete_action_confirmation_message": "Are you sure you want to delete this asset? This action will move the asset to the server's trash and will prompt if you want to delete it locally",
"delete_action_prompt": "{count} deleted",
"delete_album": "Delete album",
"delete_api_key_prompt": "Are you sure you want to delete this API key?",
"delete_dialog_alert": "These items will be permanently deleted from Immich and from your device",
@@ -760,6 +772,8 @@
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only",
"delete_local_dialog_ok_force": "Delete Anyway",
"delete_others": "Delete others",
"delete_permanently": "Delete permanently",
"delete_permanently_action_prompt": "{count} deleted permanently",
"delete_shared_link": "Delete shared link",
"delete_shared_link_dialog_title": "Delete Shared Link",
"delete_tag": "Delete tag",
@@ -815,6 +829,7 @@
"edit": "Edit",
"edit_album": "Edit album",
"edit_avatar": "Edit avatar",
"edit_birthday": "Edit Birthday",
"edit_date": "Edit date",
"edit_date_and_time": "Edit date and time",
"edit_description": "Edit description",
@@ -982,6 +997,7 @@
},
"exif": "Exif",
"exif_bottom_sheet_description": "Add Description...",
"exif_bottom_sheet_description_error": "Error updating description",
"exif_bottom_sheet_details": "DETAILS",
"exif_bottom_sheet_location": "LOCATION",
"exif_bottom_sheet_people": "PEOPLE",
@@ -1002,6 +1018,8 @@
"explorer": "Explorer",
"export": "Export",
"export_as_json": "Export as JSON",
"export_database": "Export Database",
"export_database_description": "Export the SQLite database",
"extension": "Extension",
"external": "External",
"external_libraries": "External Libraries",
@@ -1146,6 +1164,7 @@
"language_no_results_title": "No languages found",
"language_search_hint": "Search languages...",
"language_setting_description": "Select your preferred language",
"large_files": "Large Files",
"last_seen": "Last seen",
"latest_version": "Latest Version",
"latitude": "Latitude",
@@ -1165,7 +1184,6 @@
"light": "Light",
"like_deleted": "Like deleted",
"link_motion_video": "Link motion video",
"link_options": "Link options",
"link_to_oauth": "Link to OAuth",
"linked_oauth_account": "Linked OAuth account",
"list": "List",
@@ -1230,8 +1248,7 @@
"manage_your_devices": "Manage your logged-in devices",
"manage_your_oauth_connection": "Manage your OAuth connection",
"map": "Map",
"map_assets_in_bound": "{count} photo",
"map_assets_in_bounds": "{count} photos",
"map_assets_in_bounds": "{count, plural, one {# photo} other {# photos}}",
"map_cannot_get_user_location": "Cannot get user's location",
"map_location_dialog_yes": "Yes",
"map_location_picker_page_use_location": "Use this location",
@@ -1582,6 +1599,7 @@
"resume": "Resume",
"retry_upload": "Retry upload",
"review_duplicates": "Review duplicates",
"review_large_files": "Review large files",
"role": "Role",
"role_editor": "Editor",
"role_viewer": "Viewer",
@@ -1739,6 +1757,7 @@
"shared_link_clipboard_copied_massage": "Copied to clipboard",
"shared_link_clipboard_text": "Link: {link}\nPassword: {password}",
"shared_link_create_error": "Error while creating shared link",
"shared_link_custom_url_description": "Access this shared link with a custom URL",
"shared_link_edit_description_hint": "Enter the share description",
"shared_link_edit_expire_after_option_day": "1 day",
"shared_link_edit_expire_after_option_days": "{count} days",
@@ -1764,6 +1783,7 @@
"shared_link_info_chip_metadata": "EXIF",
"shared_link_manage_links": "Manage Shared links",
"shared_link_options": "Shared link options",
"shared_link_password_description": "Require a password to access this shared link",
"shared_links": "Shared links",
"shared_links_description": "Share photos and videos with a link",
"shared_photos_and_videos_count": "{assetCount, plural, other {# shared photos & videos.}}",
@@ -1941,11 +1961,13 @@
"updated_at": "Updated",
"updated_password": "Updated password",
"upload": "Upload",
"upload_action_prompt": "{count} queued for upload",
"upload_concurrency": "Upload concurrency",
"upload_details": "Upload Details",
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
"upload_dialog_title": "Upload Asset",
"upload_errors": "Upload completed with {count, plural, one {# error} other {# errors}}, refresh the page to see new upload assets.",
"upload_finished": "Upload finished",
"upload_progress": "Remaining {remaining, number} - Processed {processed, number}/{total, number}",
"upload_skipped_duplicates": "Skipped {count, plural, one {# duplicate asset} other {# duplicate assets}}",
"upload_status_duplicates": "Duplicates",
@@ -1954,6 +1976,7 @@
"upload_success": "Upload success, refresh the page to see new upload assets.",
"upload_to_immich": "Upload to Immich ({count})",
"uploading": "Uploading",
"uploading_media": "Uploading media",
"url": "URL",
"usage": "Usage",
"use_biometric": "Use biometric",

View File

@@ -573,6 +573,8 @@
"backup_options_page_title": "Varundamise valikud",
"backup_setting_subtitle": "Halda taustal ja esiplaanil üleslaadimise seadeid",
"backward": "Tagasi",
"beta_sync": "Beeta sünkroonimise staatus",
"beta_sync_subtitle": "Halda uut sünkroonimissüsteemi",
"biometric_auth_enabled": "Biomeetriline autentimine lubatud",
"biometric_locked_out": "Biomeetriline autentimine on blokeeritud",
"biometric_no_options": "Biomeetrilisi valikuid ei ole",
@@ -590,7 +592,7 @@
"cache_settings_clear_cache_button": "Tühjenda puhver",
"cache_settings_clear_cache_button_title": "Tühjendab rakenduse puhvri. See mõjutab oluliselt rakenduse jõudlust, kuni puhver uuesti täidetakse.",
"cache_settings_duplicated_assets_clear_button": "TÜHJENDA",
"cache_settings_duplicated_assets_subtitle": "Fotod ja videod, mis on rakenduse poolt mustfiltreeritud",
"cache_settings_duplicated_assets_subtitle": "Fotod ja videod, mis on rakenduse poolt ignoreeritud",
"cache_settings_duplicated_assets_title": "Dubleeritud üksused ({count})",
"cache_settings_statistics_album": "Kogu pisipildid",
"cache_settings_statistics_full": "Täismõõdus pildid",
@@ -1051,6 +1053,9 @@
"haptic_feedback_switch": "Luba haptiline tagasiside",
"haptic_feedback_title": "Haptiline tagasiside",
"has_quota": "On kvoot",
"hash_asset": "Arvuta üksuse räsi",
"hashed_assets": "Räsiga üksused",
"hashing": "Räsi arvutamine",
"header_settings_add_header_tip": "Lisa päis",
"header_settings_field_validator_msg": "Väärtus ei saa olla tühi",
"header_settings_header_name_input": "Päise nimi",
@@ -1083,6 +1088,7 @@
"host": "Host",
"hour": "Tund",
"id": "ID",
"idle": "Jõude",
"ignore_icloud_photos": "Ignoreeri iCloud fotosid",
"ignore_icloud_photos_description": "Fotosid, mis on iCloud'is, ei laadita üles Immich'i serverisse",
"image": "Pilt",
@@ -1165,7 +1171,9 @@
"list": "Loend",
"loading": "Laadimine",
"loading_search_results_failed": "Otsitulemuste laadimine ebaõnnestus",
"local": "Lokaalne üksus",
"local_asset_cast_failed": "Ei saa edastada üksust, mis pole serverisse üles laaditud",
"local_assets": "Lokaalsed üksused",
"local_network": "Kohalik võrk",
"local_network_sheet_info": "Rakendus ühendub valitud Wi-Fi võrgus olles serveriga selle URL-i kaudu",
"location_permission": "Asukoha luba",
@@ -1322,6 +1330,7 @@
"no_results": "Vasteid pole",
"no_results_description": "Proovi sünonüümi või üldisemat märksõna",
"no_shared_albums_message": "Lisa album, et fotosid ja videosid teistega jagada",
"no_uploads_in_progress": "Üleslaadimisi käimas ei ole",
"not_in_any_album": "Pole üheski albumis",
"not_selected": "Ei ole valitud",
"note_apply_storage_label_to_previously_uploaded assets": "Märkus: Et rakendada talletussilt varem üleslaaditud üksustele, käivita",
@@ -1359,6 +1368,7 @@
"original": "originaal",
"other": "Muud",
"other_devices": "Muud seadmed",
"other_entities": "Muud objektid",
"other_variables": "Muud muutujad",
"owned": "Minu omad",
"owner": "Omanik",
@@ -1519,6 +1529,8 @@
"refreshing_faces": "Nägude värskendamine",
"refreshing_metadata": "Metaandmete värskendamine",
"regenerating_thumbnails": "Pisipiltide uuesti genereerimine",
"remote": "Kaugüksus",
"remote_assets": "Kaugüksused",
"remove": "Eemalda",
"remove_assets_album_confirmation": "Kas oled kindel, et soovid {count, plural, one {# üksuse} other {# üksust}} albumist eemaldada?",
"remove_assets_shared_link_confirmation": "Kas oled kindel, et soovid eemaldada {count, plural, one {# üksuse} other {# üksust}} sellelt jagatud lingilt?",
@@ -1556,11 +1568,15 @@
"reset_password": "Lähtesta parool",
"reset_people_visibility": "Lähtesta isikute nähtavus",
"reset_pin_code": "Lähtesta PIN-kood",
"reset_sqlite": "Lähtesta SQLite andmebaas",
"reset_sqlite_confirmation": "Kas oled kindel, et soovid SQLite andmebaasi lähtestada? Andmete uuesti sünkroonimiseks pead välja ja jälle sisse logima",
"reset_sqlite_success": "SQLite andmebaas edukalt lähtestatud",
"reset_to_default": "Lähtesta",
"resolve_duplicates": "Lahenda duplikaadid",
"resolved_all_duplicates": "Kõik duplikaadid lahendatud",
"restore": "Taasta",
"restore_all": "Taasta kõik",
"restore_trash_action_prompt": "{count} prügikastust taastatud",
"restore_user": "Taasta kasutaja",
"restored_asset": "Üksus taastatud",
"resume": "Jätka",
@@ -1569,6 +1585,7 @@
"role": "Roll",
"role_editor": "Muutja",
"role_viewer": "Vaataja",
"running": "Käimas",
"save": "Salvesta",
"save_to_gallery": "Salvesta galeriisse",
"saved_api_key": "API võti salvestatud",
@@ -1822,6 +1839,7 @@
"storage_quota": "Talletuskvoot",
"storage_usage": "{used}/{available} kasutatud",
"submit": "Saada",
"success": "Õnnestus",
"suggestions": "Soovitused",
"sunrise_on_the_beach": "Päikesetõus rannal",
"support": "Tugi",
@@ -1831,6 +1849,8 @@
"sync": "Sünkrooni",
"sync_albums": "Sünkrooni albumid",
"sync_albums_manual_subtitle": "Sünkrooni kõik üleslaaditud videod ja fotod valitud varundusalbumitesse",
"sync_local": "Sünkrooni lokaalsed üksused",
"sync_remote": "Sünkrooni kaugüksused",
"sync_upload_album_setting_subtitle": "Loo ja laadi oma pildid ja videod üles Immich'isse valitud albumitesse",
"tag": "Silt",
"tag_assets": "Sildista üksuseid",
@@ -1841,6 +1861,7 @@
"tag_updated": "Muudetud silt: {tag}",
"tagged_assets": "{count, plural, one {# üksus} other {# üksust}} sildistatud",
"tags": "Sildid",
"tap_to_run_job": "Puuduta tööte käivitamiseks",
"template": "Mall",
"theme": "Teema",
"theme_selection": "Teema valik",

View File

@@ -406,6 +406,7 @@
"album_options": "Options de l'album",
"album_remove_user": "Supprimer l'utilisateur?",
"album_remove_user_confirmation": "Êtes-vous sûr de vouloir supprimer {user}?",
"album_search_not_found": "Aucun album trouvé ne correspond à votre recherche",
"album_share_no_users": "Il semble que vous ayez partagé cet album avec tous les utilisateurs ou que vous n'ayez aucun utilisateur avec lequel le partager.",
"album_updated": "Album mis à jour",
"album_updated_setting_description": "Recevoir une notification par courriel lorsqu'un album partagé a de nouveaux médias",
@@ -425,6 +426,7 @@
"albums_default_sort_order": "Ordre de tri par défaut des albums",
"albums_default_sort_order_description": "Ordre de tri des médias pour les nouveaux albums créés.",
"albums_feature_description": "Bibliothèques de médias pouvant être partagés avec d'autres utilisateurs.",
"albums_on_device_count": "Album sur l'appareil ({count})",
"all": "Tout",
"all_albums": "Tous les albums",
"all_people": "Toutes les personnes",
@@ -571,6 +573,8 @@
"backup_options_page_title": "Options de sauvegarde",
"backup_setting_subtitle": "Ajuster les paramètres d'envoi au premier et en arrière-plan",
"backward": "Arrière",
"beta_sync": "Statut de la synchronisation béta",
"beta_sync_subtitle": "Gérer le nouveau système de synchronisation",
"biometric_auth_enabled": "Authentification biométrique activée",
"biometric_locked_out": "L'authentification biométrique est verrouillé",
"biometric_no_options": "Aucune option biométrique disponible",
@@ -605,6 +609,7 @@
"cancel": "Annuler",
"cancel_search": "Annuler la recherche",
"canceled": "Annulé",
"canceling": "Annulation",
"cannot_merge_people": "Impossible de fusionner les personnes",
"cannot_undo_this_action": "Vous ne pouvez pas annuler cette action!",
"cannot_update_the_description": "Impossible de mettre à jour la description",
@@ -765,6 +770,7 @@
"description": "Description",
"description_input_hint_text": "Ajouter une description...",
"description_input_submit_error": "Erreur de mise à jour de la description, vérifier le journal pour plus de détails",
"deselect_all": "Tout désélectionner",
"details": "Détails",
"direction": "Ordre",
"disabled": "Désactivé",
@@ -839,6 +845,7 @@
"empty_trash": "Vider la corbeille",
"empty_trash_confirmation": "Êtes-vous sûr de vouloir vider la corbeille? Cela supprimera définitivement de Immich tous les médias qu'elle contient.\nVous ne pouvez pas annuler cette action!",
"enable": "Active",
"enable_backup": "Activer Backup",
"enable_biometric_auth_description": "Entrez votre code PIN pour activer l'authentification biométrique",
"enabled": "Activé",
"end_date": "Date de fin",
@@ -1046,6 +1053,9 @@
"haptic_feedback_switch": "Activer le retour haptique",
"haptic_feedback_title": "Retour haptique",
"has_quota": "Quota",
"hash_asset": "Hasher le média",
"hashed_assets": "Média hashés",
"hashing": "Hash",
"header_settings_add_header_tip": "Ajouter un en-tête",
"header_settings_field_validator_msg": "Cette valeur ne peut pas être vide",
"header_settings_header_name_input": "Nom de l'en-tête",
@@ -1160,7 +1170,9 @@
"list": "Liste",
"loading": "Chargement",
"loading_search_results_failed": "Chargement des résultats échoué",
"local": "Local",
"local_asset_cast_failed": "Impossible de caster un média qui n'a pas envoyé vers le serveur",
"local_assets": "Média locaux",
"local_network": "Réseau local",
"local_network_sheet_info": "L'application va se connecter au serveur via cette URL quand l'appareil est connecté à ce réseau Wi-Fi",
"location_permission": "Autorisation de localisation",
@@ -1317,6 +1329,7 @@
"no_results": "Aucun résultat",
"no_results_description": "Essayez un synonyme ou un mot-clé plus général",
"no_shared_albums_message": "Créer un album pour partager vos photos et vidéos avec les personnes de votre réseau",
"no_uploads_in_progress": "Pas d'envoi en cours",
"not_in_any_album": "Dans aucun album",
"not_selected": "Non sélectionné",
"note_apply_storage_label_to_previously_uploaded assets": "Note : Pour appliquer l'étiquette de stockage aux médias précédemment envoyés, exécutez",
@@ -1354,6 +1367,7 @@
"original": "original",
"other": "Autre",
"other_devices": "Autres appareils",
"other_entities": "Autres entités",
"other_variables": "Autres variables",
"owned": "Possédé",
"owner": "Propriétaire",
@@ -1485,6 +1499,7 @@
"purchase_server_description_2": "Statut de contributeur",
"purchase_server_title": "Serveur",
"purchase_settings_server_activated": "La clé du produit pour le Serveur est gérée par l'administrateur",
"queue_status": "File d'attente {count}/{total}",
"rating": "Étoile d'évaluation",
"rating_clear": "Effacer l'évaluation",
"rating_count": "{count, plural, one {# étoile} other {# étoiles}}",
@@ -1513,6 +1528,8 @@
"refreshing_faces": "Actualisation des visages",
"refreshing_metadata": "Actualisation des métadonnées",
"regenerating_thumbnails": "Regénération des miniatures",
"remote": "A distance",
"remote_assets": "Média à distance",
"remove": "Supprimer",
"remove_assets_album_confirmation": "Êtes-vous sûr de vouloir supprimer {count, plural, one {# média} other {# médias}} de l'album?",
"remove_assets_shared_link_confirmation": "Êtes-vous sûr de vouloir supprimer {count, plural, one {# média} other {# médias}} de ce lien partagé?",
@@ -1550,11 +1567,15 @@
"reset_password": "Réinitialiser le mot de passe",
"reset_people_visibility": "Réinitialiser la visibilité des personnes",
"reset_pin_code": "Réinitialiser le code PIN",
"reset_sqlite": "Réinitialiser la base de données SQLite",
"reset_sqlite_confirmation": "Êtes vous sur que vous voulez réinitialiser la base de données SQLite? Vous devrez vous déconnecter and vous reconnecter à nouveau pour re-synchroniser les données",
"reset_sqlite_success": "La base de données SQLite à été réinitialisé avec succès",
"reset_to_default": "Rétablir les valeurs par défaut",
"resolve_duplicates": "Résoudre les doublons",
"resolved_all_duplicates": "Résolution de tous les doublons",
"restore": "Restaurer",
"restore_all": "Tout restaurer",
"restore_trash_action_prompt": "{count} restauré de la corbeille",
"restore_user": "Restaurer l'utilisateur",
"restored_asset": "Média restauré",
"resume": "Reprendre",
@@ -1563,6 +1584,7 @@
"role": "Rôle",
"role_editor": "Éditeur",
"role_viewer": "Visionneuse",
"running": "En marche",
"save": "Sauvegarder",
"save_to_gallery": "Enregistrer",
"saved_api_key": "Clé API sauvegardée",
@@ -1816,6 +1838,7 @@
"storage_quota": "Quota de stockage",
"storage_usage": "{used} sur {available} utilisé",
"submit": "Soumettre",
"success": "Réussi",
"suggestions": "Suggestions",
"sunrise_on_the_beach": "Lever de soleil sur la plage",
"support": "Soutenir",
@@ -1825,6 +1848,8 @@
"sync": "Synchroniser",
"sync_albums": "Synchroniser dans des albums",
"sync_albums_manual_subtitle": "Synchroniser toutes les vidéos et photos envoyées dans les albums sélectionnés",
"sync_local": "Synchronisation locale",
"sync_remote": "Synchronisation à distance",
"sync_upload_album_setting_subtitle": "Créez et envoyez vos photos et vidéos dans les albums sélectionnés sur Immich",
"tag": "Étiquette",
"tag_assets": "Étiqueter les médias",
@@ -1835,6 +1860,7 @@
"tag_updated": "Étiquette mise à jour: {tag}",
"tagged_assets": "Étiquette ajoutée à {count, plural, one {# média} other {# médias}}",
"tags": "Étiquettes",
"tap_to_run_job": "Appuyez pour démarrer la tâche",
"template": "Modèle",
"theme": "Thème",
"theme_selection": "Sélection du thème",
@@ -1915,6 +1941,7 @@
"updated_password": "Mot de passe mis à jour",
"upload": "Envoyer",
"upload_concurrency": "Envois simultanés",
"upload_details": "Uploader les details",
"upload_dialog_info": "Voulez-vous sauvegarder la sélection vers le serveur?",
"upload_dialog_title": "Envoyer le média",
"upload_errors": "L'envoi s'est complété avec {count, plural, one {# erreur} other {# erreurs}}. Rafraîchissez la page pour voir les nouveaux médias envoyés.",
@@ -1965,6 +1992,7 @@
"view_album": "Afficher l'album",
"view_all": "Voir tout",
"view_all_users": "Voir tous les utilisateurs",
"view_details": "Voir les détails",
"view_in_timeline": "Voir dans la vue chronologique",
"view_link": "Voir le lien",
"view_links": "Voir les liens",

View File

@@ -140,7 +140,7 @@
"machine_learning_smart_search_description": "Képek szemantikai keresése CLIP beágyazások segítségével",
"machine_learning_smart_search_enabled": "Okos keresés engedélyezése",
"machine_learning_smart_search_enabled_description": "Ha ki van kapcsolva, a képek nem lesznek átalakítva okos kereséshez.",
"machine_learning_url_description": "Gépi tanulás szerver URL címe. Ha többi, mint egy URL van megadva, mindegyik szervert egyenként próbálja meg, amíg az egyik sikeresen nem válaszol, sorrendben az elsőtől az utólsóig. A nem válaszoló szervereket átmenetileg figyelmen kívül hagyja, amíg újra online nem lesznek.",
"machine_learning_url_description": "Gépi tanulás szerver URL címe. Ha többi, mint egy URL van megadva, mindegyik szervert egyenként próbálja meg, amíg az egyik sikeresen nem válaszol, sorrendben az elsőtől az utólsóig. A nem elérhető szervereket átmenetileg figyelmen kívül lesznek hagyva, amíg újra online nem lesznek.",
"manage_concurrency": "Párhuzamos Feladatok Kezelése",
"manage_log_settings": "Naplózási beállítások kezelése",
"map_dark_style": "Sötét stílus",
@@ -166,6 +166,12 @@
"metadata_settings_description": "Metaadat beállítások kezelése",
"migration_job": "Migrálás",
"migration_job_description": "Az elemek és arcok bélyegképeinek migrálása a legújabb mappastruktúrába",
"nightly_tasks_cluster_faces_setting_description": "Arcfelismerés futtatása az újonnan érzékelt arcokon",
"nightly_tasks_database_cleanup_setting": "Adatbázis-tisztítási feladatok",
"nightly_tasks_database_cleanup_setting_description": "A régi, lejárt adatok törlése az adatbázisból",
"nightly_tasks_generate_memories_setting": "Emlékek generálása",
"nightly_tasks_generate_memories_setting_description": "Új emlékek létrehozása elemekből",
"nightly_tasks_missing_thumbnails_setting": "Hiányzó indexképek generálása",
"no_paths_added": "Nincs megadva elérési útvonal",
"no_pattern_added": "Nincs megadva minta (pattern)",
"note_apply_storage_label_previous_assets": "Megjegyzés: Ha a korábban feltöltött elemekhez is szeretne Tárhely Címkéket társítani, akkor futtassa ezt",
@@ -360,7 +366,7 @@
"advanced_settings_enable_alternate_media_filter_subtitle": "Ezzel a beállítással a szinkronizálás során alternatív kritériumok alapján szűrheted a fájlokat. Csak akkor próbáld ki, ha problémáid vannak azzal, hogy az alkalmazás nem ismeri fel az összes albumot.",
"advanced_settings_enable_alternate_media_filter_title": "[KÍSÉRLETI] Alternatív eszköz album szinkronizálási szűrő használata",
"advanced_settings_log_level_title": "Naplózás szintje: {level}",
"advanced_settings_prefer_remote_subtitle": "Néhány eszköz fájdalmasan lassan tölti be az eszközön lévő bélyegképeket. Ez a beállítás inkább a távoli képeket tölti be helyettük.",
"advanced_settings_prefer_remote_subtitle": "Néhány eszköz fájdalmasan lassan tölti be az eszközön lévő indexképeket. Ez a beállítás inkább a távoli képeket (a szerverről) tölti be helyettük.",
"advanced_settings_prefer_remote_title": "Távoli képek előnyben részesítése",
"advanced_settings_proxy_headers_subtitle": "Add meg azokat a proxy fejléceket, amiket az app elküldjön minden hálózati kérésnél",
"advanced_settings_proxy_headers_title": "Proxy Fejlécek",
@@ -517,7 +523,7 @@
"backup_controller_page_background_is_on": "Automatikus mentés a háttérben be van kapcsolva",
"backup_controller_page_background_turn_off": "Háttérszolgáltatás kikapcsolása",
"backup_controller_page_background_turn_on": "Háttérszolgáltatás bekapcsolása",
"backup_controller_page_background_wifi": "Csak WiFi-n",
"backup_controller_page_background_wifi": "Csak Wi-Fi-n",
"backup_controller_page_backup": "Mentés",
"backup_controller_page_backup_selected": "Kiválasztva: ",
"backup_controller_page_backup_sub": "Mentett fotók és videók",
@@ -551,8 +557,8 @@
"backup_setting_subtitle": "A háttérben és előtérben mentés beállításainak kezelése",
"backward": "Visszafele",
"biometric_auth_enabled": "Biometrikus azonosítás engedélyezve",
"biometric_locked_out": "A biometrikus azonosításból kizárva",
"biometric_no_options": "A biometrikus azonosítás nem elérhető",
"biometric_locked_out": "Ki vagy zárva a biometrikus azonosításból",
"biometric_no_options": "Nincsen elérhető biometrikus azonosítás",
"biometric_not_available": "Biometrikus azonosítás ezen az eszközön nem elérhető",
"birthdate_saved": "Születésnap elmentve",
"birthdate_set_description": "A születés napját a rendszer arra használja, hogy kiírja, hogy a fénykép készítésekor a személy hány éves volt.",
@@ -642,7 +648,7 @@
"confirm_keep_this_delete_others": "Minden más elem a készletben törlésre kerül, kivéve ezt az elemet. Biztosan folytatni szeretnéd?",
"confirm_new_pin_code": "Új PIN kód megerősítése",
"confirm_password": "Jelszó megerősítése",
"confirm_tag_face": "Szeretnéd ezt az arcot {name}-ként megjelölni?",
"confirm_tag_face": "Szeretnéd ezt az arcot {name}-nak/nek megjelölni?",
"confirm_tag_face_unnamed": "Szeretnéd ezt az arcot megjelölni?",
"connected_device": "Kapcsolt eszköz",
"connected_to": "Kapcsolódva",
@@ -770,7 +776,7 @@
"download_started": "Letöltés megkezdve",
"download_sucess": "Sikeres letöltés",
"download_sucess_android": "Média letöltve a DCIM/Immich mappába",
"download_waiting_to_retry": "Várakozás újrapróbálkozáshoz",
"download_waiting_to_retry": "Várás az újrapróbálkozásra",
"downloading": "Letöltés",
"downloading_asset_filename": "{filename} elem letöltése",
"downloading_media": "Média letöltése",
@@ -816,7 +822,7 @@
"enqueued": "Sorba állítva",
"enter_wifi_name": "Add meg a Wi-Fi hálózat nevét",
"enter_your_pin_code": "Add meg a jelszavad",
"enter_your_pin_code_subtitle": "Add meg a jelszavad a zárolt mappa megnyitásához",
"enter_your_pin_code_subtitle": "Add meg a PIN kódodat a zárolt mappa megnyitásához",
"error": "Hiba",
"error_change_sort_album": "Album sorbarendezésének megváltoztatása sikertelen",
"error_delete_face": "Hiba az arc törlése során",
@@ -916,7 +922,7 @@
"unable_to_remove_partner": "Partner eltávolítása sikertelen",
"unable_to_remove_reaction": "Reakció eltávolítása sikertelen",
"unable_to_reset_password": "Jelszó visszaállítása sikertelen",
"unable_to_reset_pin_code": "Jelszó visszaállítása sikertelen",
"unable_to_reset_pin_code": "PIN kód visszaállítása sikertelen",
"unable_to_resolve_duplicate": "Duplikátum feloldása sikertelen",
"unable_to_restore_assets": "Elemek visszaállítása sikertelen",
"unable_to_restore_trash": "Az összes elem visszaállítása sikertelen",
@@ -988,7 +994,7 @@
"filetype": "Fájltípus",
"filter": "Szűrő",
"filter_people": "Személyek szűrése",
"filter_places": "Helyek szűrése",
"filter_places": "Helyszínek szűrése",
"find_them_fast": "Név alapján kereséssel gyorsan megtalálhatóak",
"fix_incorrect_match": "Hibás találat javítása",
"folder": "Mappa",
@@ -1029,9 +1035,9 @@
"hide_person": "Személy elrejtése",
"hide_unnamed_people": "Név nélküli személyek elrejtése",
"home_page_add_to_album_conflicts": "{added} elem hozzáadva a(z) \"{album}\" albumhoz. {failed} elem már eleve az albumban volt.",
"home_page_add_to_album_err_local": "Helyi elemeket még nem lehet albumba tenni, kihagyás",
"home_page_add_to_album_err_local": "Helyi elemeket még nem lehet albumba tenni, ki lesznek hagyva",
"home_page_add_to_album_success": "{added} elem hozzáadva a(z) \"{album}\" albumhoz.",
"home_page_album_err_partner": "Még nem lehet a partner elemeit albumokhoz adni, kihagyás",
"home_page_album_err_partner": "Még nem lehet a partner elemeit albumokhoz adni, ki lesznek hagyva",
"home_page_archive_err_local": "Helyi elemek archiválása még nem támogatott, úgyhogy kihagyjuk",
"home_page_archive_err_partner": "Partner elemeit nem lehet archiválni, úgyhogy kihagyjuk",
"home_page_building_timeline": "Idővonal összeállítása",
@@ -1040,7 +1046,7 @@
"home_page_favorite_err_local": "Helyi elemeket még nem lehet a kedvencek közé tenni, úgyhogy ezeket kihagyjuk",
"home_page_favorite_err_partner": "Partner elemeit még nem lehet a kedvencek közé tenni, úgyhogy ezeket kihagyjuk",
"home_page_first_time_notice": "Ha most használod először az alkalmazást, a fotók és videók megjelenítéséhez az idővonaladon, állítsd be, hogy melyik albumaidról készüljön biztonsági mentés",
"home_page_locked_error_local": "Helyi elemek nem mozgathatóak a zárolt mappába, átugorva",
"home_page_locked_error_local": "A Helyi elemek nem mozgathatóak a zárolt mappába, ki lesznek hagyva",
"home_page_locked_error_partner": "Partner elemek nem mozgathatóak a zárolt mappába, átugorva",
"home_page_share_err_local": "Helyi elemekről nem lehet megosztott linket készíteni, úgyhogy kihagyjuk",
"home_page_upload_err_limit": "Csak 30 elemet tudsz egyszerre feltölteni, úgyhogy kihagyjuk",
@@ -1087,10 +1093,10 @@
"invite_people": "Személyek Meghívása",
"invite_to_album": "Meghívás az albumba",
"ios_debug_info_last_sync_at": "Utoljára szinkronizálva {dateTime}",
"ios_debug_info_no_processes_queued": "Nincs sorba állított hátterfolyamat",
"ios_debug_info_no_processes_queued": "Nincs a sorban háttérfolyamat jelenleg",
"ios_debug_info_no_sync_yet": "Még nem futott szinkronizáló háttérfolyamat",
"ios_debug_info_processes_queued": "{count, plural, one {{count} háttérfolyamat előkészítve} other {{count} háttérfolyamat előkészítve}}",
"ios_debug_info_processing_ran_at": "Feldolgozás futott {dateTime}",
"ios_debug_info_processing_ran_at": "A feldolgozás ekkor futott: {dateTime}",
"items_count": "{count, plural, other {# elem}}",
"jobs": "Feladatok",
"keep": "Megtart",
@@ -1099,7 +1105,7 @@
"kept_this_deleted_others": "Ez az elem és a töröltek meg lettek hagyva {count, plural, one {# asset} other {# assets}}",
"keyboard_shortcuts": "Billentyűparancsok",
"language": "Nyelv",
"language_no_results_subtitle": "Próbáld a keresésed módosítását",
"language_no_results_subtitle": "Próbáld módosítani a szavaidat a keresésnél",
"language_search_hint": "Nyelvek keresése...",
"language_setting_description": "Válaszd ki preferált nyelvet",
"last_seen": "Utoljára láttuk",
@@ -1129,7 +1135,7 @@
"local_network": "Helyi hálózat",
"local_network_sheet_info": "Az alkalmazés ezen az URL címen fogja elérni a szervert, ha a megadott WiFi hálózathoz van csatlankozva",
"location_permission": "Helymeghatározási engedély",
"location_permission_content": "Hálózatok automatikus váltásához az Immich-nek szüksége van a pontos helymeghatározásra, hogy az alkalmazás le tudja kérni a Wi-Fi hálózat nevét",
"location_permission_content": "A Hálózatok automatikus váltásához az Immich-nek szüksége van a pontos helymeghatározásra, hogy az alkalmazás le tudja kérni a Wi-Fi hálózat nevét",
"location_picker_choose_on_map": "Válassz a térképen",
"location_picker_latitude_error": "Érvényes szélességi kört írj be",
"location_picker_latitude_hint": "Ide írd a szélességi kört",
@@ -1139,7 +1145,7 @@
"locked_folder": "Zárolt mappa",
"log_out": "Kijelentkezés",
"log_out_all_devices": "Kijelentkezés Minden Eszközön",
"logged_in_as": "{user}-ként belépve",
"logged_in_as": "Belépve: {user} néven",
"logged_out_all_devices": "Minden eszköz kijelentkeztetve",
"logged_out_device": "Eszköz kijelentkeztetve",
"login": "Bejelentkezés",
@@ -1234,9 +1240,9 @@
"monthly_title_text_date_format": "y MMMM",
"more": "Továbbiak",
"move": "Áthelyezés",
"move_off_locked_folder": "Zárolt mappából kivonás",
"move_off_locked_folder": "Átmozgatás a zárolt mappából",
"move_to_locked_folder": "Áthelyezés a zárolt mappába",
"move_to_locked_folder_confirmation": "Ezek a képek és videók az összes albumból kikerülnek, és csak a zárolt mappából lesznek elérhetőek",
"move_to_locked_folder_confirmation": "Ezek a képek és videók az összes albumból kikerülnek, és csak a zárolt mappában lesznek elérhetőek",
"moved_to_archive": "{count, plural, one {# Elem} other {# Elemek}} archiválva",
"moved_to_library": "{count, plural, one {# Elem} other {# Elemek}} másik könyvtárba költöztetve",
"moved_to_trash": "Áthelyezve a lomtárba",
@@ -1254,7 +1260,7 @@
"new_password": "Új jelszó",
"new_person": "Új személy",
"new_pin_code": "Új PIN kód",
"new_pin_code_subtitle": "Ez az első alkalom hogy megnyitod a zárolt mappát. Hozz létre egy jelszót az oldal biztosítására",
"new_pin_code_subtitle": "Ez az első alkalom hogy megnyitod a zárolt mappát. Hozz létre egy jelszót a mappa biztonságos eléréséhez",
"new_user_created": "Új felhasználó létrehozva",
"new_version_available": "ÚJ VERZIÓ ÉRHETŐ EL",
"newest_first": "Legújabb először",
@@ -1283,7 +1289,7 @@
"not_selected": "Nincs kiválasztva",
"note_apply_storage_label_to_previously_uploaded assets": "Megjegyzés: a korábban feltöltött elemek Tárhely Címkézéséhez futtasd a(z)",
"notes": "Megjegyzések",
"nothing_here_yet": "Itt még nincs semmi",
"nothing_here_yet": "Még semmi sincs itt",
"notification_permission_dialog_content": "Az értesítések bekapcsolásához a Beállítások menüben válaszd ki az Engedélyezés-t.",
"notification_permission_list_tile_content": "Értesítések engedélyezése.",
"notification_permission_list_tile_enable_button": "Értesítések Bekapcsolása",
@@ -1293,7 +1299,7 @@
"notifications_setting_description": "Értesítések kezelése",
"oauth": "OAuth",
"official_immich_resources": "Hivatalos Immich Források",
"offline": "Offline",
"offline": "Nem elérhető (offline)",
"ok": "Rendben",
"oldest_first": "Legrégebbi először",
"on_this_device": "Ezen az eszközön",
@@ -1303,7 +1309,7 @@
"onboarding_theme_description": "Válassz egy színtémát. Ezt bármikor megváltoztathatod a beállításokban.",
"onboarding_user_welcome_description": "Kezdjünk bele!",
"onboarding_welcome_user": "Üdvözöllek {user}",
"online": "Online",
"online": "Online (elérhető)",
"only_favorites": "Csak kedvencek",
"open": "Nyitva",
"open_in_map_view": "Megnyitás térkép nézetben",
@@ -1754,7 +1760,7 @@
"stack_select_one_photo": "Válassz egy fő képet a csoportból",
"stack_selected_photos": "Kiválasztott fényképek csoportosítása",
"stacked_assets_count": "{count, plural, other {# elem}} csoportosítva",
"stacktrace": "Hibaleírás",
"stacktrace": "Hiba leírása",
"start": "Elindít",
"start_date": "Kezdő dátum",
"state": "Megye/Állam",
@@ -1882,7 +1888,7 @@
"user_liked": "{user} felhasználónak {type, select, photo {ez a fénykép} video {ez a videó} asset {ez az elem} other {ez}} tetszik",
"user_pin_code_settings": "PIN kód",
"user_pin_code_settings_description": "PIN kód kezelése",
"user_privacy": "Felhasználói biztonság",
"user_privacy": "Felhasználói adatvédelem",
"user_purchase_settings": "Megvásárlás",
"user_purchase_settings_description": "Vásárlás kezelése",
"user_role_set": "{user} felhasználónak {role} jogkör biztosítása",

View File

@@ -169,15 +169,23 @@
"nightly_tasks_cluster_faces_setting_description": "Mulai pengenalan wajah pada semua wajah yang baru saja terdeteksi",
"nightly_tasks_cluster_new_faces_setting": "Kelompokkan semua wajah baru",
"nightly_tasks_database_cleanup_setting": "Tugas pembersihan basis data",
"nightly_tasks_database_cleanup_setting_description": "Membersihkan data lama, kadaluarsa dari database",
"nightly_tasks_generate_memories_setting": "Buat kenang-kenangan",
"nightly_tasks_generate_memories_setting_description": "Buat kenang-kenangan baru dari berbagai aset",
"nightly_tasks_missing_thumbnails_setting": "Membuat thumbnail yang hilang",
"nightly_tasks_missing_thumbnails_setting_description": "Mengantrikan aset tanpa thumbnail untuk pembuatan thumbnail",
"nightly_tasks_settings": "Pengaturan Tugas Malam",
"nightly_tasks_settings_description": "Atur tugas malam",
"nightly_tasks_start_time_setting": "Waktu mulai",
"nightly_tasks_start_time_setting_description": "Waktu saat server mulai menjalankan tugas malam",
"nightly_tasks_sync_quota_usage_setting": "Sinkronisasi penggunaan kuota",
"nightly_tasks_sync_quota_usage_setting_description": "Pembaruan kuota penyimpanan pengguna, berdasarkan penggunaan sekarang",
"no_paths_added": "Tidak ada jalur yang ditambahkan",
"no_pattern_added": "Tidak ada pola yang ditambahkan",
"note_apply_storage_label_previous_assets": "Catatan: Untuk menerapkan Label Penyimpanan untuk aset yang telah diunggah sebelumnya, jalankan",
"note_cannot_be_changed_later": "CATATAN: Ini tidak akan dapat diubah lagi!",
"notification_email_from_address": "Dari alamat",
"notification_email_from_address_description": "Alamat surel pengirim, misalnya: \"Server Foto Immich <noreply@example.com>\". Pastikan untuk menggunakan alamat yang diizinkan untuk mengirim email",
"notification_email_from_address_description": "Alamat surel pengirim, misalnya: \"Server Foto Immich <noreply@example.com>\". Pastikan untuk menggunakan alamat yang diizinkan untuk mengirim email.",
"notification_email_host_description": "Hos server surel (mis. smtp.immich.app)",
"notification_email_ignore_certificate_errors": "Abaikan eror sertifikat",
"notification_email_ignore_certificate_errors_description": "Abaikan eror validasi sertifikat TLS (tidak disarankan)",

View File

@@ -406,6 +406,7 @@
"album_options": "Impostazioni Album",
"album_remove_user": "Rimuovi l'utente?",
"album_remove_user_confirmation": "Sicuro di voler rimuovere l'utente {user}?",
"album_search_not_found": "Nessun album trovato corrispondente alla tua ricerca",
"album_share_no_users": "Sembra che tu abbia condiviso questo album con tutti gli utenti oppure non hai nessun utente con cui condividere.",
"album_updated": "Album aggiornato",
"album_updated_setting_description": "Ricevi una notifica email quando un album condiviso ha nuovi media",
@@ -425,6 +426,7 @@
"albums_default_sort_order": "Ordinamento predefinito degli album",
"albums_default_sort_order_description": "Ordine iniziale degli elementi alla creazione di nuovi album.",
"albums_feature_description": "Raggruppamento di elementi che possono essere condivisi con altri utenti.",
"albums_on_device_count": "Album sul dispositivo ({count})",
"all": "Tutti",
"all_albums": "Tutti gli album",
"all_people": "Tutte le persone",
@@ -605,6 +607,7 @@
"cancel": "Annulla",
"cancel_search": "Annulla ricerca",
"canceled": "Annullato",
"canceling": "Annullamento",
"cannot_merge_people": "Impossibile unire le persone",
"cannot_undo_this_action": "Non puoi annullare questa azione!",
"cannot_update_the_description": "Impossibile aggiornare la descrizione",
@@ -751,6 +754,7 @@
"delete_key": "Elimina chiave",
"delete_library": "Elimina libreria",
"delete_link": "Elimina link",
"delete_local_action_prompt": "{count} elementi rimossi in locale",
"delete_local_dialog_ok_backed_up_only": "Elimina solo con backup",
"delete_local_dialog_ok_force": "Elimina comunque",
"delete_others": "Elimina gli altri",
@@ -764,6 +768,7 @@
"description": "Descrizione",
"description_input_hint_text": "Aggiungi descrizione...",
"description_input_submit_error": "Errore modificare descrizione, controlli I log per maggiori dettagli",
"deselect_all": "Deseleziona Tutto",
"details": "Dettagli",
"direction": "Direzione",
"disabled": "Disabilitato",
@@ -781,6 +786,7 @@
"documentation": "Documentazione",
"done": "Fatto",
"download": "Scarica",
"download_action_prompt": "Scaricando {count} elementi",
"download_canceled": "Download annullato",
"download_complete": "Download completato",
"download_enqueue": "Download in coda",
@@ -837,6 +843,7 @@
"empty_trash": "Svuota cestino",
"empty_trash_confirmation": "Sei sicuro di volere svuotare il cestino? Questo rimuoverà tutte le risorse nel cestino in modo permanente da Immich.\nNon puoi annullare questa azione!",
"enable": "Abilita",
"enable_backup": "Abilita Backup",
"enable_biometric_auth_description": "Inserire il codice PIN per abilitare l'autenticazione biometrica",
"enabled": "Abilitato",
"end_date": "Data Fine",
@@ -1483,6 +1490,7 @@
"purchase_server_description_2": "Stato di Contributore",
"purchase_server_title": "Server",
"purchase_settings_server_activated": "La chiave del prodotto del server è gestita dall'amministratore",
"queue_status": "Messi in coda {count}/{total}",
"rating": "Valutazione a stelle",
"rating_clear": "Crea valutazione",
"rating_count": "{count, plural, one {# stella} other {# stelle}}",
@@ -1518,6 +1526,7 @@
"remove_custom_date_range": "Rimuovi intervallo data personalizzato",
"remove_deleted_assets": "Rimuovi file offline",
"remove_from_album": "Rimuovere dall'album",
"remove_from_album_action_prompt": "{count} elementi rimossi dall'album",
"remove_from_favorites": "Rimuovi dai preferiti",
"remove_from_lock_folder_action_prompt": "{count} elementi rimossi dalla cartella sicura",
"remove_from_locked_folder": "Rimuovi dalla cartella privata",
@@ -1691,6 +1700,7 @@
"settings_saved": "Impostazioni salvate",
"setup_pin_code": "Configura un codice PIN",
"share": "Condivisione",
"share_action_prompt": "Condivisi {count} elementi",
"share_add_photos": "Aggiungi foto",
"share_assets_selected": "{count} selezionati",
"share_dialog_preparing": "Preparo…",
@@ -1792,6 +1802,7 @@
"sort_title": "Titolo",
"source": "Fonte",
"stack": "Raggruppa",
"stack_action_prompt": "{count} elementi raggruppati",
"stack_duplicates": "Raggruppa i duplicati",
"stack_select_one_photo": "Seleziona una foto principale per il gruppo",
"stack_selected_photos": "Impila foto selezionate",
@@ -1880,6 +1891,7 @@
"unable_to_change_pin_code": "Impossibile cambiare il codice PIN",
"unable_to_setup_pin_code": "Impossibile configurare il codice PIN",
"unarchive": "Annulla l'archiviazione",
"unarchive_action_prompt": "{count} elementi rimossi dall'Archivio",
"unarchived_count": "{count, plural, other {Non archiviati #}}",
"undo": "Annulla",
"unfavorite": "Rimuovi preferito",

View File

@@ -707,6 +707,9 @@
"next_memory": "Nākamā atmiņa",
"no": "Nē",
"no_albums_message": "Izveido albumu, lai organizētu savas fotogrāfijas un video",
"no_albums_with_name_yet": "Izskatās, ka tev vēl nav albumu ar šādu nosaukumu.",
"no_albums_yet": "Izskatās, ka tev vēl nav neviena albuma.",
"no_archived_assets_message": "Arhivē fotoattēlus un videoklipus, lai paslēptu tos no Fotoattēli skata",
"no_assets_message": "NOKLIKŠĶINIET, LAI AUGŠUPIELĀDĒTU SAVU PIRMO FOTOATTĒLU",
"no_assets_to_show": "Nav uzrādāmo aktīvu",
"no_duplicates_found": "Dublikāti netika atrasti.",
@@ -730,6 +733,10 @@
"official_immich_resources": "Oficiālie Immich resursi",
"offline": "Bezsaistē",
"ok": "Labi",
"onboarding": "Uzņemšana",
"onboarding_locale_description": "Izvēlies vēlamo valodu. To vēlāk var mainīt iestatījumos.",
"onboarding_theme_description": "Izvēlies savas instances krāsu motīvu. To vēlāk var mainīt iestatījumos.",
"onboarding_user_welcome_description": "Sāksim darbu!",
"online": "Tiešsaistē",
"only_favorites": "Tikai izlase",
"open_in_map_view": "Atvērt kartes skatā",
@@ -737,13 +744,16 @@
"open_the_search_filters": "Atvērt meklēšanas filtrus",
"options": "Iestatījumi",
"or": "vai",
"organize_your_library": "Bibliotēkas organizēšana",
"original": "oriģināls",
"other": "Citi",
"other_devices": "Citas ierīces",
"other_variables": "Citi mainīgie",
"owned": "Īpašumā",
"owner": "Īpašnieks",
"partner": "Partneris",
"partner_can_access": "{partner} var piekļūt",
"partner_can_access_location": "Fotogrāfiju uzņemšanas vieta",
"partner_list_user_photos": "{user} fotoattēli",
"partner_list_view_all": "Apskatīt visu",
"partner_page_empty_message": "Jūsu fotogrāfijas pagaidām nav kopīgotas ar nevienu partneri.",
@@ -757,6 +767,7 @@
"password_does_not_match": "Parole nesakrīt",
"path": "Ceļš",
"pause": "Pauzēt",
"pause_memories": "Pauzēt atmiņas",
"paused": "Nopauzēts",
"people": "Cilvēki",
"permission_onboarding_back": "Atpakaļ",

View File

@@ -573,6 +573,8 @@
"backup_options_page_title": "Backupinnstillinger",
"backup_setting_subtitle": "Administrer opplastingsinnstillinger for bakgrunn og forgrunn",
"backward": "Bakover",
"beta_sync": "Beta synkroniseringsstatus",
"beta_sync_subtitle": "Håndter det nye synkroniseringssystemet",
"biometric_auth_enabled": "Biometrisk autentisering aktivert",
"biometric_locked_out": "Du er låst ute av biometrisk verifisering",
"biometric_no_options": "Ingen biometriske valg tilgjengelige",
@@ -590,7 +592,7 @@
"cache_settings_clear_cache_button": "Tøm buffer",
"cache_settings_clear_cache_button_title": "Tømmer app-ens buffer. Dette vil ha betydelig innvirkning på appens ytelse inntil bufferen er gjenoppbygd.",
"cache_settings_duplicated_assets_clear_button": "TØM",
"cache_settings_duplicated_assets_subtitle": "Bilder og videoer som er svartelistet av app'en",
"cache_settings_duplicated_assets_subtitle": "Bilder og videoer som er ignorert av app'en",
"cache_settings_duplicated_assets_title": "Dupliserte objekter ({count})",
"cache_settings_statistics_album": "Bibliotekminiatyrbilder",
"cache_settings_statistics_full": "Originalbilder",
@@ -1051,6 +1053,9 @@
"haptic_feedback_switch": "Aktivert haptisk tilbakemelding",
"haptic_feedback_title": "Haptisk tilbakemelding",
"has_quota": "Kvote",
"hash_asset": "Hash objekter",
"hashed_assets": "Hashede objekter",
"hashing": "Hasher",
"header_settings_add_header_tip": "Legg til header",
"header_settings_field_validator_msg": "Verdi kan ikke være null",
"header_settings_header_name_input": "Header navn",
@@ -1083,6 +1088,7 @@
"host": "Vert",
"hour": "Time",
"id": "ID",
"idle": "Uvirksom",
"ignore_icloud_photos": "Ignorer iCloud bilder",
"ignore_icloud_photos_description": "Bilder som er lagret på iCloud vil ikke lastes opp til Immich",
"image": "Bilde",
@@ -1165,7 +1171,9 @@
"list": "Liste",
"loading": "Laster",
"loading_search_results_failed": "Klarte ikke å laste inn søkeresultater",
"local": "Lokal",
"local_asset_cast_failed": "Kan ikke caste et bilde som ikke er lastet opp til serveren",
"local_assets": "Lokale objekter",
"local_network": "Lokalt nettverk",
"local_network_sheet_info": "Appen vil koble til serveren via denne URL-en når du bruker det angitte Wi-Fi-nettverket",
"location_permission": "Stedstillatelse",
@@ -1322,6 +1330,7 @@
"no_results": "Ingen resultater",
"no_results_description": "Prøv et synonym eller mer generelt søkeord",
"no_shared_albums_message": "Opprett et album for å dele bilder og videoer med personer i nettverket ditt",
"no_uploads_in_progress": "Ingen opplasting pågår",
"not_in_any_album": "Ikke i noe album",
"not_selected": "Ikke valgt",
"note_apply_storage_label_to_previously_uploaded assets": "Merk: For å bruke lagringsetiketten på tidligere opplastede filer, kjør",
@@ -1359,6 +1368,7 @@
"original": "original",
"other": "Annet",
"other_devices": "Andre enheter",
"other_entities": "Andre objekter",
"other_variables": "Andre variabler",
"owned": "Dine",
"owner": "Eier",
@@ -1519,6 +1529,8 @@
"refreshing_faces": "Oppdaterer ansikter",
"refreshing_metadata": "Oppdaterer matadata",
"regenerating_thumbnails": "Regenererer miniatyrbilder",
"remote": "Eksternt",
"remote_assets": "Eksterne objekter",
"remove": "Fjern",
"remove_assets_album_confirmation": "Er du sikker på at du fil slette {count, plural, one {# asset} other {# assets}} fra albumet?",
"remove_assets_shared_link_confirmation": "Er du sikker på at du vil slette {count, plural, one {# asset} other {# assets}} fra den delte lenken?",
@@ -1556,11 +1568,15 @@
"reset_password": "Tilbakestill passord",
"reset_people_visibility": "Tilbakestill personsynlighet",
"reset_pin_code": "Resett PINkode",
"reset_sqlite": "Reset SQLite Databasen",
"reset_sqlite_confirmation": "Er du sikker på at du vil resette SQLite databasen? Du blir nødt til å logge ut og inn igjen for å resynkronisere data",
"reset_sqlite_success": "Vellykket resetting av SQLite databasen",
"reset_to_default": "Tilbakestill til standard",
"resolve_duplicates": "Løs duplikater",
"resolved_all_duplicates": "Løste alle duplikater",
"restore": "Gjenopprett",
"restore_all": "Gjenopprett alle",
"restore_trash_action_prompt": "{count} gjenopprettet fra søppelbøtten",
"restore_user": "Gjenopprett bruker",
"restored_asset": "Gjenopprettet ressurs",
"resume": "Fortsett",
@@ -1569,6 +1585,7 @@
"role": "Rolle",
"role_editor": "Redigerer",
"role_viewer": "Visning",
"running": "Kjører",
"save": "Lagre",
"save_to_gallery": "Lagre til galleriet",
"saved_api_key": "Lagret API-nøkkel",
@@ -1822,6 +1839,7 @@
"storage_quota": "Lagringsplass",
"storage_usage": "{used} av {available} brukt",
"submit": "Send inn",
"success": "Vellykket",
"suggestions": "Forslag",
"sunrise_on_the_beach": "Soloppgang på stranden",
"support": "Støtte",
@@ -1831,6 +1849,8 @@
"sync": "Synkroniser",
"sync_albums": "Synkroniser albumer",
"sync_albums_manual_subtitle": "Synkroniser alle opplastede videoer og bilder til det valgte backupalbumet",
"sync_local": "Synkroniser lokalt",
"sync_remote": "Synkroniser eksternt",
"sync_upload_album_setting_subtitle": "Opprett og last opp dine bilder og videoer til det valgte albumet på Immich",
"tag": "Tagg",
"tag_assets": "Merk ressurser",
@@ -1841,6 +1861,7 @@
"tag_updated": "Oppdater merke: {tag}",
"tagged_assets": "Merket {count, plural, one {# asset} other {# assets}}",
"tags": "Merker",
"tap_to_run_job": "Trykk for å kjøre jobben",
"template": "Mal",
"theme": "Tema",
"theme_selection": "Temavalg",

View File

@@ -426,6 +426,7 @@
"albums_default_sort_order": "Standaard sorteervolgorde album",
"albums_default_sort_order_description": "Initiële sorteervolgorde bij het maken van nieuwe albums.",
"albums_feature_description": "Collectie van assets die je kan delen met andere gebruikers.",
"albums_on_device_count": "Albums op apparaat ({count})",
"all": "Alle",
"all_albums": "Alle albums",
"all_people": "Alle mensen",
@@ -572,6 +573,8 @@
"backup_options_page_title": "Back-up instellingen",
"backup_setting_subtitle": "Beheer achtergrond en voorgrond uploadinstellingen",
"backward": "Achteruit",
"beta_sync": "Beta Sync Status",
"beta_sync_subtitle": "Beheer het nieuwe synchronisatiesysteem",
"biometric_auth_enabled": "Biometrische authenticatie ingeschakeld",
"biometric_locked_out": "Biometrische authenticatie is vergrendeld",
"biometric_no_options": "Geen biometrische opties beschikbaar",
@@ -589,7 +592,7 @@
"cache_settings_clear_cache_button": "Cache wissen",
"cache_settings_clear_cache_button_title": "Wist de cache van de app. Dit zal de presentaties van de app aanzienlijk beïnvloeden totdat de cache opnieuw is opgebouwd.",
"cache_settings_duplicated_assets_clear_button": "MAAK VRIJ",
"cache_settings_duplicated_assets_subtitle": "Foto's en video's op de zwarte lijst van de app",
"cache_settings_duplicated_assets_subtitle": "Fotos en video's die de app negeert",
"cache_settings_duplicated_assets_title": "Gedupliceerde assets ({count})",
"cache_settings_statistics_album": "Bibliotheekthumbnails",
"cache_settings_statistics_full": "Volledige afbeeldingen",
@@ -1050,6 +1053,9 @@
"haptic_feedback_switch": "Aanraaktrillingen inschakelen",
"haptic_feedback_title": "Aanraaktrillingen",
"has_quota": "Heeft limiet",
"hash_asset": "Hash asset",
"hashed_assets": "Gehashte assets",
"hashing": "Hashen",
"header_settings_add_header_tip": "Header toevoegen",
"header_settings_field_validator_msg": "Waarde kan niet leeg zijn",
"header_settings_header_name_input": "Header naam",
@@ -1082,6 +1088,7 @@
"host": "Host",
"hour": "Uur",
"id": "ID",
"idle": "Idle",
"ignore_icloud_photos": "Negeer iCloud foto's",
"ignore_icloud_photos_description": "Foto's die op iCloud zijn opgeslagen, worden niet geüpload naar de Immich server",
"image": "Afbeelding",
@@ -1164,7 +1171,9 @@
"list": "Lijst",
"loading": "Laden",
"loading_search_results_failed": "Laden van zoekresultaten mislukt",
"local": "Lokaal",
"local_asset_cast_failed": "Kan geen asset casten die nog niet geüpload is naar de server",
"local_assets": "Lokale Assets",
"local_network": "Lokaal netwerk",
"local_network_sheet_info": "De app maakt verbinding met de server via deze URL wanneer het opgegeven WiFi-netwerk wordt gebruikt",
"location_permission": "Locatietoestemming",
@@ -1321,6 +1330,7 @@
"no_results": "Geen resultaten",
"no_results_description": "Probeer een synoniem of een algemener zoekwoord",
"no_shared_albums_message": "Maak een album om foto's en video's te delen met mensen in je netwerk",
"no_uploads_in_progress": "Geen uploads bezig",
"not_in_any_album": "Niet in een album",
"not_selected": "Niet geselecteerd",
"note_apply_storage_label_to_previously_uploaded assets": "Opmerking: om het opslaglabel toe te passen op eerder geüploade assets, voer de volgende taak uit",
@@ -1358,6 +1368,7 @@
"original": "origineel",
"other": "Overige",
"other_devices": "Andere apparaten",
"other_entities": "Andere entities",
"other_variables": "Andere variabelen",
"owned": "Eigenaar",
"owner": "Eigenaar",
@@ -1518,6 +1529,8 @@
"refreshing_faces": "Gezichten aan het vernieuwen",
"refreshing_metadata": "Metadata aan het vernieuwen",
"regenerating_thumbnails": "Thumbnails opnieuw aan het genereren",
"remote": "Remote",
"remote_assets": "Remote Assets",
"remove": "Verwijderen",
"remove_assets_album_confirmation": "Weet je zeker dat je {count, plural, one {# asset} other {# assets}} uit het album wilt verwijderen?",
"remove_assets_shared_link_confirmation": "Weet je zeker dat je {count, plural, one {# asset} other {# assets}} uit deze gedeelde link wilt verwijderen?",
@@ -1555,11 +1568,15 @@
"reset_password": "Wachtwoord resetten",
"reset_people_visibility": "Zichtbaarheid mensen resetten",
"reset_pin_code": "Reset PIN code",
"reset_sqlite": "Reset SQLite Database",
"reset_sqlite_confirmation": "Ben je zeker dat je de SQLite database wilt resetten? Je zal moetenn uitloggen om de data opnieuw te synchroniseren.",
"reset_sqlite_success": "De SQLite database is succesvol gereset",
"reset_to_default": "Resetten naar standaard",
"resolve_duplicates": "Duplicaten oplossen",
"resolved_all_duplicates": "Alle duplicaten opgelost",
"restore": "Herstellen",
"restore_all": "Herstel alle",
"restore_trash_action_prompt": "{count} teruggezet uit prullenbak",
"restore_user": "Gebruiker herstellen",
"restored_asset": "Asset hersteld",
"resume": "Hervatten",
@@ -1568,6 +1585,7 @@
"role": "Rol",
"role_editor": "Bewerker",
"role_viewer": "Bekijker",
"running": "Actief",
"save": "Opslaan",
"save_to_gallery": "Opslaan in galerij",
"saved_api_key": "API-sleutel opgeslagen",
@@ -1821,6 +1839,7 @@
"storage_quota": "Opslaglimiet",
"storage_usage": "{used} van {available} gebruikt",
"submit": "Verzenden",
"success": "Succes",
"suggestions": "Suggesties",
"sunrise_on_the_beach": "Zonsopkomst op het strand",
"support": "Ondersteuning",
@@ -1830,6 +1849,8 @@
"sync": "Sync",
"sync_albums": "Albums synchroniseren",
"sync_albums_manual_subtitle": "Synchroniseer alle geüploade videos en fotos naar de geselecteerde back-up albums",
"sync_local": "Lokaal synchroniseren",
"sync_remote": "Op afstand synchroniseren",
"sync_upload_album_setting_subtitle": "Maak en upload je foto's en video's naar de geselecteerde albums op Immich",
"tag": "Tag",
"tag_assets": "Assets taggen",
@@ -1840,6 +1861,7 @@
"tag_updated": "Tag bijgewerkt: {tag}",
"tagged_assets": "{count, plural, one {# asset} other {# assets}} getagd",
"tags": "Tags",
"tap_to_run_job": "Klik om job te starten",
"template": "Template",
"theme": "Thema",
"theme_selection": "Thema selectie",

View File

@@ -228,7 +228,7 @@
"password_settings_description": "Управление настройками входа по паролю",
"paths_validated_successfully": "Все пути успешно прошли проверку",
"person_cleanup_job": "Очистка персоны",
"quota_size_gib": "Размер квоты (ГБ)",
"quota_size_gib": "Размер квоты (GiB)",
"refreshing_all_libraries": "Обновление всех библиотек",
"registration": "Регистрация администратора",
"registration_description": "Первый зарегистрированный пользователь будет назначен администратором. В дальнейшем этой учетной записи будет доступно создание дополнительных пользователей и управление сервером.",
@@ -241,7 +241,7 @@
"server_external_domain_settings": "Внешний домен",
"server_external_domain_settings_description": "Домен для публичных ссылок, включая http(s)://",
"server_public_users": "Публичные пользователи",
"server_public_users_description": "Отображать всех пользователей (имена и email) для добавления в общие альбомы. Когда отключено, список пользователей будет доступен только администраторам.",
"server_public_users_description": "Выводить список пользователей (имена и email) в общих альбомах. Когда отключено, список доступен только администраторам, пользователи смогут делиться только ссылкой.",
"server_settings": "Настройки сервера",
"server_settings_description": "Управление настройками сервера",
"server_welcome_message": "Приветственное сообщение",
@@ -407,7 +407,7 @@
"album_remove_user": "Удалить пользователя?",
"album_remove_user_confirmation": "Вы уверены, что хотите удалить пользователя {user}?",
"album_search_not_found": "Не найдено альбомов по вашему запросу",
"album_share_no_users": "Похоже, вы поделились этим альбомом со всеми пользователями или у вас нет пользователей, с которыми можно поделиться.",
"album_share_no_users": "Нет доступных пользователей, с которыми можно поделиться альбомом.",
"album_updated": "Альбом обновлён",
"album_updated_setting_description": "Получать уведомление по электронной почте при добавлении новых ресурсов в общий альбом",
"album_user_left": "Вы покинули {album}",
@@ -433,8 +433,8 @@
"all_videos": "Все видео",
"allow_dark_mode": "Разрешить темный режим",
"allow_edits": "Разрешить редактирование",
"allow_public_user_to_download": "Разрешить скачивание публичным пользователям",
"allow_public_user_to_upload": "Разрешить публичным пользователям загружать файлы",
"allow_public_user_to_download": "Разрешить скачивание",
"allow_public_user_to_upload": "Разрешить добавление файлов",
"alt_text_qr_code": "QR-код",
"anti_clockwise": "Против часовой",
"api_key": "API ключ",
@@ -573,6 +573,8 @@
"backup_options_page_title": "Резервное копирование",
"backup_setting_subtitle": "Настройка активного и фонового резервного копирования",
"backward": "Назад",
"beta_sync": "Статус бета-синхронизации",
"beta_sync_subtitle": "Управление новой системой синхронизации",
"biometric_auth_enabled": "Биометрическая аутентификация включена",
"biometric_locked_out": "Вам закрыт доступ к биометрической аутентификации",
"biometric_no_options": "Биометрическая аутентификация недоступна",
@@ -590,7 +592,7 @@
"cache_settings_clear_cache_button": "Очистить кэш",
"cache_settings_clear_cache_button_title": "Очищает кэш приложения. Это негативно повлияет на производительность, пока кэш не будет создан заново.",
"cache_settings_duplicated_assets_clear_button": "ОЧИСТИТЬ",
"cache_settings_duplicated_assets_subtitle": "Фото и видео, занесенные приложением в черный список",
"cache_settings_duplicated_assets_subtitle": "Фото и видео, пропускаемые приложением",
"cache_settings_duplicated_assets_title": "Дублирующиеся объекты ({count})",
"cache_settings_statistics_album": "Миниатюры библиотеки",
"cache_settings_statistics_full": "Полные изображения",
@@ -616,7 +618,7 @@
"change_date": "Изменить дату",
"change_description": "Изменить описание",
"change_display_order": "Изменить порядок отображения",
"change_expiration_time": "Изменить время окончания",
"change_expiration_time": "Изменить срок действия",
"change_location": "Изменить местоположение",
"change_name": "Изменить имя",
"change_name_successfully": "Имя успешно изменено",
@@ -692,7 +694,7 @@
"copy_link": "Копировать ссылку",
"copy_link_to_clipboard": "Скопировать ссылку в буфер обмена",
"copy_password": "Скопировать пароль",
"copy_to_clipboard": "Скопировать в буфер обмена",
"copy_to_clipboard": "Скопировать настройки в буфер обмена",
"country": "Страна",
"cover": "Обложка",
"covers": "Обложки",
@@ -830,7 +832,7 @@
"edit_people": "Редактировать людей",
"edit_tag": "Изменить тег",
"edit_title": "Редактировать Заголовок",
"edit_user": "Редактирование пользователя",
"edit_user": "Изменить пользователя",
"edited": "Отредактировано",
"editor": "Редактор",
"editor_close_without_save_prompt": "Изменения не будут сохранены",
@@ -994,7 +996,7 @@
"experimental_settings_subtitle": "Используйте на свой страх и риск!",
"experimental_settings_title": "Экспериментальные функции",
"expire_after": "Истекает через",
"expired": "Срок действия истек",
"expired": "Срок действия истёк",
"expires_date": "Срок действия до {date}",
"explore": "Поиск",
"explorer": "Проводник",
@@ -1051,6 +1053,9 @@
"haptic_feedback_switch": "Включить тактильную отдачу",
"haptic_feedback_title": "Тактильная отдача",
"has_quota": "Квота",
"hash_asset": "Хешированный объект",
"hashed_assets": "Хешированные объекты",
"hashing": "Хеширование",
"header_settings_add_header_tip": "Добавить заголовок",
"header_settings_field_validator_msg": "Значение не может быть пустым",
"header_settings_header_name_input": "Имя заголовка",
@@ -1083,6 +1088,7 @@
"host": "Хост",
"hour": "Час",
"id": "ID",
"idle": "В ожидании",
"ignore_icloud_photos": "Пропускать файлы из iCloud",
"ignore_icloud_photos_description": "Не загружать файлы в Immich, если они хранятся в iCloud",
"image": "Изображения",
@@ -1109,8 +1115,8 @@
"include_archived": "Отображать архив",
"include_shared_albums": "Включать общие альбомы",
"include_shared_partner_assets": "Включать общие ресурсы партнера",
"individual_share": "Персональный доступ",
"individual_shares": "Индивидуальный доступ",
"individual_share": "Индивидуальная подборка",
"individual_shares": "Подборки",
"info": "Информация",
"interval": {
"day_at_onepm": "Каждый день в 13:00",
@@ -1165,7 +1171,9 @@
"list": "Список",
"loading": "Загрузка",
"loading_search_results_failed": "Загрузка результатов поиска не удалась",
"local": "На устройстве",
"local_asset_cast_failed": "Невозможно транслировать объект, который ещё не загружен на сервер",
"local_assets": "Объекты на устройстве",
"local_network": "Локальная сеть",
"local_network_sheet_info": "Приложение будет подключаться к серверу по этому адресу, когда устройство подключено к выбранной Wi-Fi сети",
"location_permission": "Доступ к местоположению",
@@ -1322,6 +1330,7 @@
"no_results": "Нет результатов",
"no_results_description": "Попробуйте использовать синоним или более общее ключевое слово",
"no_shared_albums_message": "Создайте альбом для обмена фотографиями и видеозаписями с людьми в вашей сети",
"no_uploads_in_progress": "Нет активных загрузок",
"not_in_any_album": "Ни в одном альбоме",
"not_selected": "Не выбрано",
"note_apply_storage_label_to_previously_uploaded assets": "Примечание: Чтобы применить метку хранилища к ранее загруженным ресурсам, запустите",
@@ -1359,6 +1368,7 @@
"original": "оригинал",
"other": "Другое",
"other_devices": "Другие устройства",
"other_entities": "Другие объекты",
"other_variables": "Другие переменные",
"owned": "Мои",
"owner": "Владелец",
@@ -1456,7 +1466,7 @@
"profile_drawer_server_out_of_date_minor": "Версия сервера устарела. Пожалуйста, обновите его.",
"profile_image_of_user": "Изображение профиля {user}",
"profile_picture_set": "Фото профиля установлено.",
"public_album": "Публичный альбом",
"public_album": "Общий альбом",
"public_share": "Публичный доступ",
"purchase_account_info": "Поддержка",
"purchase_activated_subtitle": "Благодарим вас за поддержку Immich и программного обеспечения с открытым исходным кодом",
@@ -1519,6 +1529,8 @@
"refreshing_faces": "Обновление лиц",
"refreshing_metadata": "Обновление метаданных",
"regenerating_thumbnails": "Восстановление миниатюр",
"remote": "На сервере",
"remote_assets": "Объекты на сервере",
"remove": "Удалить",
"remove_assets_album_confirmation": "Вы действительно хотите удалить {count, plural, one {# объект} many {# объектов} other {# объекта}} из альбома?",
"remove_assets_shared_link_confirmation": "Вы действительно хотите удалить {count, plural, one {# объект} many {# объектов} other {# объекта}} из публичного доступа по этой ссылке?",
@@ -1556,11 +1568,15 @@
"reset_password": "Сброс пароля",
"reset_people_visibility": "Восстановить видимость людей",
"reset_pin_code": "Сбросить PIN-код",
"reset_sqlite": "Очистить базу данных SQLite",
"reset_sqlite_confirmation": "Вы уверены, что хотите очистить базу данных SQLite? Вам потребуется выйти из системы и снова войти для повторной синхронизации данных.",
"reset_sqlite_success": "База данных SQLite успешно очищена",
"reset_to_default": "Восстановление значений по умолчанию",
"resolve_duplicates": "Устранить дубликаты",
"resolved_all_duplicates": "Все дубликаты устранены",
"restore": "Восстановить",
"restore_all": "Восстановить все",
"restore_trash_action_prompt": "{count} восстановлено из корзины",
"restore_user": "Восстановить пользователя",
"restored_asset": "Восстановленный объект",
"resume": "Продолжить",
@@ -1569,6 +1585,7 @@
"role": "Роль",
"role_editor": "Редактор",
"role_viewer": "Зритель",
"running": "Выполняется",
"save": "Сохранить",
"save_to_gallery": "Сохранить в галерею",
"saved_api_key": "API ключ изменён",
@@ -1822,6 +1839,7 @@
"storage_quota": "Квота хранилища",
"storage_usage": "{used} из {available}",
"submit": "Подтвердить",
"success": "Успешно",
"suggestions": "Предложения",
"sunrise_on_the_beach": "Восход солнца на пляже",
"support": "Поддержка",
@@ -1831,6 +1849,8 @@
"sync": "Синхр.",
"sync_albums": "Синхронизировать альбомы",
"sync_albums_manual_subtitle": "Синхронизировать все загруженные фото и видео в выбранные альбомы для резервного копирования",
"sync_local": "Синхронизировать локально",
"sync_remote": "Синхронизация с сервером",
"sync_upload_album_setting_subtitle": "Создавайте и загружайте свои фотографии и видео в выбранные альбомы на сервер Immich",
"tag": "Тег",
"tag_assets": "Добавить теги",
@@ -1841,6 +1861,7 @@
"tag_updated": "Тег {tag} изменен",
"tagged_assets": "Тег назначен для {count, plural, one {# объекта} other {# объектов}}",
"tags": "Теги",
"tap_to_run_job": "Нажмите для запуска задачи",
"template": "Шаблон",
"theme": "Тема",
"theme_selection": "Выбор темы",

View File

@@ -45,14 +45,14 @@
"backup_database_enable_description": "Povoliť výpisy z databázy",
"backup_keep_last_amount": "Množstvo predchádzajúcich výpisov, ktoré sa majú zachovať",
"backup_settings": "Nastavenia výpisu databázy",
"backup_settings_description": "Správa nastavení výpisu databázy.",
"backup_settings_description": "Spravovať nastavenia výpisu databázy.",
"cleared_jobs": "Hotové úlohy pre: {job}",
"config_set_by_file": "Konfigurácia je v súčasnosti nastavená konfiguračným súborom",
"confirm_delete_library": "Naozaj chcete vymazať knižnicu {library}?",
"confirm_delete_library_assets": "Ste si istí, že chcete vymazať túto knižnicu? Tato operácia nenávratne odstráni {count, plural, one {# zahrnutú položku} few {# zahrnuté položky} other {všetkých # zahrnutých položiek}} z aplikácie Immich. Súbory budú ponechané na disku.",
"confirm_email_below": "Pre potvrdenie zadajte \"{email}\" nižšie",
"confirm_reprocess_all_faces": "Naozaj chcete spracovať všetky tváre znova? Tento proces vymaže pomenovaných ľudí.",
"confirm_user_password_reset": "Naozaj chcete resetovať heslo pre {user}?",
"confirm_user_password_reset": "Naozaj chcete obnoviť heslo pre {user}?",
"confirm_user_pin_code_reset": "Ste si istí, že chcete opätovne nastaviť PIN kód používateľa {user}?",
"create_job": "Vytvoriť úlohu",
"cron_expression": "Výraz cron",
@@ -61,10 +61,10 @@
"disable_login": "Zakázať prihlásenie",
"duplicate_detection_job_description": "Spustite strojové učenie na položkách pre detekciu podobných obrázkov. Spolieha sa na inteligentné vyhľadávanie",
"exclusion_pattern_description": "Vylučovacie vzory Vám umožňujú ignorovať súbory a priečinky pri skenovaní Vašej knižnice. Toto je užitočné, ak máte priečinky obsahujúce súbory, ktoré nechcete importovať, napríklad RAW súbory.",
"external_library_management": "Správa Externej Knižnice",
"external_library_management": "Spravovanie externej knižnice",
"face_detection": "Detekcia tvárí",
"face_detection_description": "Rozpoznajte tváre v položkách pomocou strojového učenia. V prípade videí sa berie do úvahy len náhľad. „Obnoviť“ (znovu) spracuje všetky položky. „Resetovať“ dodatočne vymaže všetky aktuálne údaje o tvárach. „Chýbajúce“ zaradí do poradia médiá, ktoré ešte neboli spracované. Zistené tváre sa po dokončení rozpoznávania tvárí zaradia do poradia na rozpoznávanie tvárí, pričom sa zoskupia do existujúcich alebo nových osôb.",
"facial_recognition_job_description": "Zoskupte rozpoznané tváre do osôb. Tento krok sa vykoná po dokončení rozpoznávania tvárí. „Resetovať“ (znovu) zoskupí všetky tváre. „Chýbajúce“ zaradí tváre, ktoré nemajú pridelenú osobu.",
"face_detection_description": "Rozpoznajte tváre v položkách pomocou strojového učenia. V prípade videí sa berie do úvahy len náhľad. „Aktualizovať“ (znovu) spracuje všetky položky. „Obnoviť“ dodatočne vymaže všetky aktuálne údaje o tvárach. „Chýbajúce“ zaradí do poradia médiá, ktoré ešte neboli spracované. Zistené tváre sa po dokončení rozpoznávania tvárí zaradia do poradia na rozpoznávanie tvárí, pričom sa zoskupia do existujúcich alebo nových osôb.",
"facial_recognition_job_description": "Zoskupte rozpoznané tváre do osôb. Tento krok sa vykoná po dokončení rozpoznávania tvárí. „Obnoviť“ (znovu) zoskupí všetky tváre. „Chýbajúce“ zaradí tváre, ktoré nemajú pridelenú osobu.",
"failed_job_command": "Príkaz {command} zlyhal pre úlohu: {job}",
"force_delete_user_warning": "VAROVANIE: Toto okamžite odstráni používateľa a všetky položky. Tento krok nie je možné vrátiť späť a súbory nebude možné obnoviť.",
"image_format": "Formát",
@@ -94,7 +94,7 @@
"job_not_concurrency_safe": "Táto úloha nie je bezpečná pre súbežné spracovanie.",
"job_settings": "Úlohy",
"job_settings_description": "Spravovať súbežnosť úloh",
"job_status": "Stav Úloh",
"job_status": "Stav úloh",
"jobs_delayed": "{jobCount, plural, one {# oneskorený} few {# oneskorené} other {# oneskorených}}",
"jobs_failed": "{jobCount, plural, one {# neúspešný} few {# neúspešné} other {# neúspešných}}",
"library_created": "Vytvorená knižnica: {library}",
@@ -141,15 +141,15 @@
"machine_learning_smart_search_enabled": "Povoliť inteligentné vyhľadávanie",
"machine_learning_smart_search_enabled_description": "Ak je vypnuté, obrázky nebudú spracované pre inteligentné vyhľadávanie.",
"machine_learning_url_description": "URL adresa servera strojového učenia. Ak je zadaných viacero adries URL, každý server bude testovaný postupne, kým jeden z nich neodpovie úspešne, v poradí od prvého po posledný. Servery, ktoré neodpovedajú, budú dočasne ignorované, kým nebudú opäť online.",
"manage_concurrency": "Správa súbežnosti",
"manage_concurrency": "Spravovať súbežnosť",
"manage_log_settings": "Spravovať nastavenia ukladania záznamov",
"map_dark_style": "Tmavý štýl",
"map_enable_description": "Povoliť funkcie mapy",
"map_gps_settings": "Mapa a nastavenia GPS",
"map_gps_settings_description": "Spravujte nastavenia mapy a GPS (reverzné geokódovanie)",
"map_gps_settings_description": "Spravovať nastavenia mapy a GPS (reverzné geokódovanie)",
"map_implications": "Táto funkčnosť sa spolieha na externý servis spracovania mapových dlaždíc (tiles.immich.cloud)",
"map_light_style": "Svetlý štýl",
"map_manage_reverse_geocoding_settings": "Správa nastavení <link>Reverzného geokódovania</link>",
"map_manage_reverse_geocoding_settings": "Spravovať nastavenia <link>reverzného geokódovania</link>",
"map_reverse_geocoding": "Reverzné Geokódovanie",
"map_reverse_geocoding_enable_description": "Povoliť reverzné geokódovanie",
"map_reverse_geocoding_settings": "Reverzné geokódovanie",
@@ -214,7 +214,7 @@
"oauth_role_claim_description": "Automaticky udeliť prístup správcu na základe prítomnosti tejto požiadavky. Požiadavka môže mať príznak „user“ alebo „admin“.",
"oauth_settings": "OAuth",
"oauth_settings_description": "Spravovať nastavenia prihlásenia OAuth",
"oauth_settings_more_details": "Pre viac informácii o tejto funkcii, prejdite na <link>docs</link>.",
"oauth_settings_more_details": "Pre viac informácii o tejto funkcii, prejdite na <link>dokumentáciu</link>.",
"oauth_storage_label_claim": "Nárokovať Štítok úložiska",
"oauth_storage_label_claim_description": "Automaticky nastaviť štítok úložiska používateľa na hodnotu tohto nároku.",
"oauth_storage_quota_claim": "Deklarácia kvóty úložiska",
@@ -234,7 +234,7 @@
"registration_description": "Keďže ste prvým používateľom v systéme, budú vám pridelené správcovské práva na vykonávanie všetkých úloh a vrátane tvorby nových používateľov.",
"require_password_change_on_login": "Vyžadovať od používateľa zmenu hesla pri prvom prihlásení",
"reset_settings_to_default": "Obnoviť pôvodné nastavenia",
"reset_settings_to_recent_saved": "Obnov naposledy uložené nastavenia",
"reset_settings_to_recent_saved": "Nastavenia boli obnovené na posled uložené nastavenia",
"scanning_library": "Knižnica sa skenuje",
"search_jobs": "Vyhľadať úlohy…",
"send_welcome_email": "Odoslať uvítací e-mail",
@@ -259,7 +259,7 @@
"storage_template_migration_description": "Použite aktuálnu <link>{template}</link> na predtým nahrané médiá",
"storage_template_migration_info": "Šablóna úložiska skonvertuje všetky prípony na malé písmená. Zmeny šablón sa budú vzťahovať iba na nové diela. Ak chcete šablónu spätne použiť na predtým nahrané médiá, spustite <link>{job}</link>.",
"storage_template_migration_job": "Úloha migrácie šablóny úložiska",
"storage_template_more_details": "Ďalšie podrobnosti o tejto funkcii nájdete v <template-link>Šablóna úložiska</template-link> a jej <implications-link>sledky</implications-link>",
"storage_template_more_details": "Podrobnejšie informácie o tejto funkcii nájdete v časti <template-link>šablóna úložiska</template-link> a jej <implications-link>sledky</implications-link>",
"storage_template_onboarding_description_v2": "Ak je táto funkcia zapnutá, automaticky usporiada súbory na základe šablóny definovanej používateľom. Ďalšie informácie nájdete v <link>dokumentácii</link>.",
"storage_template_path_length": "Približný limit dĺžky cesty: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "Šablóna úložiska",
@@ -278,9 +278,9 @@
"template_settings_description": "Spravovanie vlastných šablón upozornení",
"theme_custom_css_settings": "Vlastné CSS",
"theme_custom_css_settings_description": "CSS štýly umožňujú prispôsobiť dizajn Immich.",
"theme_settings": "Motívy",
"theme_settings": "Nastavenia témy",
"theme_settings_description": "Spravovať prispôsobenie webového rozhrania Immich",
"thumbnail_generation_job": "Generovať Miniatúry",
"thumbnail_generation_job": "Generovať miniatúry",
"thumbnail_generation_job_description": "Generujte veľké, malé a rozmazané miniatúry pre každú položku, ako aj miniatúry pre každú osobu",
"transcoding_acceleration_api": "API pre akceleráciu",
"transcoding_acceleration_api_description": "Rozhranie API, ktoré bude spolupracovať s vaším zariadením s cieľom urýchliť prekódovanie. Toto nastavenie je „najlepšie úsilie“: pri zlyhaní sa vráti k softvérovému prekódovaniu. VP9 môže alebo nemusí fungovať v závislosti od vášho hardvéru.",
@@ -300,7 +300,7 @@
"transcoding_bitrate_description": "Videá presahujúce maximálnu bitovú rýchlosť alebo videá, ktoré nie sú v akceptovanom formáte",
"transcoding_codecs_learn_more": "Ak sa chcete dozvedieť viac o tu použitej terminológii, pozrite si dokumentáciu FFmpeg pre <h264-link>kodek H.264</h264-link>, <hevc-link>kodek HEVC</hevc-link> a <vp9-link>VP9 kodek</vp9-link>.",
"transcoding_constant_quality_mode": "Režim konštantnej kvality",
"transcoding_constant_quality_mode_description": "ICQ je lepšie ako CQP, ale niektoré zariadenia na hardvérovú akceleráciu tento režim nepodporujú. Nastavenie tejto možnosti uprednostní špecifikovaný režim pri použití kódovania založeného na kvalite. Ignorované spoločnosťou NVENC, pretože nepodporuje ICQ.",
"transcoding_constant_quality_mode_description": "ICQ je lepšie ako CQP, ale niektoré zariadenia na hardvérovú akceleráciu tento režim nepodporujú. Nastavenie tejto možnosti uprednostní špecifikovaný režim pri použití kódovania založeného na kvalite. Ignorované funkciou NVENC, pretože nepodporuje ICQ.",
"transcoding_constant_rate_factor": "Faktor konštantnej rýchlosti (-crf)",
"transcoding_constant_rate_factor_description": "Úroveň kvality videa. Typické hodnoty sú 23 pre H.264, 28 pre HEVC, 31 pre VP9 a 35 pre AV1. Nižšie je lepšie, ale vytvára väčšie súbory.",
"transcoding_disabled_description": "Neprekódovať žiadne videá, na niektorých klientoch môže prerušiť prehrávanie",
@@ -321,13 +321,13 @@
"transcoding_policy_description": "Nastavte, kedy bude video prekódované",
"transcoding_preferred_hardware_device": "Uprednostňované hardvérové zariadenie",
"transcoding_preferred_hardware_device_description": "Platí len pre VAAPI a QSV. Nastavuje uzol dri, ktorý sa používa na hardvérové prekódovanie.",
"transcoding_preset_preset": "Prednastavenie (-preset)",
"transcoding_preset_preset": "Predvoľba (-preset)",
"transcoding_preset_preset_description": "Rýchlosť kompresie. Pomalšie predvoľby vytvárajú menšie súbory a zvyšujú kvalitu, keď sa zameriavajú na určitý dátový tok. VP9 ignoruje rýchlosti vyššie ako „rýchlejšie“.",
"transcoding_reference_frames": "Referenčné snímky",
"transcoding_reference_frames_description": "Počet snímok, na ktoré sa má odkazovať pri kompresii daného snímku. Vyššie hodnoty zvyšujú účinnosť kompresie, ale spomaľujú kódovanie. Hodnota 0 sa nastavuje automaticky.",
"transcoding_required_description": "Iba videá, ktoré nie sú v prijatom formáte",
"transcoding_settings": "Nastavenia prekódovania videa",
"transcoding_settings_description": "Spravujte, ktoré videá sa majú prekódovať a ako ich spracovať",
"transcoding_settings_description": "Spravovať, ktoré videá sa majú prekódovať a ako sa majú spracovať",
"transcoding_target_resolution": "Cieľové rozlíšenie",
"transcoding_target_resolution_description": "Vyššie rozlíšenia môžu zachovať viac detailov, ale ich kódovanie trvá dlhšie, majú väčšiu veľkosť súborov a môžu znížiť odozvu aplikácie.",
"transcoding_temporal_aq": "Časové AQ",
@@ -349,13 +349,13 @@
"trash_settings_description": "Spravovať nastavenia koša",
"user_cleanup_job": "Premazanie používateľov",
"user_delete_delay": "Konto <b>{user}</b> a jeho médiá budú podľa plánu natrvalo vymazané za {delay, plural, one {# deň} few {# dni} other {# dní}}.",
"user_delete_delay_settings": "Odstrániť oneskorenie",
"user_delete_delay_settings_description": "Počet dní po odstránení na trvalé vymazanie účtu a médií používateľa. Úloha odstraňovania používateľov sa spúšťa o polnoci, aby sa skontrolovali používatelia, ktorí sú pripravení na odstránenie. Zmeny tohto nastavenia sa vyhodnotia pri ďalšom spustení.",
"user_delete_delay_settings": "Oneskorenie vymazania",
"user_delete_delay_settings_description": "Počet dní, po ktorých sa po odstránení používateľa natrvalo odstráni jeho účet a položky. Úloha odstraňovania používateľov sa spúšťa o polnoci, aby sa skontrolovali používatelia, ktorí sú pripravení na odstránenie. Zmeny tohto nastavenia sa vyhodnotia pri ďalšom spustení.",
"user_delete_immediately": "Konto a médiá používateľa <b>{user}</b> budú zaradené do poradia na trvalé vymazanie <b>okamžite</b>.",
"user_delete_immediately_checkbox": "Používateľ a médiá budú zaradení do frontu na okamžité vymazanie",
"user_details": "Podrobnosti o používateľovi",
"user_management": "Správa používateľov",
"user_password_has_been_reset": "Heslo používateľa bolo resetované:",
"user_management": "Spravovanie používateľov",
"user_password_has_been_reset": "Heslo používateľa bolo obnovené:",
"user_password_reset_description": "Poskytnite používateľovi dočasné heslo a informujte ho, že si ho bude musieť zmeniť pri ďalšom prihlásení.",
"user_restore_description": "<b>{user}</b> bude účet obnovený.",
"user_restore_scheduled_removal": "Obnoviť používateľa - plánované odstránenie na {date, date, long}",
@@ -393,7 +393,7 @@
"age_year_months": "Vek 1 rok, {months, plural, one {# month} other {# months}}",
"age_years": "{years, plural, other {Vek #}}",
"album_added": "Album bol pridaný",
"album_added_notification_setting_description": "Obdržať upozornenie emailom, keď ste prida do zdieľaného albumu",
"album_added_notification_setting_description": "Obdržať upozornenie emailom, keď vás prida do zdieľaného albumu",
"album_cover_updated": "Obal albumu aktualizovaný",
"album_delete_confirmation": "Ste si istý, že chcete odstrániť album {album}?",
"album_delete_confirmation_description": "Ak je tento album zdieľaný, ostatní používatelia k nemu už nebudú mať prístup.",
@@ -573,6 +573,8 @@
"backup_options_page_title": "Možnosti zálohovania",
"backup_setting_subtitle": "Spravovať nastavenia odosielania na pozadí a v popredí",
"backward": "Dozadu",
"beta_sync": "Stav synchronizácie verzie Beta",
"beta_sync_subtitle": "Spravovať nový systém synchronizácie",
"biometric_auth_enabled": "Biometrické overovanie je povolené",
"biometric_locked_out": "Ste vymknutí z biometrického overovania",
"biometric_no_options": "Nie sú k dispozícii žiadne biometrické možnosti",
@@ -590,7 +592,7 @@
"cache_settings_clear_cache_button": "Vymazať vyrovnávaciu pamäť",
"cache_settings_clear_cache_button_title": "Vymaže vyrovnávaciu pamäť aplikácie. To výrazne ovplyvní výkon aplikácie, kým sa vyrovnávacia pamäť neobnoví.",
"cache_settings_duplicated_assets_clear_button": "VYČISTIŤ",
"cache_settings_duplicated_assets_subtitle": "Fotky a videá ktoré sú na čiernej listine zvolené aplikáciou",
"cache_settings_duplicated_assets_subtitle": "Fotografie a videá, ktoré aplikácia ignoruje podľa zoznamu",
"cache_settings_duplicated_assets_title": "Duplicitné položky ({count})",
"cache_settings_statistics_album": "Knižnica náhľadov",
"cache_settings_statistics_full": "Kompletné fotografie",
@@ -628,7 +630,7 @@
"change_password_form_password_mismatch": "Heslá sa nezhodujú",
"change_password_form_reenter_new_password": "Znova zadajte nové heslo",
"change_pin_code": "Zmeniť PIN kód",
"change_your_password": "Zmeňte si heslo",
"change_your_password": "Zmeniť heslo",
"changed_visibility_successfully": "Viditeľnosť bola úspešne zmenená",
"check_corrupt_asset_backup": "Skontrolovať, či nie sú poškodené zálohy položiek",
"check_corrupt_asset_backup_button": "Vykonať kontrolu",
@@ -723,7 +725,7 @@
"custom_locale_description": "Formátovanie dátumov a čísel podľa jazyka a regiónu",
"daily_title_text_date": "EEEE, d. MMMM",
"daily_title_text_date_year": "EEEE, d. MMMM y",
"dark": "Tmavý",
"dark": "Tmavá",
"dark_theme": "Prepnúť tmavú tému",
"date_after": "Dátum po",
"date_and_time": "Dátum a Čas",
@@ -949,7 +951,7 @@
"unable_to_remove_library": "Nie je možné odstrániť knižnicu",
"unable_to_remove_partner": "Nie je možné odstrániť partnera",
"unable_to_remove_reaction": "Nie je možné odstrániť reakciu",
"unable_to_reset_password": "Nie je možné resetovať heslo",
"unable_to_reset_password": "Nie je možné obnoviť heslo",
"unable_to_reset_pin_code": "Nie je možné obnoviť PIN kód",
"unable_to_resolve_duplicate": "Nie je možné vyriešiť duplikát",
"unable_to_restore_assets": "Nie je možné obnoviť položky",
@@ -987,7 +989,7 @@
"exif_bottom_sheet_person_age_months": "Vek {months} mesiacov",
"exif_bottom_sheet_person_age_year_months": "Vek 1 rok, {months} mesiacov",
"exif_bottom_sheet_person_age_years": "Vek {years}",
"exit_slideshow": "Opustiť Slideshow",
"exit_slideshow": "Opustiť prezentáciu",
"expand_all": "Rozbaliť všetko",
"experimental_settings_new_asset_list_subtitle": "Prebiehajúca práca",
"experimental_settings_new_asset_list_title": "Povolenie experimentálnej mriežky fotografií",
@@ -1051,6 +1053,9 @@
"haptic_feedback_switch": "Povoliť hmatovú odozvu",
"haptic_feedback_title": "Hmatová odozva",
"has_quota": "Má kvótu",
"hash_asset": "Hashovať položku",
"hashed_assets": "Hashované položky",
"hashing": "Hashovanie",
"header_settings_add_header_tip": "Pridať hlavičku",
"header_settings_field_validator_msg": "Hodnota nemôže byť prázdna",
"header_settings_header_name_input": "Názov hlavičky",
@@ -1083,6 +1088,7 @@
"host": "Hostiteľ",
"hour": "Hodina",
"id": "ID",
"idle": "Nečinné",
"ignore_icloud_photos": "Ignorovať fotky v službe iCloud",
"ignore_icloud_photos_description": "Fotografie uložené v službe iCloud sa nebudú odosielať na server Immich",
"image": "Obrázok",
@@ -1139,7 +1145,7 @@
"language_no_results_subtitle": "Skúste upraviť hľadaný výraz",
"language_no_results_title": "Neboli nájdené žiadne jazyky",
"language_search_hint": "Vyhľadať jazyky...",
"language_setting_description": "Vyberte preferovaný jazyk",
"language_setting_description": "Vyberte požadovaný jazyk",
"last_seen": "Naposledy videné",
"latest_version": "Najnovšia verzia",
"latitude": "Zemepisná šírka",
@@ -1156,7 +1162,7 @@
"library_page_sort_last_modified": "Naposledy upravené",
"library_page_sort_title": "Podľa názvu albumu",
"licenses": "Licencie",
"light": "Svetlý",
"light": "Svetlá",
"like_deleted": "Like odstránený",
"link_motion_video": "Pripojiť pohyblivé video",
"link_options": "Možnosti odkazu",
@@ -1165,7 +1171,9 @@
"list": "Zoznam",
"loading": "Načítavanie",
"loading_search_results_failed": "Načítanie výsledkov hľadania sa nepodarilo",
"local": "Lokálne",
"local_asset_cast_failed": "Nie je možné preniesť médium, ktoré nie je nahrané na serveri",
"local_assets": "Lokálne položky",
"local_network": "Miestna sieť",
"local_network_sheet_info": "Pri použití zadanej siete Wi-Fi sa aplikácia pripojí k serveru prostredníctvom tejto URL adresy",
"location_permission": "Povolenie na určenie polohy",
@@ -1277,7 +1285,7 @@
"move_off_locked_folder": "Presunúť zo zamknutého priečinka",
"move_to_lock_folder_action_prompt": "{count} pridaných do zamknutého priečinka",
"move_to_locked_folder": "Presunúť do zamknutého priečinka",
"move_to_locked_folder_confirmation": "Tieto fotografie a videá budú odstránené zo všetkých albumov a bude ich možné zobraziť len v zamknutom priečinku",
"move_to_locked_folder_confirmation": "Tieto fotografie a videá budú odobrané zo všetkých albumov a bude ich možné zobraziť len v zamknutom priečinku",
"moved_to_archive": "{count, plural, one {Presunutá # položka} few {Presunuté # položky} other {Presunutých # položiek}} do archívu",
"moved_to_library": "{count, plural, one {Presunutá # položka} few {Presunuté # položky} other {Presunutých # položiek}} do knižnice",
"moved_to_trash": "Presunuté do koša",
@@ -1322,6 +1330,7 @@
"no_results": "Žiadne výsledky",
"no_results_description": "Skúste synonymum alebo všeobecnejší výraz",
"no_shared_albums_message": "Vytvorí album na zdieľanie fotiek a videí s ľuďmi vo vašej sieti",
"no_uploads_in_progress": "Žiadne prebiehajúce nahrávanie",
"not_in_any_album": "Nie je v žiadnom albume",
"not_selected": "Nevybrané",
"note_apply_storage_label_to_previously_uploaded assets": "Poznámka: Ak chcete použiť Štítok úložiska na predtým nahrané médiá, spustite príkaz",
@@ -1343,7 +1352,7 @@
"onboarding": "Na palube",
"onboarding_locale_description": "Vyberte požadovaný jazyk. Neskôr ho môžete zmeniť v nastaveniach.",
"onboarding_privacy_description": "Nasledujúce (voliteľné) funkcie závisia na externých službách a kedykoľvek ich môžete vypnúť nastaveniach.",
"onboarding_server_welcome_description": "Nastavme vašu inštanciu pomocou bežných nastavení.",
"onboarding_server_welcome_description": "Poďme si nastav vašu inštanciu s niekoľkými bežnými nastaveniami.",
"onboarding_theme_description": "Vyberte farbu témy pre váš server. Môžete to aj neskôr zmeniť vo vašich nastaveniach.",
"onboarding_user_welcome_description": "Začnime!",
"onboarding_welcome_user": "Vitaj, {user}",
@@ -1359,6 +1368,7 @@
"original": "originál",
"other": "Ostatné",
"other_devices": "Ďalšie zariadenia",
"other_entities": "Ostatné subjekty",
"other_variables": "Ostatné premenné",
"owned": "Vlastnené",
"owner": "Vlastník",
@@ -1434,9 +1444,9 @@
"play_or_pause_video": "Pustí alebo pozastaví video",
"please_auth_to_access": "Prosím, potvrďte overenie pre prístup",
"port": "Port",
"preferences_settings_subtitle": "Spravujte predvoľby aplikácie",
"preferences_settings_subtitle": "Spravovať predvoľby aplikácie",
"preferences_settings_title": "Predvoľby",
"preset": "Prednastavenie",
"preset": "Predvoľba",
"preview": "Náhľad",
"previous": "Predošlé",
"previous_memory": "Predošlá spomienka",
@@ -1508,7 +1518,7 @@
"recently_added_page_title": "Nedávno pridané",
"recently_taken": "Nedávno nasnímané",
"recently_taken_page_title": "Nedávno zhotovené",
"refresh": "Obnoviť",
"refresh": "Aktualizovať",
"refresh_encoded_videos": "Obnoviť enkódované videá",
"refresh_faces": "Obnoviť tváre",
"refresh_metadata": "Obnoviť metadáta",
@@ -1519,6 +1529,8 @@
"refreshing_faces": "Obnovovanie tvárí",
"refreshing_metadata": "Obnovovanie metadát",
"regenerating_thumbnails": "Pregenerovanie náhľadov",
"remote": "Vzdialené",
"remote_assets": "Vzdialené položky",
"remove": "Odstrániť",
"remove_assets_album_confirmation": "Naozaj chcete odstrániť {count, plural, one {# položku} few {# položky} other {# položiek}} z albumu?",
"remove_assets_shared_link_confirmation": "Naozaj chcete odstrániť {count, plural, one {# položku} few {# položky} other {# položiek}} z tohoto zdieľaného odkazu?",
@@ -1529,8 +1541,8 @@
"remove_from_album_action_prompt": "{count} odstránené z albumu",
"remove_from_favorites": "Odstrániť z obľúbených",
"remove_from_lock_folder_action_prompt": "{count} odobrané zo zamknutého priečinka",
"remove_from_locked_folder": "Odstrániť zo zamknutého priečinka",
"remove_from_locked_folder_confirmation": "Ste si istí, že chcete tieto fotografie a videá presunúť zo zamknutého priečinka? Budú viditeľné vo vašej knižnici.",
"remove_from_locked_folder": "Odobrať zo zamknutého priečinka",
"remove_from_locked_folder_confirmation": "Ste si istí, že chcete tieto fotografie a videá odobrať zo zamknutého priečinka? Budú viditeľné vo vašej knižnici.",
"remove_from_shared_link": "Odstrániť zo zdieľaného odkazu",
"remove_memory": "Odstrániť spomienku",
"remove_photo_from_memory": "Odstrániť fotografiu z tejto spomienky",
@@ -1552,28 +1564,33 @@
"require_password": "Vyžadovať heslo",
"require_user_to_change_password_on_first_login": "Vyžadovať zmenu hesla po prvom prihlásení",
"rescan": "Opätovné vyhľadávanie",
"reset": "Resetovať",
"reset": "Obnoviť",
"reset_password": "Obnoviť heslo",
"reset_people_visibility": "Resetovať viditeľnosť ľudí",
"reset_people_visibility": "Obnoviť viditeľnosť ľudí",
"reset_pin_code": "Obnoviť PIN kód",
"reset_to_default": "Resetovať na predvolené",
"reset_sqlite": "Obnoviť SQLite databázu",
"reset_sqlite_confirmation": "Ste si istí, že chcete obnoviť SQLite databázu? Na opätovnú synchronizáciu údajov sa budete musieť odhlásiť a znova prihlásiť",
"reset_sqlite_success": "Úspešné obnovenie databázy SQLite",
"reset_to_default": "Obnoviť na predvolené",
"resolve_duplicates": "Vyriešiť duplicity",
"resolved_all_duplicates": "Vyriešené všetky duplicity",
"restore": "Navrátiť",
"restore_all": "Navrátit všetko",
"restore_trash_action_prompt": "{count} obnovených z koša",
"restore_user": "Navrátiť používateľa",
"restored_asset": "Navrátené položky",
"resume": "Pokračovať",
"retry_upload": "Zopakovať nahrávanie",
"review_duplicates": "Prezrieť duplikáty",
"review_duplicates": "Preskúmať duplikáty",
"role": "Rola",
"role_editor": "Editor",
"role_viewer": "Divák",
"running": "Spustené",
"save": "Uložiť",
"save_to_gallery": "Uložiť do galérie",
"saved_api_key": "Uložený API Kľúč",
"saved_profile": "Uložený profil",
"saved_settings": "Uložené nastavenia",
"saved_settings": "Nastavenia boli uložené",
"say_something": "Napíšte niečo",
"scaffold_body_error_occurred": "Vyskytla sa chyba",
"scan_all_libraries": "Preskenovať všetky knižnice",
@@ -1585,7 +1602,7 @@
"search_by_context": "Hľadať s kontextom",
"search_by_description": "Vyhľadávanie podľa popisu",
"search_by_description_example": "Pešia turistika v Sape",
"search_by_filename": "Hľadať s názvom alebo príponou súboru",
"search_by_filename": "Hľadať podľa názvu alebo prípony súboru",
"search_by_filename_example": "napr. IMG_1234.JPG alebo PNG",
"search_camera_make": "Hľadať značku fotoaparátu...",
"search_camera_model": "Hľadať model fotoaparátu...",
@@ -1809,7 +1826,7 @@
"stacked_assets_count": "{count, plural, one {Zoskupená # položka} few {Zoskupené # položky} other {Zoskupených # položiek}}",
"stacktrace": "Výpis zásobníku",
"start": "Štart",
"start_date": "Začiatočný dátum",
"start_date": "Počiatočný dátum",
"state": "Štát",
"status": "Stav",
"stop_casting": "Zastaviť prenos",
@@ -1822,6 +1839,7 @@
"storage_quota": "Úložný limit",
"storage_usage": "Využitých {used} z {available}",
"submit": "Odoslať",
"success": "Úspech",
"suggestions": "Návrhy",
"sunrise_on_the_beach": "Východ slnka na pláži",
"support": "Podpora",
@@ -1831,9 +1849,11 @@
"sync": "Synchronizovať",
"sync_albums": "Synchronizovať albumy",
"sync_albums_manual_subtitle": "Synchronizujte všetky nahrané videá a fotografie s vybranými záložnými albumami",
"sync_local": "Synchronizovať lokálne",
"sync_remote": "Synchronizovať vzdialené",
"sync_upload_album_setting_subtitle": "Vytvárajte a nahrávajte svoje fotografie a videá do vybraných albumov na Immich",
"tag": "Štítok",
"tag_assets": "Označiť položky",
"tag_assets": "Pridať štítky",
"tag_created": "Vytvorený štítok: {tag}",
"tag_feature_description": "Prehliadanie fotiek a videá zoskupených podľa tematických štítkov",
"tag_not_found_question": "Neviete nájsť štítok? <link>Vytvorte nový štítok.</link>",
@@ -1841,6 +1861,7 @@
"tag_updated": "Upravený štítok: {tag}",
"tagged_assets": "Štítok priradený {count, plural, one {# položke} other {# položkám}}",
"tags": "Štítky",
"tap_to_run_job": "Ťuknutím na položku spustíte úlohu",
"template": "Šablóna",
"theme": "Téma",
"theme_selection": "Výber témy",
@@ -1863,7 +1884,7 @@
"time_based_memories": "Časové spomienky",
"timeline": "Časová os",
"timezone": "Časové pásmo",
"to_archive": "Archív",
"to_archive": "Archivovať",
"to_change_password": "Zmeniť heslo",
"to_favorite": "Obľúbiť",
"to_login": "Prihlásiť",
@@ -1898,7 +1919,7 @@
"unfavorite_action_prompt": "{count} odstránené z Obľúbených",
"unhide_person": "Odkryť osobu",
"unknown": "Neznáme",
"unknown_country": "Neznámy štát",
"unknown_country": "Neznáma krajina",
"unknown_year": "Neznámy rok",
"unlimited": "Neobmedzené",
"unlink_motion_video": "Odpojiť pohyblivé video",
@@ -1986,7 +2007,7 @@
"viewer_stack_use_as_main_asset": "Použiť ako hlavnú fotku",
"viewer_unstack": "Odskupiť",
"visibility_changed": "Viditeľnosť zmenená pre {count, plural, one {# osobu} few {# osoby} other {# osôb}}",
"waiting": "Čaká",
"waiting": "Čakajúce",
"warning": "Varovanie",
"week": "Týždeň",
"welcome": "Vitajte",
@@ -1996,7 +2017,7 @@
"year": "Rok",
"years_ago": "pred {years, plural, one {# rokom} other {# rokmi}}",
"yes": "Áno",
"you_dont_have_any_shared_links": "Nemáte žiadne zdielané linky",
"you_dont_have_any_shared_links": "Nemáte žiadne zdielané odkazy",
"your_wifi_name": "Váš názov siete Wi-Fi",
"zoom_image": "Priblížiť obrázok"
}

View File

@@ -573,6 +573,8 @@
"backup_options_page_title": "Možnosti varnostne kopije",
"backup_setting_subtitle": "Upravljaj nastavitve nalaganja v ozadju in ospredju",
"backward": "Nazaj",
"beta_sync": "Stanje sinhronizacije beta različice",
"beta_sync_subtitle": "Upravljanje novega sistema sinhronizacije",
"biometric_auth_enabled": "Biometrična avtentikacija omogočena",
"biometric_locked_out": "Biometrična avtentikacija vam je onemogočena",
"biometric_no_options": "Biometrične možnosti niso na voljo",
@@ -590,7 +592,7 @@
"cache_settings_clear_cache_button": "Počisti predpomnilnik",
"cache_settings_clear_cache_button_title": "Počisti predpomnilnik aplikacije. To bo znatno vplivalo na delovanje aplikacije, dokler se predpomnilnik ne obnovi.",
"cache_settings_duplicated_assets_clear_button": "POČISTI",
"cache_settings_duplicated_assets_subtitle": "Fotografije in videoposnetki, ki jih je aplikacija uvrstila na črni seznam",
"cache_settings_duplicated_assets_subtitle": "Fotografije in videoposnetki, ki so prezrti s strani aplikacije",
"cache_settings_duplicated_assets_title": "Podvojena sredstva ({count})",
"cache_settings_statistics_album": "Sličice knjižnice",
"cache_settings_statistics_full": "Izvirne slike",
@@ -1051,6 +1053,9 @@
"haptic_feedback_switch": "Uporabi haptičen odziv",
"haptic_feedback_title": "Haptičen odziv",
"has_quota": "Ima kvoto",
"hash_asset": "Zgoščeno sredstvo",
"hashed_assets": "Zgoščena sredstva",
"hashing": "Zgoščevanje",
"header_settings_add_header_tip": "Dodaj glavo",
"header_settings_field_validator_msg": "Vrednost ne sme biti prazna",
"header_settings_header_name_input": "Ime glave",
@@ -1083,6 +1088,7 @@
"host": "Gostitelj",
"hour": "Ura",
"id": "ID",
"idle": "Nedejavnost",
"ignore_icloud_photos": "Ignoriraj fotografije iCloud",
"ignore_icloud_photos_description": "Fotografije, shranjene v iCloud, ne bodo naložene na strežnik Immich",
"image": "Slika",
@@ -1165,7 +1171,9 @@
"list": "Seznam",
"loading": "Nalaganje",
"loading_search_results_failed": "Nalaganje rezultatov iskanja ni uspelo",
"local": "Lokalno",
"local_asset_cast_failed": "Sredstva, ki niso naložena na strežnik, ni mogoče predvajati",
"local_assets": "Lokalna sredstva",
"local_network": "Lokalno omrežje",
"local_network_sheet_info": "Aplikacija se bo povezala s strežnikom prek tega URL-ja, ko bo uporabljala navedeno omrežje Wi-Fi",
"location_permission": "Dovoljenje za lokacijo",
@@ -1322,6 +1330,7 @@
"no_results": "Brez rezultatov",
"no_results_description": "Poskusite s sinonimom ali bolj splošno ključno besedo",
"no_shared_albums_message": "Ustvarite album za skupno rabo fotografij in videoposnetkov z osebami v vašem omrežju",
"no_uploads_in_progress": "Ni nalaganj v teku",
"not_in_any_album": "Ni v nobenem albumu",
"not_selected": "Ni izbrano",
"note_apply_storage_label_to_previously_uploaded assets": "Opomba: Če želite oznako za shranjevanje uporabiti za predhodno naložena sredstva, zaženite",
@@ -1359,6 +1368,7 @@
"original": "izvirnik",
"other": "drugo",
"other_devices": "Druge naprave",
"other_entities": "Drugi subjekti",
"other_variables": "Druge spremenljivke",
"owned": "V lasti",
"owner": "Lastnik",
@@ -1390,7 +1400,7 @@
"pause": "Premor",
"pause_memories": "Zaustavi spomine",
"paused": "Zaustavljeno",
"pending": "V teku",
"pending": "Čakanje",
"people": "Osebe",
"people_edits_count": "{count, plural, one {Urejena # oseba} two {Urejeni # osebi} few {Urejene # osebe} other {Urejenih # oseb}}",
"people_feature_description": "Brskanje po fotografijah in videoposnetkih, razvrščenih po osebah",
@@ -1519,6 +1529,8 @@
"refreshing_faces": "Osveževanje obrazev",
"refreshing_metadata": "Osveževanje metapodatkov",
"regenerating_thumbnails": "Obnavljanje sličic",
"remote": "Oddaljeno",
"remote_assets": "Oddaljena sredstva",
"remove": "Odstrani",
"remove_assets_album_confirmation": "Ali ste prepričani, da želite odstraniti {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} iz albuma?",
"remove_assets_shared_link_confirmation": "Ali ste prepričani, da želite odstraniti {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} iz te skupne povezave?",
@@ -1556,11 +1568,15 @@
"reset_password": "Ponastavi geslo",
"reset_people_visibility": "Ponastavi vidnost ljudi",
"reset_pin_code": "Ponastavi PIN kodo",
"reset_sqlite": "Ponastavi bazo podatkov SQLite",
"reset_sqlite_confirmation": "Ali ste prepričani, da želite ponastaviti bazo podatkov SQLite? Za ponovno sinhronizacijo podatkov se boste morali odjaviti in znova prijaviti",
"reset_sqlite_success": "Uspešno ponastavljena baza podatkov SQLite",
"reset_to_default": "Ponastavi na privzeto",
"resolve_duplicates": "Razreši dvojnike",
"resolved_all_duplicates": "Razrešeni vsi dvojniki",
"restore": "Obnovi",
"restore_all": "Obnovi vse",
"restore_trash_action_prompt": "{count} obnovljenih iz koša",
"restore_user": "Obnovi uporabnika",
"restored_asset": "Obnovljeno sredstvo",
"resume": "Nadaljuj",
@@ -1569,6 +1585,7 @@
"role": "Vloga",
"role_editor": "Urejevalec",
"role_viewer": "Gledalec",
"running": "V teku",
"save": "Shrani",
"save_to_gallery": "Shrani v galerijo",
"saved_api_key": "Shranjen API ključ",
@@ -1822,6 +1839,7 @@
"storage_quota": "Kvota shranjevanja",
"storage_usage": "uporabljeno {used} od {available}",
"submit": "Predloži",
"success": "Uspeh",
"suggestions": "Predlogi",
"sunrise_on_the_beach": "Sončni vzhod na plaži",
"support": "Podpora",
@@ -1831,6 +1849,8 @@
"sync": "Sinhronizacija",
"sync_albums": "Sinhronizacija albumov",
"sync_albums_manual_subtitle": "Sinhronizirajte vse naložene videoposnetke in fotografije v izbrane varnostne albume",
"sync_local": "Sinhroniziraj lokalno",
"sync_remote": "Sinhroniziraj oddaljeno",
"sync_upload_album_setting_subtitle": "Ustvarite in naložite svoje fotografije in videoposnetke v izbrane albume na Immich",
"tag": "Oznaka",
"tag_assets": "Označi sredstva",
@@ -1841,6 +1861,7 @@
"tag_updated": "Posodobljena oznaka: {tag}",
"tagged_assets": "Označeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}",
"tags": "Oznake",
"tap_to_run_job": "Dotaknite se za zagon opravila",
"template": "Predloga",
"theme": "Tema",
"theme_selection": "Izbira teme",

View File

@@ -573,6 +573,8 @@
"backup_options_page_title": "备份选项",
"backup_setting_subtitle": "管理后台和前台上传设置",
"backward": "后退",
"beta_sync": "测试版同步状态",
"beta_sync_subtitle": "管理新的同步系统",
"biometric_auth_enabled": "生物识别身份验证已启用",
"biometric_locked_out": "您被锁定在生物识别身份验证之外",
"biometric_no_options": "没有可用的生物识别选项",
@@ -590,7 +592,7 @@
"cache_settings_clear_cache_button": "清除缓存",
"cache_settings_clear_cache_button_title": "清除应用缓存。在重新生成缓存之前,将显著影响应用的性能。",
"cache_settings_duplicated_assets_clear_button": "清除",
"cache_settings_duplicated_assets_subtitle": "已加入黑名单的照片和视频",
"cache_settings_duplicated_assets_subtitle": "应用程序忽略的照片和视频",
"cache_settings_duplicated_assets_title": "重复项目({count}",
"cache_settings_statistics_album": "图库缩略图",
"cache_settings_statistics_full": "完整图像",
@@ -1051,6 +1053,9 @@
"haptic_feedback_switch": "启用振动反馈",
"haptic_feedback_title": "振动反馈",
"has_quota": "配额大小",
"hash_asset": "哈希项目",
"hashed_assets": "已哈希的项目",
"hashing": "正在哈希",
"header_settings_add_header_tip": "添加标头",
"header_settings_field_validator_msg": "设置不可为空",
"header_settings_header_name_input": "标头名称",
@@ -1083,6 +1088,7 @@
"host": "服务器",
"hour": "时",
"id": "ID",
"idle": "空闲",
"ignore_icloud_photos": "忽略 iCloud 照片",
"ignore_icloud_photos_description": "存储在 iCloud 中的照片不会上传至 Immich 服务器",
"image": "图片",
@@ -1165,7 +1171,9 @@
"list": "列表",
"loading": "加载中",
"loading_search_results_failed": "加载搜索结果失败",
"local": "本地",
"local_asset_cast_failed": "无法投放未上传至服务器的项目",
"local_assets": "本地项目",
"local_network": "本地网络",
"local_network_sheet_info": "当使用指定的 Wi-Fi 网络时,应用程序将通过此 URL 访问服务器",
"location_permission": "定位权限",
@@ -1322,6 +1330,7 @@
"no_results": "无结果",
"no_results_description": "尝试使用同义词或更通用的关键词",
"no_shared_albums_message": "创建相册以共享照片和视频",
"no_uploads_in_progress": "没有正在进行的上传",
"not_in_any_album": "不在任何相册中",
"not_selected": "未选择",
"note_apply_storage_label_to_previously_uploaded assets": "提示:要将存储标签应用于之前上传的项目,需要运行",
@@ -1359,6 +1368,7 @@
"original": "原图",
"other": "其它",
"other_devices": "其它设备",
"other_entities": "其他实体",
"other_variables": "其它变量",
"owned": "我的",
"owner": "所有者",
@@ -1519,6 +1529,8 @@
"refreshing_faces": "正在面部重新识别",
"refreshing_metadata": "正在刷新元数据",
"regenerating_thumbnails": "正在重新生成缩略图",
"remote": "远程",
"remote_assets": "远程项目",
"remove": "移除",
"remove_assets_album_confirmation": "确定要从图库中移除{count, plural, one {#个项目} other {#个项目}}",
"remove_assets_shared_link_confirmation": "确定要从共享链接中移除{count, plural, one {#个项目} other {#个项目}}",
@@ -1556,11 +1568,15 @@
"reset_password": "重置密码",
"reset_people_visibility": "重置人物识别",
"reset_pin_code": "重置PIN码",
"reset_sqlite": "重置 SQLite 数据库",
"reset_sqlite_confirmation": "您确定要重置 SQLite 数据库吗?您需要注销并重新登录才能重新同步数据",
"reset_sqlite_success": "已成功重置 SQLite 数据库",
"reset_to_default": "恢复默认值",
"resolve_duplicates": "处理重复项",
"resolved_all_duplicates": "处理所有重复项",
"restore": "恢复",
"restore_all": "恢复全部",
"restore_trash_action_prompt": "从回收站中恢复了 {count} 项",
"restore_user": "恢复用户",
"restored_asset": "已恢复项目",
"resume": "继续",
@@ -1569,6 +1585,7 @@
"role": "选择用户权限",
"role_editor": "可编辑",
"role_viewer": "仅查看",
"running": "正在运行",
"save": "保存",
"save_to_gallery": "保存到图库",
"saved_api_key": "已保存的 API 密钥",
@@ -1822,6 +1839,7 @@
"storage_quota": "存储配额",
"storage_usage": "已用:{used}/{available}",
"submit": "提交",
"success": "成功",
"suggestions": "建议",
"sunrise_on_the_beach": "海滩上的日出",
"support": "支持",
@@ -1831,6 +1849,8 @@
"sync": "同步",
"sync_albums": "同步相册",
"sync_albums_manual_subtitle": "将所有上传的视频和照片同步到选定的备份相册",
"sync_local": "同步本地",
"sync_remote": "同步远程",
"sync_upload_album_setting_subtitle": "创建照片和视频并上传到 Immich 上的选定相册中",
"tag": "标签",
"tag_assets": "标记项目",
@@ -1841,6 +1861,7 @@
"tag_updated": "已更新标签:{tag}",
"tagged_assets": "{count, plural, one {# 个项目} other {# 个项目}}被加上标签",
"tags": "标签",
"tap_to_run_job": "点击运行作业",
"template": "模版",
"theme": "主题",
"theme_selection": "主题选项",

View File

@@ -1,3 +1,3 @@
{
"flutter": "3.32.6"
"flutter": "3.32.8"
}

View File

@@ -1,5 +1,9 @@
{
"dart.flutterSdkPath": ".fvm/versions/3.32.6",
"dart.flutterSdkPath": ".fvm/versions/3.32.8",
"dart.lineLength": 120,
"[dart]": {
"editor.rulers": [120],
},
"search.exclude": {
"**/.fvm": true
},

View File

@@ -9,6 +9,9 @@
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
formatter:
page_width: 120
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`

View File

@@ -72,20 +72,6 @@ android {
}
}
flavorDimensions "default"
productFlavors {
production {
dimension "default"
applicationId "app.alextran.immich"
}
beta {
dimension "default"
applicationId "app.alextran.immich.beta"
versionNameSuffix "-BETA"
}
}
buildTypes {
debug {
applicationIdSuffix '.debug'

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application android:label="Immich Beta" tools:replace="android:label" />
</manifest>

View File

@@ -88,7 +88,8 @@ data class PlatformAsset (
val width: Long? = null,
val height: Long? = null,
val durationInSeconds: Long,
val orientation: Long
val orientation: Long,
val isFavorite: Boolean
)
{
companion object {
@@ -102,7 +103,8 @@ data class PlatformAsset (
val height = pigeonVar_list[6] as Long?
val durationInSeconds = pigeonVar_list[7] as Long
val orientation = pigeonVar_list[8] as Long
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation)
val isFavorite = pigeonVar_list[9] as Boolean
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite)
}
}
fun toList(): List<Any?> {
@@ -116,6 +118,7 @@ data class PlatformAsset (
height,
durationInSeconds,
orientation,
isFavorite,
)
}
override fun equals(other: Any?): Boolean {

View File

@@ -42,6 +42,7 @@ open class NativeSyncApiImplBase(context: Context) {
MediaStore.MediaColumns.HEIGHT,
MediaStore.MediaColumns.DURATION,
MediaStore.MediaColumns.ORIENTATION,
MediaStore.MediaColumns.IS_FAVORITE,
)
const val HASH_BUFFER_SIZE = 2 * 1024 * 1024
@@ -77,6 +78,7 @@ open class NativeSyncApiImplBase(context: Context) {
val durationColumn = c.getColumnIndexOrThrow(MediaStore.MediaColumns.DURATION)
val orientationColumn =
c.getColumnIndexOrThrow(MediaStore.MediaColumns.ORIENTATION)
val favoriteColumn = c.getColumnIndexOrThrow(MediaStore.MediaColumns.IS_FAVORITE)
while (c.moveToNext()) {
val id = c.getLong(idColumn).toString()
@@ -105,6 +107,7 @@ open class NativeSyncApiImplBase(context: Context) {
else c.getLong(durationColumn) / 1000
val bucketId = c.getString(bucketIdColumn)
val orientation = c.getInt(orientationColumn)
val isFavorite = c.getInt(favoriteColumn) != 0;
val asset = PlatformAsset(
id,
@@ -116,6 +119,7 @@ open class NativeSyncApiImplBase(context: Context) {
height,
duration,
orientation.toLong(),
isFavorite,
)
yield(AssetResult.ValidAsset(asset, bucketId))
}

View File

@@ -69,7 +69,7 @@ class ImageDownloadWorker(
.build()
manager.enqueueUniqueWork(
"$uniqueWorkName-$appWidgetId",
"$uniqueWorkName-$appWidgetId-singleShot",
ExistingWorkPolicy.REPLACE,
workRequest
)

View File

@@ -28,19 +28,21 @@ class MemoryReceiver : GlanceAppWidgetReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val fromMainApp = intent.getBooleanExtra(HomeWidgetPlugin.TRIGGERED_FROM_HOME_WIDGET, false)
val provider = ComponentName(context, MemoryReceiver::class.java)
val glanceIds = AppWidgetManager.getInstance(context).getAppWidgetIds(provider)
// Launch coroutine to setup a single shot if the app requested the update
if (fromMainApp) {
CoroutineScope(Dispatchers.Default).launch {
val provider = ComponentName(context, MemoryReceiver::class.java)
val glanceIds = AppWidgetManager.getInstance(context).getAppWidgetIds(provider)
glanceIds.forEach { widgetID ->
ImageDownloadWorker.singleShot(context, widgetID, WidgetType.MEMORIES)
}
glanceIds.forEach { widgetID ->
ImageDownloadWorker.singleShot(context, widgetID, WidgetType.MEMORIES)
}
}
// make sure the periodic jobs are running
glanceIds.forEach { widgetID ->
ImageDownloadWorker.enqueuePeriodic(context, widgetID, WidgetType.MEMORIES)
}
super.onReceive(context, intent)
}

View File

@@ -28,19 +28,21 @@ class RandomReceiver : GlanceAppWidgetReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val fromMainApp = intent.getBooleanExtra(HomeWidgetPlugin.TRIGGERED_FROM_HOME_WIDGET, false)
val provider = ComponentName(context, RandomReceiver::class.java)
val glanceIds = AppWidgetManager.getInstance(context).getAppWidgetIds(provider)
// Launch coroutine to setup a single shot if the app requested the update
if (fromMainApp) {
CoroutineScope(Dispatchers.Default).launch {
val provider = ComponentName(context, RandomReceiver::class.java)
val glanceIds = AppWidgetManager.getInstance(context).getAppWidgetIds(provider)
glanceIds.forEach { widgetID ->
ImageDownloadWorker.singleShot(context, widgetID, WidgetType.RANDOM)
}
glanceIds.forEach { widgetID ->
ImageDownloadWorker.singleShot(context, widgetID, WidgetType.RANDOM)
}
}
// make sure the periodic jobs are running
glanceIds.forEach { widgetID ->
ImageDownloadWorker.enqueuePeriodic(context, widgetID, WidgetType.RANDOM)
}
super.onReceive(context, intent)
}

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -139,6 +139,7 @@ struct PlatformAsset: Hashable {
var height: Int64? = nil
var durationInSeconds: Int64
var orientation: Int64
var isFavorite: Bool
// swift-format-ignore: AlwaysUseLowerCamelCase
@@ -152,6 +153,7 @@ struct PlatformAsset: Hashable {
let height: Int64? = nilOrValue(pigeonVar_list[6])
let durationInSeconds = pigeonVar_list[7] as! Int64
let orientation = pigeonVar_list[8] as! Int64
let isFavorite = pigeonVar_list[9] as! Bool
return PlatformAsset(
id: id,
@@ -162,7 +164,8 @@ struct PlatformAsset: Hashable {
width: width,
height: height,
durationInSeconds: durationInSeconds,
orientation: orientation
orientation: orientation,
isFavorite: isFavorite
)
}
func toList() -> [Any?] {
@@ -176,6 +179,7 @@ struct PlatformAsset: Hashable {
height,
durationInSeconds,
orientation,
isFavorite,
]
}
static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool {

View File

@@ -28,7 +28,8 @@ extension PHAsset {
width: Int64(pixelWidth),
height: Int64(pixelHeight),
durationInSeconds: Int64(duration),
orientation: 0
orientation: 0,
isFavorite: isFavorite
)
}
}
@@ -171,7 +172,8 @@ class NativeSyncApiImpl: NativeSyncApi {
name: "",
type: 0,
durationInSeconds: 0,
orientation: 0
orientation: 0,
isFavorite: false
)
if (updatedAssets.contains(AssetWrapper(with: predicate))) {
continue

View File

@@ -22,7 +22,7 @@ platform :ios do
path: "./Runner.xcodeproj",
)
increment_version_number(
version_number: "1.135.3"
version_number: "1.136.0"
)
increment_build_number(
build_number: latest_testflight_build_number + 1,

View File

@@ -1,17 +1,6 @@
import 'package:flutter/material.dart';
enum ImmichColorPreset {
indigo,
deepPurple,
pink,
red,
orange,
yellow,
lime,
green,
cyan,
slateGray
}
enum ImmichColorPreset { indigo, deepPurple, pink, red, orange, yellow, lime, green, cyan, slateGray }
const ImmichColorPreset defaultColorPreset = ImmichColorPreset.indigo;
const String defaultColorPresetName = "indigo";

View File

@@ -1,13 +1,6 @@
enum SortOrder {
asc,
desc,
}
enum SortOrder { asc, desc }
enum TextSearchType {
context,
filename,
description,
}
enum TextSearchType { context, filename, description }
enum AssetVisibilityEnum { timeline, hidden, archive, locked }

View File

@@ -2,511 +2,49 @@ import 'package:flutter/material.dart';
const List<ColorFilter> filters = [
//Original
ColorFilter.matrix([
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0]),
//Vintage
ColorFilter.matrix([
0.8,
0.1,
0.1,
0,
20,
0.1,
0.8,
0.1,
0,
20,
0.1,
0.1,
0.8,
0,
20,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([0.8, 0.1, 0.1, 0, 20, 0.1, 0.8, 0.1, 0, 20, 0.1, 0.1, 0.8, 0, 20, 0, 0, 0, 1, 0]),
//Mood
ColorFilter.matrix([
1.2,
0.1,
0.1,
0,
10,
0.1,
1,
0.1,
0,
10,
0.1,
0.1,
1,
0,
10,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.2, 0.1, 0.1, 0, 10, 0.1, 1, 0.1, 0, 10, 0.1, 0.1, 1, 0, 10, 0, 0, 0, 1, 0]),
//Crisp
ColorFilter.matrix([
1.2,
0,
0,
0,
0,
0,
1.2,
0,
0,
0,
0,
0,
1.2,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.2, 0, 0, 0, 0, 0, 1.2, 0, 0, 0, 0, 0, 1.2, 0, 0, 0, 0, 0, 1, 0]),
//Cool
ColorFilter.matrix([
0.9,
0,
0.2,
0,
0,
0,
1,
0.1,
0,
0,
0.1,
0,
1.2,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([0.9, 0, 0.2, 0, 0, 0, 1, 0.1, 0, 0, 0.1, 0, 1.2, 0, 0, 0, 0, 0, 1, 0]),
//Blush
ColorFilter.matrix([
1.1,
0.1,
0.1,
0,
10,
0.1,
1,
0.1,
0,
10,
0.1,
0.1,
1,
0,
5,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.1, 0.1, 0.1, 0, 10, 0.1, 1, 0.1, 0, 10, 0.1, 0.1, 1, 0, 5, 0, 0, 0, 1, 0]),
//Sunkissed
ColorFilter.matrix([
1.3,
0,
0.1,
0,
15,
0,
1.1,
0.1,
0,
10,
0,
0,
0.9,
0,
5,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.3, 0, 0.1, 0, 15, 0, 1.1, 0.1, 0, 10, 0, 0, 0.9, 0, 5, 0, 0, 0, 1, 0]),
//Fresh
ColorFilter.matrix([
1.2,
0,
0,
0,
20,
0,
1.2,
0,
0,
20,
0,
0,
1.1,
0,
20,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.2, 0, 0, 0, 20, 0, 1.2, 0, 0, 20, 0, 0, 1.1, 0, 20, 0, 0, 0, 1, 0]),
//Classic
ColorFilter.matrix([
1.1,
0,
-0.1,
0,
10,
-0.1,
1.1,
0.1,
0,
5,
0,
-0.1,
1.1,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.1, 0, -0.1, 0, 10, -0.1, 1.1, 0.1, 0, 5, 0, -0.1, 1.1, 0, 0, 0, 0, 0, 1, 0]),
//Lomo-ish
ColorFilter.matrix([
1.5,
0,
0.1,
0,
0,
0,
1.45,
0,
0,
0,
0.1,
0,
1.3,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.5, 0, 0.1, 0, 0, 0, 1.45, 0, 0, 0, 0.1, 0, 1.3, 0, 0, 0, 0, 0, 1, 0]),
//Nashville
ColorFilter.matrix([
1.2,
0.15,
-0.15,
0,
15,
0.1,
1.1,
0.1,
0,
10,
-0.05,
0.2,
1.25,
0,
5,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.2, 0.15, -0.15, 0, 15, 0.1, 1.1, 0.1, 0, 10, -0.05, 0.2, 1.25, 0, 5, 0, 0, 0, 1, 0]),
//Valencia
ColorFilter.matrix([
1.15,
0.1,
0.1,
0,
20,
0.1,
1.1,
0,
0,
10,
0.1,
0.1,
1.2,
0,
5,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.15, 0.1, 0.1, 0, 20, 0.1, 1.1, 0, 0, 10, 0.1, 0.1, 1.2, 0, 5, 0, 0, 0, 1, 0]),
//Clarendon
ColorFilter.matrix([
1.2,
0,
0,
0,
10,
0,
1.25,
0,
0,
10,
0,
0,
1.3,
0,
10,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.2, 0, 0, 0, 10, 0, 1.25, 0, 0, 10, 0, 0, 1.3, 0, 10, 0, 0, 0, 1, 0]),
//Moon
ColorFilter.matrix([
0.33,
0.33,
0.33,
0,
0,
0.33,
0.33,
0.33,
0,
0,
0.33,
0.33,
0.33,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([0.33, 0.33, 0.33, 0, 0, 0.33, 0.33, 0.33, 0, 0, 0.33, 0.33, 0.33, 0, 0, 0, 0, 0, 1, 0]),
//Willow
ColorFilter.matrix([
0.5,
0.5,
0.5,
0,
20,
0.5,
0.5,
0.5,
0,
20,
0.5,
0.5,
0.5,
0,
20,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([0.5, 0.5, 0.5, 0, 20, 0.5, 0.5, 0.5, 0, 20, 0.5, 0.5, 0.5, 0, 20, 0, 0, 0, 1, 0]),
//Kodak
ColorFilter.matrix([
1.3,
0.1,
-0.1,
0,
10,
0,
1.25,
0.1,
0,
10,
0,
-0.1,
1.1,
0,
5,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.3, 0.1, -0.1, 0, 10, 0, 1.25, 0.1, 0, 10, 0, -0.1, 1.1, 0, 5, 0, 0, 0, 1, 0]),
//Frost
ColorFilter.matrix([
0.8,
0.2,
0.1,
0,
0,
0.2,
1.1,
0.1,
0,
0,
0.1,
0.1,
1.2,
0,
10,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([0.8, 0.2, 0.1, 0, 0, 0.2, 1.1, 0.1, 0, 0, 0.1, 0.1, 1.2, 0, 10, 0, 0, 0, 1, 0]),
//Night Vision
ColorFilter.matrix([
0.1,
0.95,
0.2,
0,
0,
0.1,
1.5,
0.1,
0,
0,
0.2,
0.7,
0,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([0.1, 0.95, 0.2, 0, 0, 0.1, 1.5, 0.1, 0, 0, 0.2, 0.7, 0, 0, 0, 0, 0, 0, 1, 0]),
//Sunset
ColorFilter.matrix([
1.5,
0.2,
0,
0,
0,
0.1,
0.9,
0.1,
0,
0,
-0.1,
-0.2,
1.3,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.5, 0.2, 0, 0, 0, 0.1, 0.9, 0.1, 0, 0, -0.1, -0.2, 1.3, 0, 0, 0, 0, 0, 1, 0]),
//Noir
ColorFilter.matrix([
1.3,
-0.3,
0.1,
0,
0,
-0.1,
1.2,
-0.1,
0,
0,
0.1,
-0.2,
1.3,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.3, -0.3, 0.1, 0, 0, -0.1, 1.2, -0.1, 0, 0, 0.1, -0.2, 1.3, 0, 0, 0, 0, 0, 1, 0]),
//Dreamy
ColorFilter.matrix([
1.1,
0.1,
0.1,
0,
0,
0.1,
1.1,
0.1,
0,
0,
0.1,
0.1,
1.1,
0,
15,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.1, 0.1, 0.1, 0, 0, 0.1, 1.1, 0.1, 0, 0, 0.1, 0.1, 1.1, 0, 15, 0, 0, 0, 1, 0]),
//Sepia
ColorFilter.matrix([
0.393,
0.769,
0.189,
0,
0,
0.349,
0.686,
0.168,
0,
0,
0.272,
0.534,
0.131,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([0.393, 0.769, 0.189, 0, 0, 0.349, 0.686, 0.168, 0, 0, 0.272, 0.534, 0.131, 0, 0, 0, 0, 0, 1, 0]),
//Radium
ColorFilter.matrix([
1.438,
@@ -554,212 +92,23 @@ const List<ColorFilter> filters = [
0,
]),
//Purple Haze
ColorFilter.matrix([
1.3,
0,
1.2,
0,
0,
0,
1.1,
0,
0,
0,
0.2,
0,
1.3,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.3, 0, 1.2, 0, 0, 0, 1.1, 0, 0, 0, 0.2, 0, 1.3, 0, 0, 0, 0, 0, 1, 0]),
//Lemonade
ColorFilter.matrix([
1.2,
0.1,
0,
0,
0,
0,
1.1,
0.2,
0,
0,
0.1,
0,
0.7,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.2, 0.1, 0, 0, 0, 0, 1.1, 0.2, 0, 0, 0.1, 0, 0.7, 0, 0, 0, 0, 0, 1, 0]),
//Caramel
ColorFilter.matrix([
1.6,
0.2,
0,
0,
0,
0.1,
1.3,
0.1,
0,
0,
0,
0.1,
0.9,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.6, 0.2, 0, 0, 0, 0.1, 1.3, 0.1, 0, 0, 0, 0.1, 0.9, 0, 0, 0, 0, 0, 1, 0]),
//Peachy
ColorFilter.matrix([
1.3,
0.5,
0,
0,
0,
0.2,
1.1,
0.3,
0,
0,
0.1,
0.1,
1.2,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.3, 0.5, 0, 0, 0, 0.2, 1.1, 0.3, 0, 0, 0.1, 0.1, 1.2, 0, 0, 0, 0, 0, 1, 0]),
//Neon
ColorFilter.matrix([
1,
0,
1,
0,
0,
0,
2,
0,
0,
0,
0,
0,
3,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 1, 0]),
//Cold Morning
ColorFilter.matrix([
0.9,
0.1,
0.2,
0,
0,
0,
1,
0.1,
0,
0,
0.1,
0,
1.2,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([0.9, 0.1, 0.2, 0, 0, 0, 1, 0.1, 0, 0, 0.1, 0, 1.2, 0, 0, 0, 0, 0, 1, 0]),
//Lush
ColorFilter.matrix([
0.9,
0.2,
0,
0,
0,
0,
1.2,
0,
0,
0,
0,
0,
1.1,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([0.9, 0.2, 0, 0, 0, 0, 1.2, 0, 0, 0, 0, 0, 1.1, 0, 0, 0, 0, 0, 1, 0]),
//Urban Neon
ColorFilter.matrix([
1.1,
0,
0.3,
0,
0,
0,
0.9,
0.3,
0,
0,
0.3,
0.1,
1.2,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([1.1, 0, 0.3, 0, 0, 0, 0.9, 0.3, 0, 0, 0.3, 0.1, 1.2, 0, 0, 0, 0, 0, 1, 0]),
//Monochrome
ColorFilter.matrix([
0.6,
0.2,
0.2,
0,
0,
0.2,
0.6,
0.2,
0,
0,
0.2,
0.2,
0.7,
0,
0,
0,
0,
0,
1,
0,
]),
ColorFilter.matrix([0.6, 0.2, 0.2, 0, 0, 0.2, 0.6, 0.2, 0, 0, 0.2, 0.2, 0.7, 0, 0, 0, 0, 0, 1, 0]),
];
const List<String> filterNames = [

View File

@@ -7,10 +7,8 @@ const Map<String, Locale> locales = {
'Arabic (ar)': Locale('ar'),
'Bulgarian (bg)': Locale('bg'),
'Catalan (ca)': Locale('ca'),
'Chinese Simplified (zh_CN)':
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'SIMPLIFIED'),
'Chinese Traditional (zh_TW)':
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'),
'Chinese Simplified (zh_CN)': Locale.fromSubtags(languageCode: 'zh', scriptCode: 'SIMPLIFIED'),
'Chinese Traditional (zh_TW)': Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'),
'Croatian (hr)': Locale('hr'),
'Czech (cs)': Locale('cs'),
'Danish (da)': Locale('da'),
@@ -37,10 +35,8 @@ const Map<String, Locale> locales = {
'Portuguese (pt)': Locale('pt'),
'Romanian (ro)': Locale('ro'),
'Russian (ru)': Locale('ru'),
'Serbian Cyrillic (sr_Cyrl)':
Locale.fromSubtags(languageCode: 'sr', scriptCode: 'Cyrl'),
'Serbian Latin (sr_Latn)':
Locale.fromSubtags(languageCode: 'sr', scriptCode: 'Latn'),
'Serbian Cyrillic (sr_Cyrl)': Locale.fromSubtags(languageCode: 'sr', scriptCode: 'Cyrl'),
'Serbian Latin (sr_Latn)': Locale.fromSubtags(languageCode: 'sr', scriptCode: 'Latn'),
'Slovak (sk)': Locale('sk'),
'Slovenian (sl)': Locale('sl'),
'Spanish (es)': Locale('es'),
@@ -55,7 +51,4 @@ const Map<String, Locale> locales = {
const String translationsPath = 'assets/i18n';
const List<Locale> localesNotSupportedByOverpass = [
Locale('el', 'GR'),
Locale('sr', 'Cyrl'),
];
const List<Locale> localesNotSupportedByOverpass = [Locale('el', 'GR'), Locale('sr', 'Cyrl')];

View File

@@ -9,11 +9,7 @@ enum AssetType {
audio,
}
enum AssetState {
local,
remote,
merged,
}
enum AssetState { local, remote, merged }
sealed class BaseAsset {
final String name;
@@ -53,10 +49,8 @@ sealed class BaseAsset {
return const Duration();
}
bool get hasRemote =>
storage == AssetState.remote || storage == AssetState.merged;
bool get hasLocal =>
storage == AssetState.local || storage == AssetState.merged;
bool get hasRemote => storage == AssetState.remote || storage == AssetState.merged;
bool get hasLocal => storage == AssetState.local || storage == AssetState.merged;
bool get isLocalOnly => storage == AssetState.local;
bool get isRemoteOnly => storage == AssetState.remote;

View File

@@ -22,8 +22,7 @@ class LocalAsset extends BaseAsset {
});
@override
AssetState get storage =>
remoteId == null ? AssetState.local : AssetState.merged;
AssetState get storage => remoteId == null ? AssetState.local : AssetState.merged;
@override
String get heroTag => '${id}_${remoteId ?? checksum}';
@@ -54,8 +53,7 @@ class LocalAsset extends BaseAsset {
}
@override
int get hashCode =>
super.hashCode ^ id.hashCode ^ remoteId.hashCode ^ orientation.hashCode;
int get hashCode => super.hashCode ^ id.hashCode ^ remoteId.hashCode ^ orientation.hashCode;
LocalAsset copyWith({
String? id,

View File

@@ -1,11 +1,6 @@
part of 'base_asset.model.dart';
enum AssetVisibility {
timeline,
hidden,
archive,
locked,
}
enum AssetVisibility { timeline, hidden, archive, locked }
// Model for an asset stored in the server
class RemoteAsset extends BaseAsset {
@@ -15,7 +10,6 @@ class RemoteAsset extends BaseAsset {
final AssetVisibility visibility;
final String ownerId;
final String? stackId;
final int stackCount;
const RemoteAsset({
required this.id,
@@ -34,12 +28,10 @@ class RemoteAsset extends BaseAsset {
this.visibility = AssetVisibility.timeline,
super.livePhotoVideoId,
this.stackId,
this.stackCount = 0,
});
@override
AssetState get storage =>
localId == null ? AssetState.remote : AssetState.merged;
AssetState get storage => localId == null ? AssetState.remote : AssetState.merged;
@override
String get heroTag => '${localId ?? checksum}_$id';
@@ -61,7 +53,6 @@ class RemoteAsset extends BaseAsset {
thumbHash: ${thumbHash ?? "<NA>"},
visibility: $visibility,
stackId: ${stackId ?? "<NA>"},
stackCount: $stackCount,
checksum: $checksum,
livePhotoVideoId: ${livePhotoVideoId ?? "<NA>"},
}''';
@@ -77,8 +68,7 @@ class RemoteAsset extends BaseAsset {
ownerId == other.ownerId &&
thumbHash == other.thumbHash &&
visibility == other.visibility &&
stackId == other.stackId &&
stackCount == other.stackCount;
stackId == other.stackId;
}
@override
@@ -89,8 +79,7 @@ class RemoteAsset extends BaseAsset {
localId.hashCode ^
thumbHash.hashCode ^
visibility.hashCode ^
stackId.hashCode ^
stackCount.hashCode;
stackId.hashCode;
RemoteAsset copyWith({
String? id,
@@ -109,7 +98,6 @@ class RemoteAsset extends BaseAsset {
AssetVisibility? visibility,
String? livePhotoVideoId,
String? stackId,
int? stackCount,
}) {
return RemoteAsset(
id: id ?? this.id,
@@ -128,7 +116,6 @@ class RemoteAsset extends BaseAsset {
visibility: visibility ?? this.visibility,
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
stackId: stackId ?? this.stackId,
stackCount: stackCount ?? this.stackCount,
);
}
}

View File

@@ -5,19 +5,13 @@ class DeviceAsset {
final Uint8List hash;
final DateTime modifiedTime;
const DeviceAsset({
required this.assetId,
required this.hash,
required this.modifiedTime,
});
const DeviceAsset({required this.assetId, required this.hash, required this.modifiedTime});
@override
bool operator ==(covariant DeviceAsset other) {
if (identical(this, other)) return true;
return other.assetId == assetId &&
other.hash == hash &&
other.modifiedTime == modifiedTime;
return other.assetId == assetId && other.hash == hash && other.modifiedTime == modifiedTime;
}
@override
@@ -30,11 +24,7 @@ class DeviceAsset {
return 'DeviceAsset(assetId: $assetId, hash: $hash, modifiedTime: $modifiedTime)';
}
DeviceAsset copyWith({
String? assetId,
Uint8List? hash,
DateTime? modifiedTime,
}) {
DeviceAsset copyWith({String? assetId, Uint8List? hash, DateTime? modifiedTime}) {
return DeviceAsset(
assetId: assetId ?? this.assetId,
hash: hash ?? this.hash,

View File

@@ -25,8 +25,7 @@ class ExifInfo {
final int? iso;
final double? exposureSeconds;
bool get hasCoordinates =>
latitude != null && longitude != null && latitude != 0 && longitude != 0;
bool get hasCoordinates => latitude != null && longitude != null && latitude != 0 && longitude != 0;
String get exposureTime {
if (exposureSeconds == null) {

View File

@@ -1,16 +1,5 @@
/// Log levels according to dart logging [Level]
enum LogLevel {
all,
finest,
finer,
fine,
config,
info,
warning,
severe,
shout,
off,
}
enum LogLevel { all, finest, finer, fine, config, info, warning, severe, shout, off }
class LogMessage {
final String message;
@@ -43,12 +32,7 @@ class LogMessage {
@override
int get hashCode {
return message.hashCode ^
level.hashCode ^
createdAt.hashCode ^
logger.hashCode ^
error.hashCode ^
stack.hashCode;
return message.hashCode ^ level.hashCode ^ createdAt.hashCode ^ logger.hashCode ^ error.hashCode ^ stack.hashCode;
}
@override

View File

@@ -0,0 +1,18 @@
import 'package:maplibre_gl/maplibre_gl.dart';
class Marker {
final LatLng location;
final String assetId;
const Marker({required this.location, required this.assetId});
@override
bool operator ==(covariant Marker other) {
if (identical(this, other)) return true;
return other.location == location && other.assetId == assetId;
}
@override
int get hashCode => location.hashCode ^ assetId.hashCode;
}

View File

@@ -13,34 +13,23 @@ enum MemoryTypeEnum {
class MemoryData {
final int year;
const MemoryData({
required this.year,
});
const MemoryData({required this.year});
MemoryData copyWith({
int? year,
}) {
return MemoryData(
year: year ?? this.year,
);
MemoryData copyWith({int? year}) {
return MemoryData(year: year ?? this.year);
}
Map<String, dynamic> toMap() {
return <String, dynamic>{
'year': year,
};
return <String, dynamic>{'year': year};
}
factory MemoryData.fromMap(Map<String, dynamic> map) {
return MemoryData(
year: map['year'] as int,
);
return MemoryData(year: map['year'] as int);
}
String toJson() => json.encode(toMap());
factory MemoryData.fromJson(String source) =>
MemoryData.fromMap(json.decode(source) as Map<String, dynamic>);
factory MemoryData.fromJson(String source) => MemoryData.fromMap(json.decode(source) as Map<String, dynamic>);
@override
String toString() => 'MemoryData(year: $year)';

View File

@@ -55,22 +55,17 @@ class PersonDto {
factory PersonDto.fromMap(Map<String, dynamic> map) {
return PersonDto(
id: map['id'] as String,
birthDate: map['birthDate'] != null
? DateTime.fromMillisecondsSinceEpoch(map['birthDate'] as int)
: null,
birthDate: map['birthDate'] != null ? DateTime.fromMillisecondsSinceEpoch(map['birthDate'] as int) : null,
isHidden: map['isHidden'] as bool,
name: map['name'] as String,
thumbnailPath: map['thumbnailPath'] as String,
updatedAt: map['updatedAt'] != null
? DateTime.fromMillisecondsSinceEpoch(map['updatedAt'] as int)
: null,
updatedAt: map['updatedAt'] != null ? DateTime.fromMillisecondsSinceEpoch(map['updatedAt'] as int) : null,
);
}
String toJson() => json.encode(toMap());
factory PersonDto.fromJson(String source) =>
PersonDto.fromMap(json.decode(source) as Map<String, dynamic>);
factory PersonDto.fromJson(String source) => PersonDto.fromMap(json.decode(source) as Map<String, dynamic>);
@override
bool operator ==(covariant PersonDto other) {
@@ -96,7 +91,7 @@ class PersonDto {
}
// Model for a person stored in the server
class Person {
class DriftPerson {
final String id;
final DateTime createdAt;
final DateTime updatedAt;
@@ -108,7 +103,7 @@ class Person {
final String? color;
final DateTime? birthDate;
const Person({
const DriftPerson({
required this.id,
required this.createdAt,
required this.updatedAt,
@@ -121,7 +116,7 @@ class Person {
this.birthDate,
});
Person copyWith({
DriftPerson copyWith({
String? id,
DateTime? createdAt,
DateTime? updatedAt,
@@ -133,7 +128,7 @@ class Person {
String? color,
DateTime? birthDate,
}) {
return Person(
return DriftPerson(
id: id ?? this.id,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
@@ -164,7 +159,7 @@ class Person {
}
@override
bool operator ==(covariant Person other) {
bool operator ==(covariant DriftPerson other) {
if (identical(this, other)) return true;
return other.id == id &&

View File

@@ -5,21 +5,12 @@ class SearchResult {
final List<BaseAsset> assets;
final int? nextPage;
const SearchResult({
required this.assets,
this.nextPage,
});
const SearchResult({required this.assets, this.nextPage});
int get totalAssets => assets.length;
SearchResult copyWith({
List<BaseAsset>? assets,
int? nextPage,
}) {
return SearchResult(
assets: assets ?? this.assets,
nextPage: nextPage ?? this.nextPage,
);
SearchResult copyWith({List<BaseAsset>? assets, int? nextPage}) {
return SearchResult(assets: assets ?? this.assets, nextPage: nextPage ?? this.nextPage);
}
@override

View File

@@ -8,7 +8,7 @@ enum Setting<T> {
loadOriginalVideo<bool>(StoreKey.loadOriginalVideo, false),
preferRemoteImage<bool>(StoreKey.preferRemoteImage, false),
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, false),
;
enableBackup<bool>(StoreKey.enableBackup, false);
const Setting(this.storeKey, this.defaultValue);

View File

@@ -14,13 +14,7 @@ class Stack {
required this.primaryAssetId,
});
Stack copyWith({
String? id,
DateTime? createdAt,
DateTime? updatedAt,
String? ownerId,
String? primaryAssetId,
}) {
Stack copyWith({String? id, DateTime? createdAt, DateTime? updatedAt, String? ownerId, String? primaryAssetId}) {
return Stack(
id: id ?? this.id,
createdAt: createdAt ?? this.createdAt,
@@ -54,11 +48,7 @@ class Stack {
@override
int get hashCode {
return id.hashCode ^
createdAt.hashCode ^
updatedAt.hashCode ^
ownerId.hashCode ^
primaryAssetId.hashCode;
return id.hashCode ^ createdAt.hashCode ^ updatedAt.hashCode ^ ownerId.hashCode ^ primaryAssetId.hashCode;
}
}
@@ -67,19 +57,13 @@ class StackResponse {
final String primaryAssetId;
final List<String> assetIds;
const StackResponse({
required this.id,
required this.primaryAssetId,
required this.assetIds,
});
const StackResponse({required this.id, required this.primaryAssetId, required this.assetIds});
@override
bool operator ==(covariant StackResponse other) {
if (identical(this, other)) return true;
return other.id == id &&
other.primaryAssetId == primaryAssetId &&
other.assetIds == assetIds;
return other.id == id && other.primaryAssetId == primaryAssetId && other.assetIds == assetIds;
}
@override

View File

@@ -1,17 +1,8 @@
import 'package:immich_mobile/domain/utils/event_stream.dart';
enum GroupAssetsBy {
day,
month,
none;
}
enum GroupAssetsBy { day, month, auto, none }
enum HeaderType {
none,
month,
day,
monthAndDay;
}
enum HeaderType { none, month, day, monthAndDay }
class Bucket {
final int assetCount;
@@ -44,3 +35,13 @@ class TimeBucket extends Bucket {
class TimelineReloadEvent extends Event {
const TimelineReloadEvent();
}
class ScrollToTopEvent extends Event {
const ScrollToTopEvent();
}
class ScrollToDateEvent extends Event {
final DateTime date;
const ScrollToDateEvent(this.date);
}

View File

@@ -74,22 +74,21 @@ quotaSizeInBytes: $quotaSizeInBytes,
bool? isPartnerSharedWith,
int? quotaUsageInBytes,
int? quotaSizeInBytes,
}) =>
UserDto(
id: id ?? this.id,
email: email ?? this.email,
name: name ?? this.name,
isAdmin: isAdmin ?? this.isAdmin,
updatedAt: updatedAt ?? this.updatedAt,
profileImagePath: profileImagePath ?? this.profileImagePath,
avatarColor: avatarColor ?? this.avatarColor,
memoryEnabled: memoryEnabled ?? this.memoryEnabled,
inTimeline: inTimeline ?? this.inTimeline,
isPartnerSharedBy: isPartnerSharedBy ?? this.isPartnerSharedBy,
isPartnerSharedWith: isPartnerSharedWith ?? this.isPartnerSharedWith,
quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes,
quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes,
);
}) => UserDto(
id: id ?? this.id,
email: email ?? this.email,
name: name ?? this.name,
isAdmin: isAdmin ?? this.isAdmin,
updatedAt: updatedAt ?? this.updatedAt,
profileImagePath: profileImagePath ?? this.profileImagePath,
avatarColor: avatarColor ?? this.avatarColor,
memoryEnabled: memoryEnabled ?? this.memoryEnabled,
inTimeline: inTimeline ?? this.inTimeline,
isPartnerSharedBy: isPartnerSharedBy ?? this.isPartnerSharedBy,
isPartnerSharedWith: isPartnerSharedWith ?? this.isPartnerSharedWith,
quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes,
quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes,
);
@override
bool operator ==(covariant UserDto other) {
@@ -143,13 +142,7 @@ class PartnerUserDto {
this.profileImagePath,
});
PartnerUserDto copyWith({
String? id,
String? email,
String? name,
bool? inTimeline,
String? profileImagePath,
}) {
PartnerUserDto copyWith({String? id, String? email, String? name, bool? inTimeline, String? profileImagePath}) {
return PartnerUserDto(
id: id ?? this.id,
email: email ?? this.email,
@@ -175,16 +168,13 @@ class PartnerUserDto {
email: map['email'] as String,
name: map['name'] as String,
inTimeline: map['inTimeline'] as bool,
profileImagePath: map['profileImagePath'] != null
? map['profileImagePath'] as String
: null,
profileImagePath: map['profileImagePath'] != null ? map['profileImagePath'] as String : null,
);
}
String toJson() => json.encode(toMap());
factory PartnerUserDto.fromJson(String source) =>
PartnerUserDto.fromMap(json.decode(source) as Map<String, dynamic>);
factory PartnerUserDto.fromJson(String source) => PartnerUserDto.fromMap(json.decode(source) as Map<String, dynamic>);
@override
String toString() {
@@ -204,10 +194,6 @@ class PartnerUserDto {
@override
int get hashCode {
return id.hashCode ^
email.hashCode ^
name.hashCode ^
inTimeline.hashCode ^
profileImagePath.hashCode;
return id.hashCode ^ email.hashCode ^ name.hashCode ^ inTimeline.hashCode ^ profileImagePath.hashCode;
}
}

View File

@@ -24,18 +24,17 @@ enum AvatarColor {
const AvatarColor(this.value);
Color toColor({bool isDarkTheme = false}) => switch (this) {
AvatarColor.primary =>
isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF),
AvatarColor.pink => const Color.fromARGB(255, 244, 114, 182),
AvatarColor.red => const Color.fromARGB(255, 239, 68, 68),
AvatarColor.yellow => const Color.fromARGB(255, 234, 179, 8),
AvatarColor.blue => const Color.fromARGB(255, 59, 130, 246),
AvatarColor.green => const Color.fromARGB(255, 22, 163, 74),
AvatarColor.purple => const Color.fromARGB(255, 147, 51, 234),
AvatarColor.orange => const Color.fromARGB(255, 234, 88, 12),
AvatarColor.gray => const Color.fromARGB(255, 75, 85, 99),
AvatarColor.amber => const Color.fromARGB(255, 217, 119, 6),
};
AvatarColor.primary => isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF),
AvatarColor.pink => const Color.fromARGB(255, 244, 114, 182),
AvatarColor.red => const Color.fromARGB(255, 239, 68, 68),
AvatarColor.yellow => const Color.fromARGB(255, 234, 179, 8),
AvatarColor.blue => const Color.fromARGB(255, 59, 130, 246),
AvatarColor.green => const Color.fromARGB(255, 22, 163, 74),
AvatarColor.purple => const Color.fromARGB(255, 147, 51, 234),
AvatarColor.orange => const Color.fromARGB(255, 234, 88, 12),
AvatarColor.gray => const Color.fromARGB(255, 75, 85, 99),
AvatarColor.amber => const Color.fromARGB(255, 217, 119, 6),
};
}
class Onboarding {
@@ -194,17 +193,9 @@ class License {
final String activationKey;
final String licenseKey;
const License({
required this.activatedAt,
required this.activationKey,
required this.licenseKey,
});
const License({required this.activatedAt, required this.activationKey, required this.licenseKey});
License copyWith({
DateTime? activatedAt,
String? activationKey,
String? licenseKey,
}) {
License copyWith({DateTime? activatedAt, String? activationKey, String? licenseKey}) {
return License(
activatedAt: activatedAt ?? this.activatedAt,
activationKey: activationKey ?? this.activationKey,
@@ -241,14 +232,11 @@ licenseKey: $licenseKey,
bool operator ==(covariant License other) {
if (identical(this, other)) return true;
return activatedAt == other.activatedAt &&
activationKey == other.activationKey &&
licenseKey == other.licenseKey;
return activatedAt == other.activatedAt && activationKey == other.activationKey && licenseKey == other.licenseKey;
}
@override
int get hashCode =>
activatedAt.hashCode ^ activationKey.hashCode ^ licenseKey.hashCode;
int get hashCode => activatedAt.hashCode ^ activationKey.hashCode ^ licenseKey.hashCode;
}
// Model for a user metadata stored in the server
@@ -259,16 +247,11 @@ class UserMetadata {
final Preferences? preferences;
final License? license;
const UserMetadata({
required this.userId,
required this.key,
this.onboarding,
this.preferences,
this.license,
}) : assert(
onboarding != null || preferences != null || license != null,
'One of onboarding, preferences and license must be provided',
);
const UserMetadata({required this.userId, required this.key, this.onboarding, this.preferences, this.license})
: assert(
onboarding != null || preferences != null || license != null,
'One of onboarding, preferences and license must be provided',
);
UserMetadata copyWith({
String? userId,
@@ -310,10 +293,6 @@ license: ${license ?? "<NA>"},
@override
int get hashCode {
return userId.hashCode ^
key.hashCode ^
onboarding.hashCode ^
preferences.hashCode ^
license.hashCode;
return userId.hashCode ^ key.hashCode ^ onboarding.hashCode ^ preferences.hashCode ^ license.hashCode;
}
}

View File

@@ -13,15 +13,17 @@ class AssetService {
const AssetService({
required RemoteAssetRepository remoteAssetRepository,
required DriftLocalAssetRepository localAssetRepository,
}) : _remoteAssetRepository = remoteAssetRepository,
_localAssetRepository = localAssetRepository,
_platform = const LocalPlatform();
}) : _remoteAssetRepository = remoteAssetRepository,
_localAssetRepository = localAssetRepository,
_platform = const LocalPlatform();
Stream<BaseAsset?> watchAsset(BaseAsset asset) {
final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).id;
return asset is LocalAsset
? _localAssetRepository.watchAsset(id)
: _remoteAssetRepository.watchAsset(id);
return asset is LocalAsset ? _localAssetRepository.watchAsset(id) : _remoteAssetRepository.watchAsset(id);
}
Future<RemoteAsset?> getRemoteAsset(String id) {
return _remoteAssetRepository.get(id);
}
Future<List<RemoteAsset>> getStack(RemoteAsset asset) async {
@@ -40,8 +42,7 @@ class AssetService {
return null;
}
final id =
asset is LocalAsset ? asset.remoteId! : (asset as RemoteAsset).id;
final id = asset is LocalAsset ? asset.remoteId! : (asset as RemoteAsset).id;
return _remoteAssetRepository.getExif(id);
}
@@ -56,8 +57,7 @@ class AssetService {
width = exif?.width ?? asset.width?.toDouble();
height = exif?.height ?? asset.height?.toDouble();
} else if (asset is LocalAsset) {
isFlipped = _platform.isAndroid &&
(asset.orientation == 90 || asset.orientation == 270);
isFlipped = _platform.isAndroid && (asset.orientation == 90 || asset.orientation == 270);
width = asset.width?.toDouble();
height = asset.height?.toDouble();
} else {
@@ -78,10 +78,7 @@ class AssetService {
}
Future<(int local, int remote)> getAssetCounts() async {
return (
await _localAssetRepository.getCount(),
await _remoteAssetRepository.getCount()
);
return (await _localAssetRepository.getCount(), await _remoteAssetRepository.getCount());
}
Future<int> getLocalHashedCount() {

View File

@@ -25,24 +25,20 @@ class HashService {
required NativeSyncApi nativeSyncApi,
this.batchSizeLimit = kBatchHashSizeLimit,
this.batchFileLimit = kBatchHashFileLimit,
}) : _localAlbumRepository = localAlbumRepository,
_localAssetRepository = localAssetRepository,
_storageRepository = storageRepository,
_nativeSyncApi = nativeSyncApi;
}) : _localAlbumRepository = localAlbumRepository,
_localAssetRepository = localAssetRepository,
_storageRepository = storageRepository,
_nativeSyncApi = nativeSyncApi;
Future<void> hashAssets() async {
final Stopwatch stopwatch = Stopwatch()..start();
// Sorted by backupSelection followed by isCloud
final localAlbums = await _localAlbumRepository.getAll(
sortBy: {
SortLocalAlbumsBy.backupSelection,
SortLocalAlbumsBy.isIosSharedAlbum,
},
sortBy: {SortLocalAlbumsBy.backupSelection, SortLocalAlbumsBy.isIosSharedAlbum},
);
for (final album in localAlbums) {
final assetsToHash =
await _localAlbumRepository.getAssetsToHash(album.id);
final assetsToHash = await _localAlbumRepository.getAssetsToHash(album.id);
if (assetsToHash.isNotEmpty) {
await _hashAssets(assetsToHash);
}
@@ -88,8 +84,7 @@ class HashService {
_log.fine("Hashing ${toHash.length} files");
final hashed = <LocalAsset>[];
final hashes =
await _nativeSyncApi.hashPaths(toHash.map((e) => e.path).toList());
final hashes = await _nativeSyncApi.hashPaths(toHash.map((e) => e.path).toList());
assert(
hashes.length == toHash.length,
"Hashes length does not match toHash length: ${hashes.length} != ${toHash.length}",

View File

@@ -21,9 +21,9 @@ class LocalSyncService {
required DriftLocalAlbumRepository localAlbumRepository,
required NativeSyncApi nativeSyncApi,
Platform? platform,
}) : _localAlbumRepository = localAlbumRepository,
_nativeSyncApi = nativeSyncApi,
_platform = platform ?? const LocalPlatform();
}) : _localAlbumRepository = localAlbumRepository,
_nativeSyncApi = nativeSyncApi,
_platform = platform ?? const LocalPlatform();
Future<void> sync({bool full = false}) async {
final Stopwatch stopwatch = Stopwatch()..start();
@@ -66,14 +66,11 @@ class LocalSyncService {
// On iOS, we need to full sync albums that are marked as cloud as the delta sync
// does not include changes for cloud albums. If ignoreIcloudAssets is enabled,
// remove the albums from the local database from the previous sync
final cloudAlbums =
deviceAlbums.where((a) => a.isCloud).toLocalAlbums();
final cloudAlbums = deviceAlbums.where((a) => a.isCloud).toLocalAlbums();
for (final album in cloudAlbums) {
final dbAlbum = dbAlbums.firstWhereOrNull((a) => a.id == album.id);
if (dbAlbum == null) {
_log.warning(
"Cloud album ${album.name} not found in local database. Skipping sync.",
);
_log.warning("Cloud album ${album.name} not found in local database. Skipping sync.");
continue;
}
await updateAlbum(dbAlbum, album);
@@ -95,8 +92,7 @@ class LocalSyncService {
final Stopwatch stopwatch = Stopwatch()..start();
final deviceAlbums = await _nativeSyncApi.getAlbums();
final dbAlbums =
await _localAlbumRepository.getAll(sortBy: {SortLocalAlbumsBy.id});
final dbAlbums = await _localAlbumRepository.getAll(sortBy: {SortLocalAlbumsBy.id});
await diffSortedLists(
dbAlbums,
@@ -120,14 +116,9 @@ class LocalSyncService {
try {
_log.fine("Adding device album ${album.name}");
final assets = album.assetCount > 0
? await _nativeSyncApi.getAssetsForAlbum(album.id)
: <PlatformAsset>[];
final assets = album.assetCount > 0 ? await _nativeSyncApi.getAssetsForAlbum(album.id) : <PlatformAsset>[];
await _localAlbumRepository.upsert(
album,
toUpsert: assets.toLocalAssets(),
);
await _localAlbumRepository.upsert(album, toUpsert: assets.toLocalAssets());
_log.fine("Successfully added device album ${album.name}");
} catch (e, s) {
_log.warning("Error while adding device album", e, s);
@@ -150,9 +141,7 @@ class LocalSyncService {
_log.fine("Syncing device album ${dbAlbum.name}");
if (_albumsEqual(deviceAlbum, dbAlbum)) {
_log.fine(
"Device album ${dbAlbum.name} has not changed. Skipping sync.",
);
_log.fine("Device album ${dbAlbum.name} has not changed. Skipping sync.");
return false;
}
@@ -176,10 +165,7 @@ class LocalSyncService {
@visibleForTesting
// The [deviceAlbum] is expected to be refreshed before calling this method
// with modified time and asset count
Future<bool> checkAddition(
LocalAlbum dbAlbum,
LocalAlbum deviceAlbum,
) async {
Future<bool> checkAddition(LocalAlbum dbAlbum, LocalAlbum deviceAlbum) async {
try {
_log.fine("Fast syncing device album ${dbAlbum.name}");
// Assets has been modified
@@ -188,16 +174,12 @@ class LocalSyncService {
return false;
}
final updatedTime =
(dbAlbum.updatedAt.millisecondsSinceEpoch ~/ 1000) + 1;
final newAssetsCount =
await _nativeSyncApi.getAssetsCountSince(deviceAlbum.id, updatedTime);
final updatedTime = (dbAlbum.updatedAt.millisecondsSinceEpoch ~/ 1000) + 1;
final newAssetsCount = await _nativeSyncApi.getAssetsCountSince(deviceAlbum.id, updatedTime);
// Early return if no new assets were found
if (newAssetsCount == 0) {
_log.fine(
"No new assets found despite album having changes. Proceeding to full sync for ${dbAlbum.name}",
);
_log.fine("No new assets found despite album having changes. Proceeding to full sync for ${dbAlbum.name}");
return false;
}
@@ -207,10 +189,7 @@ class LocalSyncService {
return false;
}
final newAssets = await _nativeSyncApi.getAssetsForAlbum(
deviceAlbum.id,
updatedTimeCond: updatedTime,
);
final newAssets = await _nativeSyncApi.getAssetsForAlbum(deviceAlbum.id, updatedTimeCond: updatedTime);
await _localAlbumRepository.upsert(
deviceAlbum.copyWith(backupSelection: dbAlbum.backupSelection),
@@ -230,18 +209,12 @@ class LocalSyncService {
Future<bool> fullDiff(LocalAlbum dbAlbum, LocalAlbum deviceAlbum) async {
try {
final assetsInDevice = deviceAlbum.assetCount > 0
? await _nativeSyncApi
.getAssetsForAlbum(deviceAlbum.id)
.then((a) => a.toLocalAssets())
: <LocalAsset>[];
final assetsInDb = dbAlbum.assetCount > 0
? await _localAlbumRepository.getAssets(dbAlbum.id)
? await _nativeSyncApi.getAssetsForAlbum(deviceAlbum.id).then((a) => a.toLocalAssets())
: <LocalAsset>[];
final assetsInDb = dbAlbum.assetCount > 0 ? await _localAlbumRepository.getAssets(dbAlbum.id) : <LocalAsset>[];
if (deviceAlbum.assetCount == 0) {
_log.fine(
"Device album ${deviceAlbum.name} is empty. Removing assets from DB.",
);
_log.fine("Device album ${deviceAlbum.name} is empty. Removing assets from DB.");
await _localAlbumRepository.upsert(
deviceAlbum.copyWith(backupSelection: dbAlbum.backupSelection),
toDelete: assetsInDb.map((a) => a.id),
@@ -249,18 +222,11 @@ class LocalSyncService {
return true;
}
final updatedDeviceAlbum = deviceAlbum.copyWith(
backupSelection: dbAlbum.backupSelection,
);
final updatedDeviceAlbum = deviceAlbum.copyWith(backupSelection: dbAlbum.backupSelection);
if (dbAlbum.assetCount == 0) {
_log.fine(
"Device album ${deviceAlbum.name} is empty. Adding assets to DB.",
);
await _localAlbumRepository.upsert(
updatedDeviceAlbum,
toUpsert: assetsInDevice,
);
_log.fine("Device album ${deviceAlbum.name} is empty. Adding assets to DB.");
await _localAlbumRepository.upsert(updatedDeviceAlbum, toUpsert: assetsInDevice);
return true;
}
@@ -292,18 +258,12 @@ class LocalSyncService {
);
if (assetsToUpsert.isEmpty && assetsToDelete.isEmpty) {
_log.fine(
"No asset changes detected in album ${deviceAlbum.name}. Updating metadata.",
);
_log.fine("No asset changes detected in album ${deviceAlbum.name}. Updating metadata.");
_localAlbumRepository.upsert(updatedDeviceAlbum);
return true;
}
await _localAlbumRepository.upsert(
updatedDeviceAlbum,
toUpsert: assetsToUpsert,
toDelete: assetsToDelete,
);
await _localAlbumRepository.upsert(updatedDeviceAlbum, toUpsert: assetsToUpsert, toDelete: assetsToDelete);
return true;
} catch (e, s) {
@@ -321,9 +281,7 @@ class LocalSyncService {
}
bool _albumsEqual(LocalAlbum a, LocalAlbum b) {
return a.name == b.name &&
a.assetCount == b.assetCount &&
a.updatedAt.isAtSameMomentAs(b.updatedAt);
return a.name == b.name && a.assetCount == b.assetCount && a.updatedAt.isAtSameMomentAs(b.updatedAt);
}
}
@@ -333,9 +291,7 @@ extension on Iterable<PlatformAlbum> {
(e) => LocalAlbum(
id: e.id,
name: e.name,
updatedAt: e.updatedAt == null
? DateTime.now()
: DateTime.fromMillisecondsSinceEpoch(e.updatedAt! * 1000),
updatedAt: e.updatedAt == null ? DateTime.now() : DateTime.fromMillisecondsSinceEpoch(e.updatedAt! * 1000),
assetCount: e.assetCount,
),
).toList();
@@ -350,16 +306,13 @@ extension on Iterable<PlatformAsset> {
name: e.name,
checksum: null,
type: AssetType.values.elementAtOrNull(e.type) ?? AssetType.other,
createdAt: e.createdAt == null
? DateTime.now()
: DateTime.fromMillisecondsSinceEpoch(e.createdAt! * 1000),
updatedAt: e.updatedAt == null
? DateTime.now()
: DateTime.fromMillisecondsSinceEpoch(e.updatedAt! * 1000),
createdAt: e.createdAt == null ? DateTime.now() : DateTime.fromMillisecondsSinceEpoch(e.createdAt! * 1000),
updatedAt: e.updatedAt == null ? DateTime.now() : DateTime.fromMillisecondsSinceEpoch(e.updatedAt! * 1000),
width: e.width,
height: e.height,
durationInSeconds: e.durationInSeconds,
orientation: e.orientation,
isFavorite: e.isFavorite,
),
).toList();
}

View File

@@ -56,17 +56,12 @@ class LogService {
}) async {
final instance = LogService._(logRepository, storeRepository, shouldBuffer);
await logRepository.truncate(limit: kLogTruncateLimit);
final level = await instance._storeRepository.tryGet(StoreKey.logLevel) ??
LogLevel.info.index;
final level = await instance._storeRepository.tryGet(StoreKey.logLevel) ?? LogLevel.info.index;
Logger.root.level = Level.LEVELS.elementAtOrNull(level) ?? Level.INFO;
return instance;
}
LogService._(
this._logRepository,
this._storeRepository,
this._shouldBuffer,
) {
LogService._(this._logRepository, this._storeRepository, this._shouldBuffer) {
_logSubscription = Logger.root.onRecord.listen(_handleLogRecord);
}
@@ -90,10 +85,7 @@ class LogService {
if (_shouldBuffer) {
_msgBuffer.add(record);
_flushTimer ??= Timer(
const Duration(seconds: 5),
() => unawaited(flushBuffer()),
);
_flushTimer ??= Timer(const Duration(seconds: 5), () => unawaited(flushBuffer()));
} else {
unawaited(_logRepository.insert(record));
}
@@ -146,9 +138,7 @@ class LoggerUnInitializedException implements Exception {
/// Log levels according to dart logging [Level]
extension LevelDomainToInfraExtension on Level {
LogLevel toLogLevel() =>
LogLevel.values.elementAtOrNull(Level.LEVELS.indexOf(this)) ??
LogLevel.info;
LogLevel toLogLevel() => LogLevel.values.elementAtOrNull(Level.LEVELS.indexOf(this)) ?? LogLevel.info;
}
extension on LogLevel {

View File

@@ -0,0 +1,23 @@
import 'package:immich_mobile/domain/models/map.model.dart';
import 'package:immich_mobile/infrastructure/repositories/map.repository.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
typedef MapMarkerSource = Future<List<Marker>> Function(LatLngBounds? bounds);
typedef MapQuery = ({MapMarkerSource markerSource});
class MapFactory {
final DriftMapRepository _mapRepository;
const MapFactory({required DriftMapRepository mapRepository}) : _mapRepository = mapRepository;
MapService remote(String ownerId) => MapService(_mapRepository.remote(ownerId));
}
class MapService {
final MapMarkerSource _markerSource;
MapService(MapQuery query) : _markerSource = query.markerSource;
Future<List<Marker>> Function(LatLngBounds? bounds) get getMarkers => _markerSource;
}

View File

@@ -13,6 +13,10 @@ class DriftMemoryService {
return _repository.getAll(ownerId);
}
Future<DriftMemory?> get(String memoryId) {
return _repository.get(memoryId);
}
Future<int> getCount() {
return _repository.getCount();
}

View File

@@ -7,10 +7,7 @@ class DriftPartnerService {
final DriftPartnerRepository _driftPartnerRepository;
final PartnerApiRepository _partnerApiRepository;
const DriftPartnerService(
this._driftPartnerRepository,
this._partnerApiRepository,
);
const DriftPartnerService(this._driftPartnerRepository, this._partnerApiRepository);
Future<List<PartnerUserDto>> getSharedWith(String userId) {
return _driftPartnerRepository.getSharedWith(userId);
@@ -20,13 +17,9 @@ class DriftPartnerService {
return _driftPartnerRepository.getSharedBy(userId);
}
Future<List<PartnerUserDto>> getAvailablePartners(
String currentUserId,
) async {
final otherUsers =
await _driftPartnerRepository.getAvailablePartners(currentUserId);
final currentPartners =
await _driftPartnerRepository.getSharedBy(currentUserId);
Future<List<PartnerUserDto>> getAvailablePartners(String currentUserId) async {
final otherUsers = await _driftPartnerRepository.getAvailablePartners(currentUserId);
final currentPartners = await _driftPartnerRepository.getSharedBy(currentUserId);
final available = otherUsers.where((user) {
return !currentPartners.any((partner) => partner.id == user.id);
}).toList();
@@ -41,10 +34,7 @@ class DriftPartnerService {
return;
}
await _partnerApiRepository.update(
partnerId,
inTimeline: !partner.inTimeline,
);
await _partnerApiRepository.update(partnerId, inTimeline: !partner.inTimeline);
await _driftPartnerRepository.toggleShowInTimeline(partner, userId);
}

View File

@@ -0,0 +1,30 @@
import 'dart:async';
import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/infrastructure/repositories/people.repository.dart';
import 'package:immich_mobile/repositories/person_api.repository.dart';
class DriftPeopleService {
final DriftPeopleRepository _repository;
final PersonApiRepository _personApiRepository;
const DriftPeopleService(this._repository, this._personApiRepository);
Future<List<DriftPerson>> getAssetPeople(String assetId) {
return _repository.getAssetPeople(assetId);
}
Future<List<DriftPerson>> getAllPeople() {
return _repository.getAllPeople();
}
Future<int> updateName(String personId, String name) async {
await _personApiRepository.update(personId, name: name);
return _repository.updateName(personId, name);
}
Future<int> updateBrithday(String personId, DateTime birthday) async {
await _personApiRepository.update(personId, birthday: birthday);
return _repository.updateBirthday(personId, birthday);
}
}

View File

@@ -22,11 +22,11 @@ class RemoteAlbumService {
return _repository.getAll();
}
List<RemoteAlbum> sortAlbums(
List<RemoteAlbum> albums,
RemoteAlbumSortMode sortMode, {
bool isReverse = false,
}) {
Future<RemoteAlbum?> get(String albumId) {
return _repository.get(albumId);
}
List<RemoteAlbum> sortAlbums(List<RemoteAlbum> albums, RemoteAlbumSortMode sortMode, {bool isReverse = false}) {
return sortMode.sortFn(albums, isReverse);
}
@@ -44,8 +44,7 @@ class RemoteAlbumService {
filtered = filtered
.where(
(album) =>
album.name.toLowerCase().contains(lowerQuery) ||
album.description.toLowerCase().contains(lowerQuery),
album.name.toLowerCase().contains(lowerQuery) || album.description.toLowerCase().contains(lowerQuery),
)
.toList();
}
@@ -53,12 +52,10 @@ class RemoteAlbumService {
if (userId != null) {
switch (filterMode) {
case QuickFilterMode.myAlbums:
filtered =
filtered.where((album) => album.ownerId == userId).toList();
filtered = filtered.where((album) => album.ownerId == userId).toList();
break;
case QuickFilterMode.sharedWithMe:
filtered =
filtered.where((album) => album.ownerId != userId).toList();
filtered = filtered.where((album) => album.ownerId != userId).toList();
break;
case QuickFilterMode.all:
break;
@@ -68,16 +65,8 @@ class RemoteAlbumService {
return filtered;
}
Future<RemoteAlbum> createAlbum({
required String title,
required List<String> assetIds,
String? description,
}) async {
final album = await _albumApiRepository.createDriftAlbum(
title,
description: description,
assetIds: assetIds,
);
Future<RemoteAlbum> createAlbum({required String title, required List<String> assetIds, String? description}) async {
final album = await _albumApiRepository.createDriftAlbum(title, description: description, assetIds: assetIds);
await _repository.create(album, assetIds);
@@ -119,14 +108,8 @@ class RemoteAlbumService {
return _repository.getAssets(albumId);
}
Future<int> addAssets({
required String albumId,
required List<String> assetIds,
}) async {
final album = await _albumApiRepository.addAssets(
albumId,
assetIds,
);
Future<int> addAssets({required String albumId, required List<String> assetIds}) async {
final album = await _albumApiRepository.addAssets(albumId, assetIds);
await _repository.addAssets(albumId, album.added);
@@ -139,10 +122,7 @@ class RemoteAlbumService {
await _repository.deleteAlbum(albumId);
}
Future<void> addUsers({
required String albumId,
required List<String> userIds,
}) async {
Future<void> addUsers({required String albumId, required List<String> userIds}) async {
await _albumApiRepository.addUsers(albumId, userIds);
return _repository.addUsers(albumId, userIds);

View File

@@ -83,10 +83,10 @@ extension on AssetResponseDto {
extension on AssetTypeEnum {
AssetType toAssetType() => switch (this) {
AssetTypeEnum.IMAGE => AssetType.image,
AssetTypeEnum.VIDEO => AssetType.video,
AssetTypeEnum.AUDIO => AssetType.audio,
AssetTypeEnum.OTHER => AssetType.other,
_ => throw Exception('Unknown AssetType value: $this'),
};
AssetTypeEnum.IMAGE => AssetType.image,
AssetTypeEnum.VIDEO => AssetType.video,
AssetTypeEnum.AUDIO => AssetType.audio,
AssetTypeEnum.OTHER => AssetType.other,
_ => throw Exception('Unknown AssetType value: $this'),
};
}

View File

@@ -9,16 +9,11 @@ final AppSetting = SettingsService(storeService: StoreService.I);
class SettingsService {
final StoreService _storeService;
const SettingsService({required StoreService storeService})
: _storeService = storeService;
const SettingsService({required StoreService storeService}) : _storeService = storeService;
T get<T>(Setting<T> setting) =>
_storeService.get(setting.storeKey, setting.defaultValue);
T get<T>(Setting<T> setting) => _storeService.get(setting.storeKey, setting.defaultValue);
Future<void> set<T>(Setting<T> setting, T value) =>
_storeService.put(setting.storeKey, value);
Future<void> set<T>(Setting<T> setting, T value) => _storeService.put(setting.storeKey, value);
Stream<T> watch<T>(Setting<T> setting) => _storeService
.watch(setting.storeKey)
.map((v) => v ?? setting.defaultValue);
Stream<T> watch<T>(Setting<T> setting) => _storeService.watch(setting.storeKey).map((v) => v ?? setting.defaultValue);
}

View File

@@ -12,8 +12,7 @@ class StoreService {
final Map<int, Object?> _cache = {};
late final StreamSubscription<StoreDto> _storeUpdateSubscription;
StoreService._({required IsarStoreRepository storeRepository})
: _storeRepository = storeRepository;
StoreService._({required IsarStoreRepository storeRepository}) : _storeRepository = storeRepository;
// TODO: Temporary typedef to make minimal changes. Remove this and make the presentation layer access store through a provider
static StoreService? _instance;
@@ -25,16 +24,12 @@ class StoreService {
}
// TODO: Replace the implementation with the one from create after removing the typedef
static Future<StoreService> init({
required IsarStoreRepository storeRepository,
}) async {
static Future<StoreService> init({required IsarStoreRepository storeRepository}) async {
_instance ??= await create(storeRepository: storeRepository);
return _instance!;
}
static Future<StoreService> create({
required IsarStoreRepository storeRepository,
}) async {
static Future<StoreService> create({required IsarStoreRepository storeRepository}) async {
final instance = StoreService._(storeRepository: storeRepository);
await instance._populateCache();
instance._storeUpdateSubscription = instance._listenForChange();
@@ -48,10 +43,9 @@ class StoreService {
}
}
StreamSubscription<StoreDto> _listenForChange() =>
_storeRepository.watchAll().listen((event) {
_cache[event.key.id] = event.value;
});
StreamSubscription<StoreDto> _listenForChange() => _storeRepository.watchAll().listen((event) {
_cache[event.key.id] = event.value;
});
/// Disposes the store and cancels the subscription. To reuse the store call init() again
void dispose() async {

View File

@@ -18,14 +18,14 @@ class SyncStreamService {
required SyncApiRepository syncApiRepository,
required SyncStreamRepository syncStreamRepository,
bool Function()? cancelChecker,
}) : _syncApiRepository = syncApiRepository,
_syncStreamRepository = syncStreamRepository,
_cancelChecker = cancelChecker;
}) : _syncApiRepository = syncApiRepository,
_syncStreamRepository = syncStreamRepository,
_cancelChecker = cancelChecker;
bool get isCancelled => _cancelChecker?.call() ?? false;
Future<void> sync() {
_logger.info("Remote sync request for userr");
_logger.info("Remote sync request for user");
DLog.log("Remote sync request for user");
// Start the sync stream and handle events
return _syncApiRepository.streamChanges(_handleEvents);
@@ -34,9 +34,7 @@ class SyncStreamService {
Future<void> handleWsAssetUploadReadyV1Batch(List<dynamic> batchData) async {
if (batchData.isEmpty) return;
_logger.info(
'Processing batch of ${batchData.length} AssetUploadReadyV1 events',
);
_logger.info('Processing batch of ${batchData.length} AssetUploadReadyV1 events');
final List<SyncAssetV1> assets = [];
final List<SyncAssetExifV1> exifs = [];
@@ -65,22 +63,12 @@ class SyncStreamService {
}
if (assets.isNotEmpty && exifs.isNotEmpty) {
await _syncStreamRepository.updateAssetsV1(
assets,
debugLabel: 'websocket-batch',
);
await _syncStreamRepository.updateAssetsExifV1(
exifs,
debugLabel: 'websocket-batch',
);
await _syncStreamRepository.updateAssetsV1(assets, debugLabel: 'websocket-batch');
await _syncStreamRepository.updateAssetsExifV1(exifs, debugLabel: 'websocket-batch');
_logger.info('Successfully processed ${assets.length} assets in batch');
}
} catch (error, stackTrace) {
_logger.severe(
"Error processing AssetUploadReadyV1 websocket batch events",
error,
stackTrace,
);
_logger.severe("Error processing AssetUploadReadyV1 websocket batch events", error, stackTrace);
}
}
@@ -114,10 +102,7 @@ class SyncStreamService {
batch.clear();
}
Future<void> _handleSyncData(
SyncEntityType type,
Iterable<Object> data,
) async {
Future<void> _handleSyncData(SyncEntityType type, Iterable<Object> data) async {
_logger.fine("Processing sync data for $type of length ${data.length}");
switch (type) {
case SyncEntityType.userV1:
@@ -135,30 +120,15 @@ class SyncStreamService {
case SyncEntityType.assetExifV1:
return _syncStreamRepository.updateAssetsExifV1(data.cast());
case SyncEntityType.partnerAssetV1:
return _syncStreamRepository.updateAssetsV1(
data.cast(),
debugLabel: 'partner',
);
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner');
case SyncEntityType.partnerAssetBackfillV1:
return _syncStreamRepository.updateAssetsV1(
data.cast(),
debugLabel: 'partner backfill',
);
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner backfill');
case SyncEntityType.partnerAssetDeleteV1:
return _syncStreamRepository.deleteAssetsV1(
data.cast(),
debugLabel: "partner",
);
return _syncStreamRepository.deleteAssetsV1(data.cast(), debugLabel: "partner");
case SyncEntityType.partnerAssetExifV1:
return _syncStreamRepository.updateAssetsExifV1(
data.cast(),
debugLabel: 'partner',
);
return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'partner');
case SyncEntityType.partnerAssetExifBackfillV1:
return _syncStreamRepository.updateAssetsExifV1(
data.cast(),
debugLabel: 'partner backfill',
);
return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'partner backfill');
case SyncEntityType.albumV1:
return _syncStreamRepository.updateAlbumsV1(data.cast());
case SyncEntityType.albumDeleteV1:
@@ -166,39 +136,21 @@ class SyncStreamService {
case SyncEntityType.albumUserV1:
return _syncStreamRepository.updateAlbumUsersV1(data.cast());
case SyncEntityType.albumUserBackfillV1:
return _syncStreamRepository.updateAlbumUsersV1(
data.cast(),
debugLabel: 'backfill',
);
return _syncStreamRepository.updateAlbumUsersV1(data.cast(), debugLabel: 'backfill');
case SyncEntityType.albumUserDeleteV1:
return _syncStreamRepository.deleteAlbumUsersV1(data.cast());
case SyncEntityType.albumAssetV1:
return _syncStreamRepository.updateAssetsV1(
data.cast(),
debugLabel: 'album',
);
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album');
case SyncEntityType.albumAssetBackfillV1:
return _syncStreamRepository.updateAssetsV1(
data.cast(),
debugLabel: 'album backfill',
);
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album backfill');
case SyncEntityType.albumAssetExifV1:
return _syncStreamRepository.updateAssetsExifV1(
data.cast(),
debugLabel: 'album',
);
return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'album');
case SyncEntityType.albumAssetExifBackfillV1:
return _syncStreamRepository.updateAssetsExifV1(
data.cast(),
debugLabel: 'album backfill',
);
return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'album backfill');
case SyncEntityType.albumToAssetV1:
return _syncStreamRepository.updateAlbumToAssetsV1(data.cast());
case SyncEntityType.albumToAssetBackfillV1:
return _syncStreamRepository.updateAlbumToAssetsV1(
data.cast(),
debugLabel: 'backfill',
);
return _syncStreamRepository.updateAlbumToAssetsV1(data.cast(), debugLabel: 'backfill');
case SyncEntityType.albumToAssetDeleteV1:
return _syncStreamRepository.deleteAlbumToAssetsV1(data.cast());
// No-op. SyncAckV1 entities are checkpoints in the sync stream
@@ -218,28 +170,15 @@ class SyncStreamService {
case SyncEntityType.stackDeleteV1:
return _syncStreamRepository.deleteStacksV1(data.cast());
case SyncEntityType.partnerStackV1:
return _syncStreamRepository.updateStacksV1(
data.cast(),
debugLabel: 'partner',
);
return _syncStreamRepository.updateStacksV1(data.cast(), debugLabel: 'partner');
case SyncEntityType.partnerStackBackfillV1:
return _syncStreamRepository.updateStacksV1(
data.cast(),
debugLabel: 'partner backfill',
);
return _syncStreamRepository.updateStacksV1(data.cast(), debugLabel: 'partner backfill');
case SyncEntityType.partnerStackDeleteV1:
return _syncStreamRepository.deleteStacksV1(
data.cast(),
debugLabel: 'partner',
);
return _syncStreamRepository.deleteStacksV1(data.cast(), debugLabel: 'partner');
case SyncEntityType.userMetadataV1:
return _syncStreamRepository.updateUserMetadatasV1(
data.cast(),
);
return _syncStreamRepository.updateUserMetadatasV1(data.cast());
case SyncEntityType.userMetadataDeleteV1:
return _syncStreamRepository.deleteUserMetadatasV1(
data.cast(),
);
return _syncStreamRepository.deleteUserMetadatasV1(data.cast());
case SyncEntityType.personV1:
return _syncStreamRepository.updatePeopleV1(data.cast());
case SyncEntityType.personDeleteV1:

View File

@@ -10,35 +10,29 @@ import 'package:immich_mobile/domain/services/setting.service.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
import 'package:immich_mobile/utils/async_mutex.dart';
import 'package:logging/logging.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
typedef TimelineAssetSource = Future<List<BaseAsset>> Function(
int index,
int count,
);
typedef TimelineAssetSource = Future<List<BaseAsset>> Function(int index, int count);
typedef TimelineBucketSource = Stream<List<Bucket>> Function();
typedef TimelineQuery = ({
TimelineAssetSource assetSource,
TimelineBucketSource bucketSource,
});
typedef TimelineQuery = ({TimelineAssetSource assetSource, TimelineBucketSource bucketSource});
class TimelineFactory {
final DriftTimelineRepository _timelineRepository;
final SettingsService _settingsService;
const TimelineFactory({
required DriftTimelineRepository timelineRepository,
required SettingsService settingsService,
}) : _timelineRepository = timelineRepository,
_settingsService = settingsService;
const TimelineFactory({required DriftTimelineRepository timelineRepository, required SettingsService settingsService})
: _timelineRepository = timelineRepository,
_settingsService = settingsService;
GroupAssetsBy get groupBy =>
GroupAssetsBy.values[_settingsService.get(Setting.groupAssetsBy)];
GroupAssetsBy get groupBy {
final group = GroupAssetsBy.values[_settingsService.get(Setting.groupAssetsBy)];
// We do not support auto grouping in the new timeline yet, fallback to day grouping
return group == GroupAssetsBy.auto ? GroupAssetsBy.day : group;
}
TimelineService main(List<String> timelineUsers) =>
TimelineService(_timelineRepository.main(timelineUsers, groupBy));
TimelineService main(List<String> timelineUsers) => TimelineService(_timelineRepository.main(timelineUsers, groupBy));
TimelineService localAlbum({required String albumId}) =>
TimelineService(_timelineRepository.localAlbum(albumId, groupBy));
@@ -46,29 +40,26 @@ class TimelineFactory {
TimelineService remoteAlbum({required String albumId}) =>
TimelineService(_timelineRepository.remoteAlbum(albumId, groupBy));
TimelineService remoteAssets(String userId) =>
TimelineService(_timelineRepository.remote(userId, groupBy));
TimelineService remoteAssets(String userId) => TimelineService(_timelineRepository.remote(userId, groupBy));
TimelineService favorite(String userId) =>
TimelineService(_timelineRepository.favorite(userId, groupBy));
TimelineService favorite(String userId) => TimelineService(_timelineRepository.favorite(userId, groupBy));
TimelineService trash(String userId) =>
TimelineService(_timelineRepository.trash(userId, groupBy));
TimelineService trash(String userId) => TimelineService(_timelineRepository.trash(userId, groupBy));
TimelineService archive(String userId) =>
TimelineService(_timelineRepository.archived(userId, groupBy));
TimelineService archive(String userId) => TimelineService(_timelineRepository.archived(userId, groupBy));
TimelineService lockedFolder(String userId) =>
TimelineService(_timelineRepository.locked(userId, groupBy));
TimelineService lockedFolder(String userId) => TimelineService(_timelineRepository.locked(userId, groupBy));
TimelineService video(String userId) =>
TimelineService(_timelineRepository.video(userId, groupBy));
TimelineService video(String userId) => TimelineService(_timelineRepository.video(userId, groupBy));
TimelineService place(String place) =>
TimelineService(_timelineRepository.place(place, groupBy));
TimelineService place(String place) => TimelineService(_timelineRepository.place(place, groupBy));
TimelineService fromAssets(List<BaseAsset> assets) =>
TimelineService(_timelineRepository.fromAssets(assets));
TimelineService person(String userId, String personId) =>
TimelineService(_timelineRepository.person(userId, personId, groupBy));
TimelineService fromAssets(List<BaseAsset> assets) => TimelineService(_timelineRepository.fromAssets(assets));
TimelineService map(LatLngBounds bounds) => TimelineService(_timelineRepository.map(bounds, groupBy));
}
class TimelineService {
@@ -78,26 +69,18 @@ class TimelineService {
int _bufferOffset = 0;
List<BaseAsset> _buffer = [];
StreamSubscription? _bucketSubscription;
final _log = Logger('TimelineService');
int _totalAssets = 0;
int get totalAssets => _totalAssets;
TimelineService(TimelineQuery query)
: this._(
assetSource: query.assetSource,
bucketSource: query.bucketSource,
);
TimelineService(TimelineQuery query) : this._(assetSource: query.assetSource, bucketSource: query.bucketSource);
TimelineService._({
required TimelineAssetSource assetSource,
required TimelineBucketSource bucketSource,
}) : _assetSource = assetSource,
_bucketSource = bucketSource {
TimelineService._({required TimelineAssetSource assetSource, required TimelineBucketSource bucketSource})
: _assetSource = assetSource,
_bucketSource = bucketSource {
_bucketSubscription = _bucketSource().listen((buckets) {
_mutex.run(() async {
final totalAssets =
buckets.fold<int>(0, (acc, bucket) => acc + bucket.assetCount);
final totalAssets = buckets.fold<int>(0, (acc, bucket) => acc + bucket.assetCount);
if (totalAssets == 0) {
_bufferOffset = 0;
@@ -112,10 +95,7 @@ class TimelineService {
count = kTimelineAssetLoadBatchSize;
} else {
offset = _bufferOffset;
count = math.min(
_buffer.length,
totalAssets - _bufferOffset,
);
count = math.min(_buffer.length, totalAssets - _bufferOffset);
}
_buffer = await _assetSource(offset, count);
_bufferOffset = offset;
@@ -130,10 +110,9 @@ class TimelineService {
Stream<List<Bucket>> Function() get watchBuckets => _bucketSource;
Future<List<BaseAsset>?> loadAssets(int index, int count) =>
_mutex.run(() => _loadAssets(index, count));
Future<List<BaseAsset>> loadAssets(int index, int count) => _mutex.run(() => _loadAssets(index, count));
Future<List<BaseAsset>?> _loadAssets(int index, int count) async {
Future<List<BaseAsset>> _loadAssets(int index, int count) async {
if (hasRange(index, count)) {
return getAssets(index, count);
}
@@ -144,10 +123,7 @@ class TimelineService {
// make sure to load a meaningful amount of data (and not only the requested slice)
// otherwise, each call to [loadAssets] would result in DB call trashing performance
// fills small requests to [kTimelineAssetLoadBatchSize], adds some legroom into the opposite scroll direction for large requests
final len = math.max(
kTimelineAssetLoadBatchSize,
count + kTimelineAssetLoadOppositeSize,
);
final len = math.max(kTimelineAssetLoadBatchSize, count + kTimelineAssetLoadOppositeSize);
// when scrolling forward, start shortly before the requested offset
// when scrolling backward, end shortly after the requested offset to guard against the user scrolling
// in the other direction a tiny bit resulting in another required load from the DB
@@ -171,21 +147,18 @@ class TimelineService {
index + count <= _bufferOffset + _buffer.length &&
index + count <= _totalAssets;
List<BaseAsset>? getAssets(int index, int count) {
List<BaseAsset> getAssets(int index, int count) {
if (!hasRange(index, count)) {
_log.warning('TimelineService::getAssets Index out of range');
return null;
throw RangeError('TimelineService::getAssets Index out of range');
}
int start = index - _bufferOffset;
return _buffer.slice(start, start + count);
}
// Pre-cache assets around the given index for asset viewer
Future<void> preCacheAssets(int index) =>
_mutex.run(() => _loadAssets(index, math.min(5, _totalAssets - index)));
Future<void> preCacheAssets(int index) => _mutex.run(() => _loadAssets(index, math.min(5, _totalAssets - index)));
BaseAsset getRandomAsset() =>
_buffer.elementAt(math.Random().nextInt(_buffer.length));
BaseAsset getRandomAsset() => _buffer.elementAt(math.Random().nextInt(_buffer.length));
BaseAsset getAsset(int index) {
if (!hasRange(index, 1)) {

View File

@@ -18,9 +18,9 @@ class UserService {
required IsarUserRepository isarUserRepository,
required UserApiRepository userApiRepository,
required StoreService storeService,
}) : _isarUserRepository = isarUserRepository,
_userApiRepository = userApiRepository,
_storeService = storeService;
}) : _isarUserRepository = isarUserRepository,
_userApiRepository = userApiRepository,
_storeService = storeService;
UserDto getMyUser() {
return _storeService.get(StoreKey.currentUser);
@@ -44,10 +44,7 @@ class UserService {
Future<String?> createProfileImage(String name, Uint8List image) async {
try {
final path = await _userApiRepository.createProfileImage(
name: name,
data: image,
);
final path = await _userApiRepository.createProfileImage(name: name, data: image);
final updatedUser = getMyUser().copyWith(profileImagePath: path);
await _storeService.put(StoreKey.currentUser, updatedUser);
await _isarUserRepository.update(updatedUser);

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