Compare commits

...

155 Commits

Author SHA1 Message Date
mertalev
3af4b8d7a7 sync add 2026-02-14 07:08:25 -05:00
mertalev
1989a8bec2 fix bottleneck 2026-02-14 06:28:09 -05:00
mertalev
0e1f61176a better serial queue handling 2026-02-14 04:42:29 -05:00
mertalev
0160e6fd5f fix bottleneck 2026-02-14 04:14:52 -05:00
mertalev
839fb61340 fix copy race 2026-02-14 03:32:59 -05:00
mertalev
6b04fa3f94 migration 2026-02-14 03:11:55 -05:00
mertalev
947f00ac9d better debounce 2026-02-14 02:47:39 -05:00
mertalev
8bfacda3da retry 2026-02-14 02:22:42 -05:00
mertalev
b9fceeef75 fix index 2026-02-14 01:25:36 -05:00
mertalev
b05848f5ab tetris 2026-02-14 01:13:13 -05:00
mertalev
27de6e7c1a fixes 2026-02-14 00:57:08 -05:00
mertalev
880b2ab665 generate migration 2026-02-13 23:39:22 -05:00
mertalev
5a7b298d02 use COPY 2026-02-13 21:45:35 -05:00
mertalev
4ee7a39e7a refactor 2026-02-13 21:00:04 -05:00
mertalev
c5c8fc56a5 pg impl 2026-02-12 20:42:38 -05:00
mertalev
295ab7a11a sql-tools changes 2026-02-12 20:30:18 -05:00
Jason Rasmussen
8ef4e4d452 feat: schema-check (#25904) 2026-02-12 17:59:00 -05:00
Christos Longros
7413356a2f fix: clarify external domain translation (#26009)
* fix: clarify external domain setting is used for emails too (#24950)

* Update i18n/en.json

Co-authored-by: Jason Rasmussen <jason@rasm.me>

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2026-02-12 14:01:51 -05:00
Jason Rasmussen
5bf4e9595c refactor: purchase store (#25734) 2026-02-12 13:32:17 -05:00
renovate[bot]
6c0c4b3dda chore(deps): update docker.io/valkey/valkey:9 docker digest to 930b414 (#26087)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-12 12:36:46 -05:00
Yaros
206a208410 feat(web): change link expiration logic & presets (#26064)
* feat(web): link expiration presets

* refactor: implement suggestions

* chore: remove createdAt prop

* fix: tests

* fix: button keys
2026-02-12 18:27:49 +01:00
Jason Rasmussen
72cef8b94b feat: shared link login (#25678) 2026-02-12 12:08:38 -05:00
Jason Rasmussen
81c93101a0 feat: verify permissions (#25647) 2026-02-12 12:08:20 -05:00
Christos Longros
b06c21325e fix(mobile): prevent nav bar label text wrapping (#26011)
* fix(mobile): prevent nav bar label text wrapping (#25921)

* chore: fix format

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-02-12 16:57:03 +00:00
Bastian KÃļcher
730b770e67 fix(mobile): timeline orientation & foldable phones handling (#25088)
* [mobile]: Fix timeline handling on foldable phones + ensuring that images are not cut off

This fixes the handling of unfolding the phone while having the application opened. So,
the timeline is correctly rescaled and the current position is kept.
Besides that it fixes a bug with the ordering which lead to images being "cut off" at the right side
of the screen.

* refactor + cleanup

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-02-12 22:20:00 +05:30
Michel Heusschen
b85f6f3fce fix(web): add missing @immich/ui translations (#26143) 2026-02-12 16:42:35 +00:00
Min Idzelis
81f592ca52 feat: remove Cache API, rework preload(), cancel() and fetch() (#25289)
* feat: remove Cache API, rework preload(), cancel() and fetch()

perf - replace broadcast channel with direct postMessage

* remove sw response handling

* review comments
2026-02-12 11:25:20 -05:00
renovate[bot]
a62e8ed179 fix(deps): update typescript-projects (#25549)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-02-12 16:24:04 +00:00
Michel Heusschen
1cf3a80840 fix(web): show correct assets in memory gallery (#26157) 2026-02-12 11:17:09 -05:00
Klenner Martins Barros
9f6dbf710c fix(web): improve api key modal responsiveness (#26151) 2026-02-12 17:13:09 +01:00
Michel Heusschen
f207f99e86 fix(web): prevent event manager from throwing error (#26156) 2026-02-12 11:09:15 -05:00
Thomas
0d35231dfd chore(mobile): cleanup server storage info (#26038)
The server storage info has a lot of whitespace due to the ListTile.
Converting it to be inline makes the styling appear more intentional.

There are also a few semantically relevant list items in the app bar
dialog which have been grouped together.
2026-02-12 21:09:55 +05:30
shenlong
675bbf3ac3 chore: remove unused key and fix casing for recent_albums (#24691)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-02-12 16:06:22 +01:00
Weblate (bot)
c45450b6ac chore(web): update translations (#26118)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/lt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ro/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ta/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/th/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/tr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/
Translation: Immich/immich

Co-authored-by: Anton Palmqvist <apq@users.noreply.hosted.weblate.org>
Co-authored-by: PPNplus <ppnplus@protonmail.com>
Co-authored-by: Rishi <rishikesh.200@gmail.com>
Co-authored-by: TV Box <realceday.tvbox@gmail.com>
Co-authored-by: Vivek M <vivekmalhotra004@gmail.com>
Co-authored-by: WellsTsai <dan50907@gmail.com>
Co-authored-by: czlevi7 <czlevi7@gmail.com>
Co-authored-by: jässin <aouanijassin@gmail.com>
Co-authored-by: muziqaz <muziqaz@users.noreply.hosted.weblate.org>
Co-authored-by: pyccl <changcongliang@163.com>
2026-02-12 15:02:07 +00:00
renovate[bot]
fea6e8d9f3 chore(deps): update dependency python-multipart to v0.0.22 [security] (#25559) 2026-02-12 09:32:59 -05:00
renovate[bot]
27ebbab1d9 fix(deps): update dependency pillow to v12 [security] (#26142) 2026-02-12 09:32:17 -05:00
renovate[bot]
4647ecf2ea chore(deps): update machine-learning (#25067) 2026-02-12 09:31:13 -05:00
shenlong
78c8f1d5a9 chore: add indexes for foreign keys (#25925)
* chore: add indexes for foreign keys

* update idx_asset_face_person_id index

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-02-12 19:58:31 +05:30
shenlong
ec4de54ea2 fix: null local date time in timeline queries (#26133)
* fix: null local date time in timeline queries

* refactor effectiveCreatedAt

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-02-12 19:58:00 +05:30
Peter Ombodi
420cd5193b fix(mobile): Login routing on Splash screen (#26128)
* fix(mobile): fix Login routing on Splash screen

* fix(mobile): remove _duplicateGuard from the LoginRoute
revert changes in splash_screen

---------

Co-authored-by: Peter Ombodi <peter.ombodi@gmail.com>
2026-02-12 19:57:43 +05:30
Daniel Dietzler
7e0356e227 refactor: more small tests (#26159) 2026-02-12 08:34:32 -05:00
Michel Heusschen
913904f418 fix(web): escape shortcut handling (#26096) 2026-02-11 18:55:28 +01:00
Daniel Dietzler
e54678e0d6 refactor: small tests (#26141) 2026-02-11 11:49:00 -05:00
Daniel Dietzler
222c90b7b7 chore: remove album stubs (#26130) 2026-02-11 09:12:46 -05:00
Michel Heusschen
1c1a000c78 fix(web): use locale for date picker (#26125) 2026-02-11 11:39:30 +01:00
Jason Rasmussen
458d5f0f8f fix: ignore checksum constraint error when logging (#26113) 2026-02-10 20:43:55 +00:00
Jason Rasmussen
4c948647fc chore: asset-job medium tests (#26111) 2026-02-10 13:41:40 -05:00
github-actions
3be8e265cd chore: version v2.5.6 2026-02-10 18:24:40 +00:00
Weblate (bot)
e3c4e0197a chore(web): update translations (#25947)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ar/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/bg/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/bn/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ca/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/da/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de_CH/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/el/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/eo/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/et/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ga/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/gl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/id/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ja/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ka/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/kn/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/lt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/lv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nn/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sq/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/th/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/tr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/uk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/
Translation: Immich/immich

Co-authored-by: Adam HavrÃĄnek <adamhavra@seznam.cz>
Co-authored-by: Ahmed Khaleel Shihab <ahmed91shihab@gmail.com>
Co-authored-by: AindriÃē Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Arif Budiman <arifpedia@gmail.com>
Co-authored-by: Arnie97 <arnie97@gmail.com>
Co-authored-by: David Krizak <lavidk@icloud.com>
Co-authored-by: DevServs <bonov@mail.ru>
Co-authored-by: Gautam Pai <gautam.gp4207@gmail.com>
Co-authored-by: Gilgalard Elendil <Gilgalard11@gmail.com>
Co-authored-by: Gisle-Andre Larsen <gisleandre@hotmail.com>
Co-authored-by: Guillaume Petit <gpetit@edelzone.fr>
Co-authored-by: Guillermo Ramos Santos <guillermo.ramosantos@gmail.com>
Co-authored-by: HackingAll <hacking.all.YT@gmail.com>
Co-authored-by: Hans Cats <hanscats@gmail.com>
Co-authored-by: Happy <59247878+happy2452354@users.noreply.github.com>
Co-authored-by: Haru Ijima <haruijimakun@gmail.com>
Co-authored-by: Hurricane_32 <rodrigorimo@hotmail.com>
Co-authored-by: Indrek Haav <indrek.haav@hotmail.com>
Co-authored-by: Ivan Dimitrov <idimitrov08@gmail.com>
Co-authored-by: Jozef Gaal <preklady@mayday.sk>
Co-authored-by: Julius Lehmann <julius.lehmann.privat@gmail.com>
Co-authored-by: Luuk Heijnen <luukheijnen1@gmail.com>
Co-authored-by: MarcSerraPeralta <marcserraperalta@gmail.com>
Co-authored-by: Masani Amin Yasin <langugetranslategay.taps663@silomails.com>
Co-authored-by: MatjaÅž T. <matjaz@moj-svet.si>
Co-authored-by: MārtiÅ†ÅĄ Bruņenieks <martinsb@gmail.com>
Co-authored-by: Olaf Nielsen <solluh@mail.de>
Co-authored-by: Oleksandr Yurov <oyurov@icloud.com>
Co-authored-by: Philipp Frauenfelder <philipp.frauenfelder@gmail.com>
Co-authored-by: Reetryyy <kaxa.kikalishvili.kk@gmail.com>
Co-authored-by: Romo <romo@romo.al>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: Skanda <skillwiz94@gmail.com>
Co-authored-by: Sylvain Pichon <service@spichon.fr>
Co-authored-by: TV Box <realceday.tvbox@gmail.com>
Co-authored-by: Tage Lauritsen <tage@tunenet.dk>
Co-authored-by: Tim Morley <weblate.3919org@timsk.org>
Co-authored-by: Ulices <hasecilu@tuta.io>
Co-authored-by: User 123456789 <user123456789@users.noreply.hosted.weblate.org>
Co-authored-by: Veerasak Kritsanapraphan <veerasak.kritsanapraphan@gmail.com>
Co-authored-by: albanobattistella <albano_battistella@hotmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: chamdim <chamdim@protonmail.com>
Co-authored-by: dvbthien <dvbthien@users.noreply.hosted.weblate.org>
Co-authored-by: ea stanley <hang4ea@gmail.com>
Co-authored-by: muziqaz <muziqaz@users.noreply.hosted.weblate.org>
Co-authored-by: nobo73 <noboru.tanaka@gmail.com>
Co-authored-by: pyccl <changcongliang@163.com>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: Óscar FernÃĄndez Díaz <42654671+oscfdezdz@users.noreply.github.com>
2026-02-10 18:22:41 +00:00
NicolÃ˛ Maria Semprini
1ddb8f0667 feat: jxl browser support detection (#25599) 2026-02-10 12:49:09 -05:00
Alex
adfb003d03 fix: local date time group fall back (#26110)
* fix: local date time group fall back

* remove else clause
2026-02-10 11:47:13 -06:00
Brandon Wees
8c8b11f80c fix: person thumbnail generation on edited assets (#26112)
* fix: person thumbnail generation on edited assets

* chore: sql sync
2026-02-10 11:38:43 -06:00
Claudio
90d554947f docs: update ml-hardware-acceleration.md (#25755)
Update ml-hardware-acceleration.md

Invert the lines about editing the docker-compose.yml file to have users add the tag to the image first, then uncomment the extends section. This should help users follow the instructions as they flow through the YAML file.
2026-02-10 12:28:21 -05:00
Paul Makles
caeba5063b refactor(server): move database restores code into a service (#25918)
* fix(server): use provided database name/username for restore & ensure name is not mangled

fixes #25633

Signed-off-by: izzy <me@insrt.uk>

* chore: add db switch back but with comments

Signed-off-by: izzy <me@insrt.uk>

* refactor: no need to restore database since it's not technically possible
chore: late fallback for username in parameter builder

Signed-off-by: izzy <me@insrt.uk>

* chore: type fix

Signed-off-by: izzy <me@insrt.uk>

* refactor: move db backup code into service

* test: check SQL sent to psql

* chore: remove todo

Signed-off-by: izzy <me@insrt.uk>

---------

Signed-off-by: izzy <me@insrt.uk>
2026-02-10 12:12:27 -05:00
Matthew Momjian
280174026f feat(deployment): rootless compose file (#25931)
rootless deployment
2026-02-10 12:11:44 -05:00
Michel Heusschen
a9e0fa43fa fix: correctly cancel select all assets (#26067) 2026-02-10 11:47:23 -05:00
Jason Rasmussen
e6e56d75e2 fix(web): refresh text (#26071) 2026-02-10 16:42:03 +00:00
Brandon Wees
0886281dd8 fix: create face exif orientation handling (#26108)
* fix: handle exif orientation when creating face

* chore: tests
2026-02-10 11:30:34 -05:00
renovate[bot]
32dea76a92 chore(deps): update dependency @types/node to ^24.10.11 (#26088)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-10 11:24:33 -05:00
Min Idzelis
6af534fe4c feat: run maintenance tests in isolation, share containers between all â€Ļ (#25856)
* feat: run maintance tests in isolation, share containers between all serial test suites

* refactor: organize files

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2026-02-10 11:05:06 -05:00
Daniel Dietzler
71fe9192fd chore: metadata extraction date test (#26102) 2026-02-10 10:05:05 -05:00
Mert
7fa6f617f5 fix(server): thumbnail queueing (#26077)
* fix thumbnail queueing

* add bmp

* other isEdited column
2026-02-10 09:04:03 -06:00
Thomas
c3730c8eab chore(mobile): enable high refresh rate in debug builds (#26085)
I'm testing changes to animations and app performance, and noticed it
felt quite sluggish on a 120hz display. It turns out that high refresh
is disabled in debug builds. It's probably a good idea to enable it so
that it more closely mirrors the production build.
2026-02-10 03:51:33 +00:00
Luis Nachtigall
3462fc434e fix: evict image from cache on error during image loading (#26078) 2026-02-09 17:01:10 -05:00
Luis Nachtigall
561469b826 fix(mobile): handle image stream completion when no image is emitted (#25984)
* Fix image cancellation to be stream-scoped instead of widget-scoped

* fix(OneFramePlaceholderImageStreamCompleter): make onLastListenerRemoved callback synchronous with removing the last listener

* fix(OneFrameMultiImageStreamCompleter): remove unnecessary blank line in code

* fix(OneFramePlaceholderImageStreamCompleter): cancel pending requests when only cache listener remains

* fix(OneFrameMultiImageStreamCompleter): ensure onLastListenerRemoved callback is invoked only once
2026-02-09 16:59:29 -05:00
bo0tzz
937bef9a4d fix: run rocm builds on pokedex (#26062)
* fix: run rocm builds on pokedex

* fix: --parallel 48 (#26065)
2026-02-09 15:06:05 -05:00
dolfje
5f18110e97 fix(web): removing a person in an asset, doesn't remove the asset in â€Ļ (#26068)
* fix(web): removing a person in an asset, doesn't remove the asset in the persons view (without refresh)

* prettier

---------

Co-authored-by: Nikos Verschore <nikos@uwsoftware.be>
2026-02-09 11:51:02 -06:00
Alex
57485023ae fix: free up space using small batch size to reliably work on Android (#26047)
* fix: free up space delete in small batch

* fix: free up space delete in small batch
2026-02-09 11:48:55 -06:00
Michel Heusschen
8a9b541dd0 fix: slideshow setting dropdown overflow (#26066) 2026-02-09 09:28:47 -06:00
Justin Xiao
25be5fc22d fix(web): prevent context menu from overflowing viewport (#26041)
* fix(web): prevent context menu from overflowing viewport

The context menu used `max-h-dvh` (100% viewport height) as its max height,
but did not account for the menu's top position. When the menu opens at
y > 0, its bottom extends beyond the viewport.

Compute `maxHeight` dynamically based on the menu's top position and apply
it as an inline style, so the menu always fits within the viewport and
scrolls when content exceeds the available space.

* fix: linting

* fix: overflow

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2026-02-09 09:26:25 -06:00
Alex
906c38273f fix: profile dialog auto dismiss after opening on iPad (#26046) 2026-02-08 17:30:57 -06:00
Alex
10b2bf7970 fix: iOS slow start (#26043) 2026-02-08 16:28:51 -06:00
Kolin
7cf8a9936a fix(web): display storage unit next to value instead of absolute positioning in admin user page (#25985)
* fix(web): display storage unit next to value instead of absolute positioning in admin user page

* chore: styling

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2026-02-08 17:22:42 +00:00
Michel Heusschen
59c4a49ffd fix: scroll jump when opening show & hide people (#25932) 2026-02-08 11:19:35 -06:00
Yaros
00486cbcc8 fix(web): toast fixed location (#25966)
fix: toast fixed location
2026-02-08 11:15:09 -06:00
Michel Heusschen
b524d7b6fd fix: reduce queue graph jitter and include paused count (#26023)
fix: reduce queue graph jitter and show paused count
2026-02-08 11:03:59 -06:00
Michel Heusschen
5b705cb723 fix: improve albums page load time on firefox (#26025) 2026-02-08 11:03:17 -06:00
Luis Nachtigall
354dd3cc3c feat(mobile): enhance album sorting functionality with order handling (#24816)
* feat: enhance album sorting functionality with effective order handling

* mobile: formatting

* test: align album sorting order in unit tests with defaultSortOrder

* test(mobile): add reverse order validation for album sorting

* chore(PR): remove OppositeSortOrder Extension and move it directly into SortOrder enum

* refactor: return sorted list directly in album sorting function

* refactor: remove sort_order_extensions.dart
2026-02-07 10:11:37 +05:30
shenlong
57483a1e7f fix: user profile refetched each time on opening app dialog (#25992)
fix: user profile on opening app dialog

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-02-07 10:10:17 +05:30
Romo
bcea64875f fix: image and video download complete notification shows "file_name" (#25975)
* fix: image and video download complete notification shows "file_name"

* fix lint

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-02-07 02:56:14 +00:00
Matthew Momjian
84e30abe5d feat(docs): version policy (#25979)
version policy
2026-02-06 20:31:57 -05:00
Jason Rasmussen
e3e243fa2b refactor: tests (#25987) 2026-02-07 00:47:54 +01:00
Jason Rasmussen
b3820c259e refactor: test factories (#25977) 2026-02-06 16:32:50 -05:00
Daniel Dietzler
a356497d96 refactor: album service small tests (#25640) 2026-02-06 08:56:46 -05:00
Michel Heusschen
16fe828913 fix: revert "fix(web): Ensure profile picture is cropped to 1:1 ratio (#25892)" (#25956)
Revert "fix(web): Ensure profile picture is cropped to 1:1 ratio (#25892)"

This reverts commit 3c77c724c5.
2026-02-06 08:21:56 -05:00
Michel Heusschen
211dc3c056 fix: add missing translations for image editor (#25957) 2026-02-06 13:08:24 +01:00
github-actions
ff9052f7f5 chore: version v2.5.5 2026-02-06 03:49:02 +00:00
Aditya Gaurav
999ce34251 fix(web): avoid transparent pixels from rounded-full during profile picture capture (#25950)
The cropContainer has rounded-full class which creates transparent
corners when captured by domtoimage.toBlob(). Override border-radius
and border to 0/none during capture to produce a clean square image.

Co-authored-by: Aditya Gaurav <aditya-ai-architect@users.noreply.github.com>
2026-02-06 03:21:27 +00:00
Vahant Sharma
491ed3d927 fix(cli): suppress startup messages for immich-admin (#25928)
fix(server): suppress startup messages for immich-admin CLI Introduce a log_message() helper function with QUIET flag to suppress informational startup output when running immich-admin. This prevents both shell messages and Node.js WASI experimental warnings from interfering with CLI help output. Fixes: #25909
2026-02-05 18:34:15 -05:00
Jason Rasmussen
94e86c6e76 fix: dedupe version announcement modal (#25946)
fix: version announcement modal
2026-02-05 16:24:10 -05:00
github-actions
8581b4f350 chore: version v2.5.4 2026-02-05 21:16:52 +00:00
Weblate (bot)
4835d5f97f chore(web): update translations (#25585)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ar/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/bg/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/bn/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ca/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/eo/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/et/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ga/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/id/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ja/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ka/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ko/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/lv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ro/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ta/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/th/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/tr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/uk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/
Translation: Immich/immich

Co-authored-by: Ahmed Khaleel Shihab <ahmed91shihab@gmail.com>
Co-authored-by: AindriÃē Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Alan Lai <alanlai22@outlook.com>
Co-authored-by: Aldi Maulana <aldim222@gmail.com>
Co-authored-by: Andrei P <darkandrei197@gmail.com>
Co-authored-by: Ari Selseng <ari@selseng.net>
Co-authored-by: Beans <leey0818@gmail.com>
Co-authored-by: Bryan Tank <perso@bryantank.fr>
Co-authored-by: Cheng Chien <jamesqian1999@gmail.com>
Co-authored-by: Chun-Hei Lam <chun.lam18@imperial.ac.uk>
Co-authored-by: Ciprriann <cipriannebeja@gmail.com>
Co-authored-by: Damien Doumax <damien@orfaon.net>
Co-authored-by: Fran SÃĄnchez <mrurkaz@gmail.com>
Co-authored-by: Hans Cats <hanscats@gmail.com>
Co-authored-by: Happy <59247878+happy2452354@users.noreply.github.com>
Co-authored-by: Hurricane_32 <rodrigorimo@hotmail.com>
Co-authored-by: Indrek Haav <indrek.haav@hotmail.com>
Co-authored-by: Ivan Dimitrov <idimitrov08@gmail.com>
Co-authored-by: Jordy H <jordy@hoebergen.net>
Co-authored-by: Jozef Gaal <preklady@mayday.sk>
Co-authored-by: Junghyuk Kwon <kwon@junghy.uk>
Co-authored-by: KecskeTech <teonyitas@gmail.com>
Co-authored-by: Kim Hokyeong <manmen.mi1375@gmail.com>
Co-authored-by: Marc Casillas <mcasillassu@gmail.com>
Co-authored-by: Masani Amin Yasin <langugetranslategay.taps663@silomails.com>
Co-authored-by: Masani Amin Yasin <masaniamin@proton.me>
Co-authored-by: MatjaÅž T. <matjaz@moj-svet.si>
Co-authored-by: Melih Ozkan <malihozkan156@gmail.com>
Co-authored-by: MārtiÅ†ÅĄ Bruņenieks <martinsb@gmail.com>
Co-authored-by: NicolÃ˛ <nicosemp@gmail.com>
Co-authored-by: Nik GrebovÅĄek <nikigre@gmail.com>
Co-authored-by: Oleksandr Yurov <oyurov@icloud.com>
Co-authored-by: PPNplus <ppnplus@protonmail.com>
Co-authored-by: Phantom0174 <darrenhsiou@gmail.com>
Co-authored-by: Pieter Lexis <pieter@plexis.eu>
Co-authored-by: Rey <x46puy43k@mozmail.com>
Co-authored-by: Roman Fedorchuk <roma.fedorchuk@gmail.com>
Co-authored-by: Samuel Medeiros <steixeiramedeiros@gmail.com>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: Stan P <g97d6liib@mozmail.com>
Co-authored-by: Taiki M. <vexingly-many-mace@duck.com>
Co-authored-by: Temuri Doghonadze <temuri.doghonadze@gmail.com>
Co-authored-by: Tim Morley <weblate.3919org@timsk.org>
Co-authored-by: Tim MÃŧller <info@timxllr.de>
Co-authored-by: User 123456789 <user123456789@users.noreply.hosted.weblate.org>
Co-authored-by: balc11 <balc11@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: john family <tyutee09@gmail.com>
Co-authored-by: kellogcheung <kellogcheung@gmail.com>
Co-authored-by: liimee <git.taaa@fedora.email>
Co-authored-by: millallo <millallo@tiscali.it>
Co-authored-by: pyccl <changcongliang@163.com>
Co-authored-by: shiuh67 <shiuh.cheng@gmail.com>
Co-authored-by: stesoma <soma.steltzer@gmail.com>
Co-authored-by: Óscar FernÃĄndez Díaz <42654671+oscfdezdz@users.noreply.github.com>
Co-authored-by: გიორგი áƒ™áƒŖáƒĒია <giorgi.kucia@gmail.com>
2026-02-05 21:14:41 +00:00
Aram Akhavan
435565be1b docs: remove writeTimeout on traefik example (#25837)
Don't recommend writeTimeout: 600s for traefik example
2026-02-05 20:58:10 +00:00
Jason Rasmussen
94d3039606 fix: prettier not found (#25941) 2026-02-05 20:44:09 +00:00
Min Idzelis
092ebe01a5 fix: queue assets missing fullsize files for thumbnail regeneration (#25794)
* fix: queue assets missing fullsize files for thumbnail regeneration

* refactor: query

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2026-02-05 19:31:13 +00:00
Brandon Wees
37e5968a7a fix: face and edit handling (#25738)
* fix: handle edits when creating face
2026-02-05 19:29:46 +00:00
Dion de Koning
cfc5ed5997 fix: timezone issue in tests (#25937)
* Fixed an issue where time tests fail in some timezones

* Revert previous fix and add TZ env variable to fix the issue

* Revert other changes and align TZ fix between server and web

* Revert package lock file

---------

Co-authored-by: Dion de Koning <dion@DionK01.local>
2026-02-05 19:24:23 +00:00
Romo
1b3c0e4f65 fix: image download complete notification shows an extra {file_name} template tag (#25936)
* [fix] Image download complete notification shows an extra {file_name} template tag

fixes https://github.com/immich-app/immich/issues/25690
added

```dart
final FileName = 'file_name'.t( args: {'file_name': '{filename}', }, );
```

* chore: fix formatting

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-02-05 19:19:33 +00:00
Mert
fd49d7d566 chore(mobile): skip macro validation (#25744)
* use podfile

* update deps
2026-02-05 14:16:21 -05:00
cmdPromptCritical
ad9f3cfa05 fix(mobile): cancel share download when dialog is dismissed (#25466)
* fix(mobile): cancel share download when dialog is dismissed

* refactor: centralize temporary file cleanup logic

* refactor: replace `CancellationToken` with `Completer<void>` for asset sharing cancellation

---------

Co-authored-by: cmdpromptcritical <cmdpromptcritical@github.com>
2026-02-05 19:08:35 +00:00
Dane
9d8efe2685 fix(docs): add missing --json-output arg to CLI example (#25870) 2026-02-05 14:00:27 -05:00
Paul Makles
ed4d9abdae fix(server): use provided database username for restore & ensure name is not mangled (#25679)
* fix(server): use provided database name/username for restore & ensure name is not mangled

fixes #25633

Signed-off-by: izzy <me@insrt.uk>

* chore: add db switch back but with comments

Signed-off-by: izzy <me@insrt.uk>

* refactor: no need to restore database since it's not technically possible
chore: late fallback for username in parameter builder

Signed-off-by: izzy <me@insrt.uk>

* chore: type fix

Signed-off-by: izzy <me@insrt.uk>

* chore: re-use the username we just pulled out

---------

Signed-off-by: izzy <me@insrt.uk>
2026-02-05 13:59:05 -05:00
Vahant Sharma
ac9f6921cc fix(server): add missing history metadata to getAuthStatus endpoint (#25927)
* fix(server): add missing history metadata to getAuthStatus endpoint

* chore: regenerate openapi specs
2026-02-05 18:56:44 +00:00
shenlong
f0da875e37 fix: allow clear text traffic on android (#25933)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-02-06 00:15:33 +05:30
Peter Ombodi
b0e1a425b3 fix(mobile): jump to previous asset when last asset is deleted (#25563)
* fix(mobile): fix wrong index, update pageController

* fix(mobile): refactor code

---------

Co-authored-by: Peter Ombodi <peter.ombodi@gmail.com>
Co-authored-by: shenlong <139912620+shenlong-tanwen@users.noreply.github.com>
2026-02-05 18:19:40 +00:00
Yaros
7211d80e5f docs: update mobile setup to use mise (#25847)
docs: update mobile setup to mise
2026-02-05 12:55:38 -05:00
shenlong
92c79a7122 chore: increase cache_size and use memory temp store (#25930)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-02-05 11:55:07 -06:00
renovate[bot]
7580521a76 chore(deps): update grafana/grafana docker tag to v12.3.2 (#25840)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-05 12:52:07 -05:00
shenlong
2dd3a764ae fix: timezone in timeline bucketing (#25894)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-02-05 11:47:16 -06:00
Thomas
a42c08ed84 fix(mobile): reset asset index on timeline refresh (#25729)
The current asset changes when the timeline refreshes, which can be
quite jarring. Assets are tracked by their index, and that index becomes
stale when the timeline refreshes. This can be resolved by updating the
index of asset based on a unique identifier (like the hero tag).
2026-02-05 11:46:38 -06:00
Aditya Gaurav
3c77c724c5 fix(web): Ensure profile picture is cropped to 1:1 ratio (#25892)
* fix(web): Ensure profile picture is cropped to 1:1 ratio

Fixes #20097

The profile picture was being captured from the PhotoViewer element
which could have non-square dimensions based on the original image.

Changed to capture from the crop container element which has the
aspect-square class, ensuring the output is always 1:1 ratio.

* fix: remove trailing whitespace to pass prettier check

---------

Co-authored-by: Aditya Gaurav <aditya-ai-architect@users.noreply.github.com>
2026-02-05 11:45:06 -06:00
Yaros
84b2979485 docs: non-code contributing (datasets) (#25850)
docs: datasets
2026-02-05 11:44:35 -06:00
Paul Makles
e9c2ca008a docs: update manual backup/restore to match the automatic process (#25924)
* docs: update manual backup/restore to match automatic process

closes #25772

* docs: update DB_NAME to DB_DATABASE_NAME
2026-02-05 12:43:33 -05:00
Daniel Dietzler
9c098109e0 fix: time zone upserts (#25889) 2026-02-05 12:43:03 -05:00
Mert
27a2808470 fix(mobile): mtls on native clients (#25802)
* handle mtls on ios

* update android impl

* ui improvements

* dead code

* no need to store data separately

* improve concurrency

* dead code

* add migration

* remove unused dependency

* trust user-installed certs

* removed print statement

* fix ios

* improve android styling

* outdated comments

* update lock file

* handle translation

* fix prompt cancellation

* fix video playback

* Apply suggestion from @shenlong-tanwen

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

* Apply suggestion from @shenlong-tanwen

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

* formatting

---------

Co-authored-by: shenlong <139912620+shenlong-tanwen@users.noreply.github.com>
2026-02-05 17:42:53 +00:00
Alex
0a8a65a45e fix: file name search label (#25916) 2026-02-05 17:24:58 +00:00
shenlong
2b6055e830 chore: do not process cloud ids during sync & hash (#25914)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-02-05 11:23:50 -06:00
Mert
ba2dfa7df6 refactor(mobile): consolidate image requests (#25743)
remote url image provider

remove cached_network_image

formatting

linting

remove thumb provider

formatting
2026-02-05 12:16:42 -05:00
Jason Rasmussen
237ea3aedd chore: update oauth documentation (#25907)
* chore: prefer lowercase for non i18n labels

* chore: update documentation
2026-02-05 09:00:00 -05:00
Michel Heusschen
810e9254f3 fix: preserve hidden people state across pagination (#25886)
* fix: preserve hidden people state across pagination

* track overrides instead

* use event instead of bind:people

* update test
2026-02-05 08:51:30 -05:00
Michel Heusschen
57e0835b46 fix: ensure theme stays in sync with @immich/ui (#25922) 2026-02-05 08:36:20 -05:00
Michel Heusschen
e97030a7ae fix: make switch labels properly clickable (#25898) 2026-02-05 12:09:27 +01:00
Michel Heusschen
fdf06a91cc fix: improve asset editor exit handling (#25917) 2026-02-05 12:01:54 +01:00
Michel Heusschen
732303661b fix: allow null tagIds in search dto (#25920) 2026-02-05 11:52:35 +01:00
Alex
e9f8521a50 fix: date time picker text color in dark mode (#25883) 2026-02-04 18:45:56 +00:00
Michel Heusschen
6bd60270b4 fix: correctly sync shared link download with metadata toggle (#25885) 2026-02-04 12:38:42 -05:00
Michel Heusschen
5a6fd7af06 fix: close tag modal after tagging assets (#25884) 2026-02-04 12:35:00 -05:00
Jason Rasmussen
6cdebdd3b3 fix(server): deleting stacked assets (#25874)
* fix(server): deleting stacked assets

* fix: log a warning when removing an empty directory fails
2026-02-04 17:33:37 +00:00
Jason Rasmussen
9dddccd831 fix: null validation (#25891) 2026-02-04 12:27:52 -05:00
Min Idzelis
440b3b4c6f chore: move devcontainer specific tasks to devcontainer.json (#25881)
refactor: move devcontainer specific tasks to devcontainer.json
2026-02-03 23:04:09 -05:00
Jason Rasmussen
3ea65f8d27 fix: album dto docs (#25873) 2026-02-03 21:05:18 +00:00
github-actions
38c1f0b1fd chore: version v2.5.3 2026-02-03 18:14:21 +00:00
Michel Heusschen
5212bca3d0 fix: reset zoom when navigating between assets (#25863) 2026-02-03 11:07:06 -06:00
Daniel Dietzler
2990bde0bb fix: metadata extraction race condition (#25866) 2026-02-03 11:03:27 -06:00
Michel Heusschen
af1ecaf5cc fix: prevent backspace from accidentally triggering delete modals (#25858)
* fix: prevent backspace from accidentally triggering delete modals

* ignore input fields instead of removing shortcut
2026-02-03 16:42:46 +00:00
Alex
3870ebc3c6 fix: prevent album page get rebuilt when resuming app (#25862) 2026-02-03 16:35:53 +00:00
Michel Heusschen
0a9d969b47 fix: prevent stale values in edit user form after save (#25859) 2026-02-03 17:29:01 +01:00
Daniel Dietzler
94965f6d66 chore: rework tags sidebar (#25855) 2026-02-03 16:06:26 +00:00
Alex
8872d2c7ae chore: remove swift logs (#25857) 2026-02-03 16:00:17 +00:00
Alex
23445fdcc1 fix: upload progress bar flickering (#25829)
* fix: upload progress bar flickering

* pr feedback and more logs
2026-02-03 09:28:29 -06:00
renovate[bot]
25f2273e24 chore(deps): update redis:6.2-alpine docker digest to 46884be (#25839)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-03 12:56:56 +01:00
Min Idzelis
95e8e474b8 fix(web): enable asset viewer navigation across memory boundaries (#25741) 2026-02-02 10:12:08 -06:00
Timon
9f52d864cf chore(ml): replace black with ruff format (#25578) 2026-02-02 09:02:06 -05:00
Mees Frensel
0273dcb0cf fix(web): user settings styling (#25775) 2026-02-02 08:47:28 -05:00
Matthew Momjian
1436e2a75f fix(docs): clarify supported vector version (#25753)
clarify supported version
2026-01-31 11:46:51 -05:00
Thomas
855817514c fix(mobile): hide latest version if disabled (#25691)
* fix(mobile): hide latest version if disabled

If the version check feature is disabled, the server will currently send
stale data to the client. In addition to no longer sending stale data,
the client should also not show the latest version if the feature is
disabled.

This complements the server PR #25688.

* lint

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2026-01-30 16:17:03 +00:00
Thomas
d5ad35ea52 chore(mobile): remove references to fvm, add mise docs, use java 21 (#25703) 2026-01-29 23:03:56 -06:00
shenlong
e63213d774 fix(mobile): do not autocorrect on endpoint input (#25696)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-01-29 23:03:26 -06:00
Jason Rasmussen
0be1ffade6 fix: no notification if release check is disabled (#25688) 2026-01-29 18:31:11 -05:00
Brandon Wees
1a04caee29 fix: reset and unsaved change states in editor (#25588) 2026-01-29 15:18:30 -06:00
renovate[bot]
3ace578fc0 chore(deps): update dependency opentofu to v1.11.4 (#24609)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-29 12:14:44 -05:00
Jason Rasmussen
25c573bc7a chore: remove random code snippet (#25677) 2026-01-29 16:11:25 +00:00
renovate[bot]
10bb83cf75 chore(deps): update dependency terragrunt to v0.98.0 (#24328)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-29 08:55:42 -05:00
Jason Rasmussen
10b53b525d refactor: event manager (#25565) 2026-01-29 08:52:18 -05:00
Timon
8db61d341f docs(openapi): add descriptions to OpenAPI specification (#25185)
* faces

* add openapi descriptions

* remove dto descriptions

* gen openapi

* dtos

* fix dtos

* fix more

* fix build

* more

* complete dtos

* descriptions on rebase

* gen rebase

* revert correct integer type conversion

* gen after revert

* revert correct nullables

* regen after revert

* actually incorrect adding default here

* revert correct number type conversion

* regen after revert

* revert nullable usage

* regen fully

* readd some comments

* one more

* one more

* use enum

* add missing

* add missing controllers

* add missing dtos

* complete it

* more

* describe global key and slug

* add remaining body and param descriptions

* lint and format

* cleanup

* response and schema descriptions

* test patch according to suggestion

* revert added api response objects

* revert added api body objects

* revert added api param object

* revert added api query objects

* revert reorganized http code objects

* revert reorganize ApiOkResponse objects

* revert added api response objects (2)

* revert added api tag object

* revert added api schema objects

* migrate missing asset.dto.ts

* regenerate openapi builds

* delete generated mustache files

* remove descriptions from properties that are schemas

* lint

* revert nullable type changes

* revert int/num type changes

* remove explicit default

* readd comment

* lint

* pr fixes

* last bits and pieces

* lint and format

* chore: remove rejected patches

* fix: deleting asset from asset-viewer on search results (#25596)

* fix: escape handling in search asset viewer (#25621)

* fix: correctly show owner in album options modal (#25618)

* fix: validation issues

* fix: validation issues

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
Co-authored-by: Min Idzelis <min123@gmail.com>
Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
Co-authored-by: Paul Makles <me@insrt.uk>
2026-01-29 08:49:15 -05:00
954 changed files with 33435 additions and 13393 deletions

View File

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

View File

@@ -131,7 +131,7 @@ jobs:
- device: rocm
suffixes: '-rocm'
platforms: linux/amd64
runner-mapping: '{"linux/amd64": "mich"}'
runner-mapping: '{"linux/amd64": "pokedex-giant"}'
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@0477486d82313fba68f7c82c034120a4b8981297 # multi-runner-build-workflow-v2.1.0
permissions:
contents: read

View File

@@ -497,14 +497,15 @@ jobs:
run: npx playwright install chromium --only-shell
if: ${{ !cancelled() }}
- name: Docker build
run: docker compose build
run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
if: ${{ !cancelled() }}
- name: Run e2e tests (web)
env:
CI: true
run: npx playwright test --project=chromium
PLAYWRIGHT_DISABLE_WEBSERVER: true
run: npx playwright test --project=web
if: ${{ !cancelled() }}
- name: Archive web results
- name: Archive e2e test (web) results
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
if: success() || failure()
with:
@@ -513,14 +514,37 @@ jobs:
- name: Run ui tests (web)
env:
CI: true
PLAYWRIGHT_DISABLE_WEBSERVER: true
run: npx playwright test --project=ui
if: ${{ !cancelled() }}
- name: Archive ui results
- name: Archive ui test (web) results
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
if: success() || failure()
with:
name: e2e-ui-test-results-${{ matrix.runner }}
path: e2e/playwright-report/
- name: Run maintenance tests
env:
CI: true
PLAYWRIGHT_DISABLE_WEBSERVER: true
run: npx playwright test --project=maintenance
if: ${{ !cancelled() }}
- name: Archive maintenance tests (web) results
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
if: success() || failure()
with:
name: e2e-maintenance-isolated-test-results-${{ matrix.runner }}
path: e2e/playwright-report/
- name: Capture Docker logs
if: always()
run: docker compose logs --no-color > docker-compose-logs.txt
working-directory: ./e2e
- name: Archive Docker logs
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
if: always()
with:
name: docker-compose-logs-${{ matrix.runner }}
path: e2e/docker-compose-logs.txt
success-check-e2e:
name: End-to-End Tests Success
needs: [e2e-tests-server-cli, e2e-tests-web]
@@ -591,9 +615,9 @@ jobs:
- name: Lint with ruff
run: |
uv run ruff check --output-format=github immich_ml
- name: Check black formatting
- name: Format with ruff
run: |
uv run black --check immich_ml
uv run ruff format --check immich_ml
- name: Run mypy type checking
run: |
uv run mypy --strict immich_ml/

80
.vscode/tasks.json vendored
View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@immich/cli",
"version": "2.5.2",
"version": "2.5.6",
"description": "Command Line Interface (CLI) for Immich",
"type": "module",
"exports": "./dist/index.js",
@@ -20,7 +20,7 @@
"@types/lodash-es": "^4.17.12",
"@types/micromatch": "^4.0.9",
"@types/mock-fs": "^4.13.1",
"@types/node": "^24.10.9",
"@types/node": "^24.10.11",
"@vitest/coverage-v8": "^3.0.0",
"byte-size": "^9.0.0",
"cli-progress": "^3.12.0",

View File

@@ -4,6 +4,7 @@ import {
AssetBulkUploadCheckResult,
AssetMediaResponseDto,
AssetMediaStatus,
Permission,
addAssetsToAlbum,
checkBulkUpload,
createAlbum,
@@ -20,13 +21,11 @@ import { Stats, createReadStream } from 'node:fs';
import { stat, unlink } from 'node:fs/promises';
import path, { basename } from 'node:path';
import { Queue } from 'src/queue';
import { BaseOptions, Batcher, authenticate, crawl, sha1 } from 'src/utils';
import { BaseOptions, Batcher, authenticate, crawl, requirePermissions, s, sha1 } from 'src/utils';
const UPLOAD_WATCH_BATCH_SIZE = 100;
const UPLOAD_WATCH_DEBOUNCE_TIME_MS = 10_000;
const s = (count: number) => (count === 1 ? '' : 's');
// TODO figure out why `id` is missing
type AssetBulkUploadCheckResults = Array<AssetBulkUploadCheckResult & { id: string }>;
type Asset = { id: string; filepath: string };
@@ -136,6 +135,7 @@ export const startWatch = async (
export const upload = async (paths: string[], baseOptions: BaseOptions, options: UploadOptionsDto) => {
await authenticate(baseOptions);
await requirePermissions([Permission.AssetUpload]);
const scanFiles = await scan(paths, options);

View File

@@ -1,7 +1,15 @@
import { getMyUser } from '@immich/sdk';
import { getMyUser, Permission } from '@immich/sdk';
import { existsSync } from 'node:fs';
import { mkdir, unlink } from 'node:fs/promises';
import { BaseOptions, connect, getAuthFilePath, logError, withError, writeAuthFile } from 'src/utils';
import {
BaseOptions,
connect,
getAuthFilePath,
logError,
requirePermissions,
withError,
writeAuthFile,
} from 'src/utils';
export const login = async (url: string, key: string, options: BaseOptions) => {
console.log(`Logging in to ${url}`);
@@ -9,6 +17,7 @@ export const login = async (url: string, key: string, options: BaseOptions) => {
const { configDirectory: configDir } = options;
await connect(url, key);
await requirePermissions([Permission.UserRead]);
const [error, user] = await withError(getMyUser());
if (error) {

View File

@@ -1,8 +1,9 @@
import { getAssetStatistics, getMyUser, getServerVersion, getSupportedMediaTypes } from '@immich/sdk';
import { BaseOptions, authenticate } from 'src/utils';
import { getAssetStatistics, getMyUser, getServerVersion, getSupportedMediaTypes, Permission } from '@immich/sdk';
import { authenticate, BaseOptions, requirePermissions } from 'src/utils';
export const serverInfo = async (options: BaseOptions) => {
const { url } = await authenticate(options);
await requirePermissions([Permission.ServerAbout, Permission.AssetStatistics, Permission.UserRead]);
const [versionInfo, mediaTypes, stats, userInfo] = await Promise.all([
getServerVersion(),

View File

@@ -1,4 +1,4 @@
import { getMyUser, init, isHttpError } from '@immich/sdk';
import { ApiKeyResponseDto, getMyApiKey, getMyUser, init, isHttpError, Permission } from '@immich/sdk';
import { convertPathToPattern, glob } from 'fast-glob';
import { createHash } from 'node:crypto';
import { createReadStream } from 'node:fs';
@@ -34,6 +34,36 @@ export const authenticate = async (options: BaseOptions): Promise<AuthDto> => {
return auth;
};
export const s = (count: number) => (count === 1 ? '' : 's');
let _apiKey: ApiKeyResponseDto;
export const requirePermissions = async (permissions: Permission[]) => {
if (!_apiKey) {
_apiKey = await getMyApiKey();
}
if (_apiKey.permissions.includes(Permission.All)) {
return;
}
const missing: Permission[] = [];
for (const permission of permissions) {
if (!_apiKey.permissions.includes(permission)) {
missing.push(permission);
}
}
if (missing.length > 0) {
const combined = missing.map((permission) => `"${permission}"`).join(', ');
console.log(
`Missing required permission${s(missing.length)}: ${combined}.
Please make sure your API key has the correct permissions.`,
);
process.exit(1);
}
};
export const connect = async (url: string, key: string) => {
const wellKnownUrl = new URL('.well-known/immich', url);
try {

View File

@@ -1,6 +1,6 @@
[tools]
terragrunt = "0.93.10"
opentofu = "1.10.7"
terragrunt = "0.98.0"
opentofu = "1.11.4"
[tasks."tg:fmt"]
run = "terragrunt hclfmt"

View File

@@ -127,7 +127,7 @@ services:
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:546304417feac0874c3dd576e0952c6bb8f06bb4093ea0c9ca303c73cf458f63
image: docker.io/valkey/valkey:9@sha256:930b41430fb727f533c5982fe509b6f04233e26d0f7354e04de4b0d5c706e44e
healthcheck:
test: redis-cli ping || exit 1

View File

@@ -56,7 +56,7 @@ services:
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:546304417feac0874c3dd576e0952c6bb8f06bb4093ea0c9ca303c73cf458f63
image: docker.io/valkey/valkey:9@sha256:930b41430fb727f533c5982fe509b6f04233e26d0f7354e04de4b0d5c706e44e
healthcheck:
test: redis-cli ping || exit 1
restart: always
@@ -97,7 +97,7 @@ services:
command: ['./run.sh', '-disable-reporting']
ports:
- 3000:3000
image: grafana/grafana:12.3.1-ubuntu@sha256:d57f1365197aec34c4d80869d8ca45bb7787c7663904950dab214dfb40c1c2fd
image: grafana/grafana:12.3.2-ubuntu@sha256:6cca4b429a1dc0d37d401dee54825c12d40056c3c6f3f56e3f0d6318ce77749b
volumes:
- grafana-data:/var/lib/grafana

View File

@@ -0,0 +1,100 @@
#
# WARNING: To install Immich, follow our guide: https://docs.immich.app/install/docker-compose
#
# Make sure to use the docker-compose.yml of the current release:
#
# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
#
# The compose file on main may not be compatible with the latest release.
name: immich
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
# extends:
# file: hwaccel.transcoding.yml
# service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
user: '1000:1000'
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
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}:/data
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ports:
- '2283:2283'
depends_on:
- redis
- database
restart: always
healthcheck:
disable: false
immich-machine-learning:
container_name: immich_machine_learning
# For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag.
# Example tag: ${IMMICH_VERSION:-release}-cuda
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
# extends: # uncomment this section for hardware acceleration - see https://docs.immich.app/features/ml-hardware-acceleration
# file: hwaccel.ml.yml
# service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable
user: '1000:1000'
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
volumes:
- ./ml-model-cache:/cache
- ./ml-dotcache:/.cache
- ./ml-config:/.config
env_file:
- .env
restart: always
healthcheck:
disable: false
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:930b41430fb727f533c5982fe509b6f04233e26d0f7354e04de4b0d5c706e44e
user: '1000:1000'
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
volumes:
- ./redis:/data
healthcheck:
test: redis-cli ping || exit 1
restart: always
database:
container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23
user: '1000:1000'
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
POSTGRES_INITDB_ARGS: '--data-checksums'
# Uncomment the DB_STORAGE_TYPE: 'HDD' var if your database isn't stored on SSDs
# DB_STORAGE_TYPE: 'HDD'
volumes:
# Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
shm_size: 128mb
restart: always
healthcheck:
disable: false
volumes:
model-cache:

View File

@@ -49,7 +49,7 @@ services:
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:546304417feac0874c3dd576e0952c6bb8f06bb4093ea0c9ca303c73cf458f63
image: docker.io/valkey/valkey:9@sha256:930b41430fb727f533c5982fe509b6f04233e26d0f7354e04de4b0d5c706e44e
healthcheck:
test: redis-cli ping || exit 1
restart: always

View File

@@ -402,6 +402,9 @@ To decrease Redis logs, you can add the following line to the `redis:` section o
### How can I run Immich as a non-root user?
You can change the user in the container by setting the `user` argument in `docker-compose.yml` for each service.
[Example docker-compose.yml file](https://github.com/immich-app/immich/blob/main/docker/docker-compose.rootless.yml)
You may need to add mount points or docker volumes for the following internal container paths:
- `immich-machine-learning:/.config`

View File

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

View File

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

View File

@@ -88,7 +88,7 @@ The easiest option is to have both extensions installed during the migration:
<details>
<summary>Migration steps (automatic)</summary>
1. Ensure you still have pgvecto.rs installed
2. Install `pgvector` (`>= 0.7.0, < 1.0.0`). The easiest way to do this is on Debian/Ubuntu by adding the [PostgreSQL Apt repository][pg-apt] and then running `apt install postgresql-NN-pgvector`, where `NN` is your Postgres version (e.g., `16`)
2. Install `pgvector` (`>= 0.7, < 0.9`). The easiest way to do this is on Debian/Ubuntu by adding the [PostgreSQL Apt repository][pg-apt] and then running `apt install postgresql-NN-pgvector`, where `NN` is your Postgres version (e.g., `16`)
3. [Install VectorChord][vchord-install]
4. Add `shared_preload_libraries= 'vchord.so, vectors.so'` to your `postgresql.conf`, making sure to include _both_ `vchord.so` and `vectors.so`. You may include other libraries here as well if needed
5. Restart the Postgres database

View File

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

View File

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

View File

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

View File

@@ -86,8 +86,8 @@ You do not need to redo any machine learning jobs after enabling hardware accele
## Setup
1. If you do not already have it, download the latest [`hwaccel.ml.yml`][hw-file] file and ensure it's in the same folder as the `docker-compose.yml`.
2. In the `docker-compose.yml` under `immich-machine-learning`, uncomment the `extends` section and change `cpu` to the appropriate backend.
3. Still in `immich-machine-learning`, add one of -[armnn, cuda, rocm, openvino, rknn] to the `image` section's tag at the end of the line.
2. In `immich-machine-learning`, add one of -[armnn, cuda, rocm, openvino, rknn] to the `image` section's tag at the end of the line.
3. Still in the `docker-compose.yml` under `immich-machine-learning`, uncomment the `extends` section and change `cpu` to the appropriate backend.
4. Redeploy the `immich-machine-learning` container with these updated settings.
### Confirming Device Usage

View File

@@ -66,7 +66,7 @@ Now make sure that the local album is selected in the backup screen (steps 1-2 a
- **Keep on device:** You can choose to restrict removal to `Always keep` **All photos** or **All videos**, regardless of other settings. This setting can hamper freeing up space significantly — with 80 GB of videos and 40 GB photos, selecting `Always keep photos` retains thousands of photos on your device.
2. **Scan & Review:** Before any files are removed, you are presented with a review screen to verify which items will be deleted and how much storage is reclamable.
3. **Deletion:** Confirmed items are moved to your device's native Trash/Recycle Bin.
3. **Deletion:** Confirmed items are moved to your device's native Trash/Recycle Bin. For large queues, Immich processes deletion in batches for stability (`2000` assets per batch on Android, `10000` per batch on iOS).
:::info reclaim storage
To use the reclaimed space right away, you must empty the system/gallery trash manually outside of Immich.

View File

@@ -26,6 +26,16 @@ docker image prune
[breaking]: https://github.com/immich-app/immich/discussions?discussions_q=label%3Achangelog%3Abreaking-change+sort%3Adate_created
[releases]: https://github.com/immich-app/immich/releases
## Versioning Policy
Immich follows [semantic versioning][semver], which tags releases in the format `<major>.<minor>.<patch>`. We intend for breaking changes to be limited to major version releases.
You can configure your Docker image to point to the current major version by using a metatag, such as `:v2`.
Currently, we have no plans to backport patches to earlier versions. We encourage all users to run the most recent release of Immich.
Switching back to an earlier version, even within the same minor release tag, is not supported.
[semver]: https://semver.org/
## Migrating to VectorChord
:::info

View File

@@ -32,3 +32,7 @@ If you would like to migrate from one media location to another, simply successf
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.
## Schema drift
Schema drift is when the database schema is out of sync with the code. This could be the result of manual database tinkering, issues during a database restore, or something else. Schema drift can lead to data corruption, application bugs, and other unpredictable behavior. Please reconcile the differences as soon as possible. Specifically, missing `CONSTRAINT`s can result in duplicate assets being uploaded, since the server relies on a checksum `CONSTRAINT` to prevent duplicates.

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "immich-e2e",
"version": "2.5.2",
"version": "2.5.6",
"description": "",
"main": "index.js",
"type": "module",
@@ -27,7 +27,7 @@
"@playwright/test": "^1.44.1",
"@socket.io/component-emitter": "^3.1.2",
"@types/luxon": "^3.4.2",
"@types/node": "^24.10.9",
"@types/node": "^24.10.11",
"@types/pg": "^8.15.1",
"@types/pngjs": "^6.0.4",
"@types/supertest": "^6.0.2",

View File

@@ -14,7 +14,8 @@ export const playwrightDisableWebserver = process.env.PLAYWRIGHT_DISABLE_WEBSERV
process.env.PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS = '1';
const config: PlaywrightTestConfig = {
testDir: './src/web/specs',
testDir: './src/specs/server',
testMatch: /.*\.e2e-spec\.ts/,
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 4 : 0,
@@ -28,54 +29,28 @@ const config: PlaywrightTestConfig = {
},
},
testMatch: /.*\.e2e-spec\.ts/,
workers: process.env.CI ? 4 : Math.round(cpus().length * 0.75),
projects: [
{
name: 'chromium',
name: 'web',
use: { ...devices['Desktop Chrome'] },
testMatch: /.*\.e2e-spec\.ts/,
testDir: './src/specs/web',
workers: 1,
},
{
name: 'ui',
use: { ...devices['Desktop Chrome'] },
testMatch: /.*\.ui-spec\.ts/,
testDir: './src/ui/specs',
fullyParallel: true,
workers: process.env.CI ? 3 : Math.max(1, Math.round(cpus().length * 0.75) - 1),
},
// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },
// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
{
name: 'maintenance',
use: { ...devices['Desktop Chrome'] },
testDir: './src/specs/maintenance',
workers: 1,
},
],
/* Run your local dev server before starting the tests */

View File

@@ -43,10 +43,10 @@ export const errorDto = {
message: 'Invalid share key',
correlationId: expect.any(String),
},
invalidSharePassword: {
passwordRequired: {
error: 'Unauthorized',
statusCode: 401,
message: 'Invalid password',
message: 'Password required',
correlationId: expect.any(String),
},
badRequest: (message: any = null) => ({

View File

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

View File

@@ -239,7 +239,7 @@ describe('/shared-links', () => {
const { status, body } = await request(app).get('/shared-links/me').query({ key: linkWithPassword.key });
expect(status).toBe(401);
expect(body).toEqual(errorDto.invalidSharePassword);
expect(body).toEqual(errorDto.passwordRequired);
});
it('should get data for correct password protected link', async () => {

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import { generateConsecutiveDays, generateDayAssets } from 'src/generators/timeline/model-objects';
import { SeededRandom, selectRandomDays } from 'src/generators/timeline/utils';
import { generateConsecutiveDays, generateDayAssets } from 'src/ui/generators/timeline/model-objects';
import { SeededRandom, selectRandomDays } from 'src/ui/generators/timeline/utils';
import type { MockTimelineAsset } from './timeline-config';
import { GENERATION_CONSTANTS } from './timeline-config';

View File

@@ -1,5 +1,5 @@
import sharp from 'sharp';
import { SeededRandom } from 'src/generators/timeline/utils';
import { SeededRandom } from 'src/ui/generators/timeline/utils';
export const randomThumbnail = async (seed: string, ratio: number) => {
const height = 235;

View File

@@ -6,7 +6,7 @@ import { faker } from '@faker-js/faker';
import { AssetVisibility } from '@immich/sdk';
import { DateTime } from 'luxon';
import { writeFileSync } from 'node:fs';
import { SeededRandom } from 'src/generators/timeline/utils';
import { SeededRandom } from 'src/ui/generators/timeline/utils';
import type { DayPattern, MonthDistribution } from './distribution-patterns';
import { ASSET_DISTRIBUTION, DAY_DISTRIBUTION } from './distribution-patterns';
import type { MockTimelineAsset, MockTimelineData, SerializedTimelineData, TimelineConfig } from './timeline-config';

View File

@@ -15,7 +15,7 @@ import {
} from '@immich/sdk';
import { DateTime } from 'luxon';
import { signupDto } from 'src/fixtures';
import { parseTimeBucketKey } from 'src/generators/timeline/utils';
import { parseTimeBucketKey } from 'src/ui/generators/timeline/utils';
import type { MockTimelineAsset, MockTimelineData } from './timeline-config';
/**

View File

@@ -1,5 +1,5 @@
import type { AssetVisibility } from '@immich/sdk';
import { DayPattern, MonthDistribution } from 'src/generators/timeline/distribution-patterns';
import { DayPattern, MonthDistribution } from 'src/ui/generators/timeline/distribution-patterns';
// Constants for generation parameters
export const GENERATION_CONSTANTS = {

View File

@@ -1,5 +1,5 @@
import { DateTime } from 'luxon';
import { GENERATION_CONSTANTS, MockTimelineAsset } from 'src/generators/timeline/timeline-config';
import { GENERATION_CONSTANTS, MockTimelineAsset } from 'src/ui/generators/timeline/timeline-config';
/**
* Linear Congruential Generator for deterministic pseudo-random numbers

View File

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

View File

@@ -10,8 +10,8 @@ import {
randomPreview,
randomThumbnail,
TimelineData,
} from 'src/generators/timeline';
import { sleep } from 'src/web/specs/timeline/utils';
} from 'src/ui/generators/timeline';
import { sleep } from 'src/ui/specs/timeline/utils';
export class TimelineTestContext {
slowBucket = false;

View File

@@ -8,11 +8,11 @@ import {
selectRandom,
TimelineAssetConfig,
TimelineData,
} from 'src/generators/timeline';
import { setupBaseMockApiRoutes } from 'src/mock-network/base-network';
import { setupTimelineMockApiRoutes, TimelineTestContext } from 'src/mock-network/timeline-network';
} from 'src/ui/generators/timeline';
import { setupBaseMockApiRoutes } from 'src/ui/mock-network/base-network';
import { setupTimelineMockApiRoutes, TimelineTestContext } from 'src/ui/mock-network/timeline-network';
import { utils } from 'src/utils';
import { assetViewerUtils } from 'src/web/specs/timeline/utils';
import { assetViewerUtils } from '../timeline/utils';
test.describe.configure({ mode: 'parallel' });
test.describe('asset-viewer', () => {

View File

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

View File

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

View File

@@ -6,10 +6,10 @@ import {
generateTimelineData,
TimelineAssetConfig,
TimelineData,
} from 'src/generators/timeline';
import { setupBaseMockApiRoutes } from 'src/mock-network/base-network';
import { setupTimelineMockApiRoutes, TimelineTestContext } from 'src/mock-network/timeline-network';
import { assetViewerUtils } from 'src/web/specs/timeline/utils';
} from 'src/ui/generators/timeline';
import { setupBaseMockApiRoutes } from 'src/ui/mock-network/base-network';
import { setupTimelineMockApiRoutes, TimelineTestContext } from 'src/ui/mock-network/timeline-network';
import { assetViewerUtils } from '../timeline/utils';
const buildSearchUrl = (assetId: string) => {
const searchQuery = encodeURIComponent(JSON.stringify({ originalFileName: 'test' }));

View File

@@ -12,18 +12,15 @@ import {
selectRandomMultiple,
TimelineAssetConfig,
TimelineData,
} from 'src/generators/timeline';
import { setupBaseMockApiRoutes } from 'src/mock-network/base-network';
import { pageRoutePromise, setupTimelineMockApiRoutes, TimelineTestContext } from 'src/mock-network/timeline-network';
import { utils } from 'src/utils';
} from 'src/ui/generators/timeline';
import { setupBaseMockApiRoutes } from 'src/ui/mock-network/base-network';
import {
assetViewerUtils,
padYearMonth,
pageUtils,
poll,
thumbnailUtils,
timelineUtils,
} from 'src/web/specs/timeline/utils';
pageRoutePromise,
setupTimelineMockApiRoutes,
TimelineTestContext,
} from 'src/ui/mock-network/timeline-network';
import { utils } from 'src/utils';
import { assetViewerUtils, padYearMonth, pageUtils, poll, thumbnailUtils, timelineUtils } from './utils';
test.describe.configure({ mode: 'parallel' });
test.describe('Timeline', () => {

View File

@@ -1,6 +1,6 @@
import { BrowserContext, expect, Page } from '@playwright/test';
import { DateTime } from 'luxon';
import { TimelineAssetConfig } from 'src/generators/timeline';
import { TimelineAssetConfig } from 'src/ui/generators/timeline';
export const sleep = (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms));

View File

@@ -15,7 +15,6 @@
"incremental": true,
"skipLibCheck": true,
"esModuleInterop": true,
"rootDirs": ["src"],
"baseUrl": "./"
},
"include": ["src/**/*.ts"],

View File

@@ -3,14 +3,14 @@ import { defineConfig } from 'vitest/config';
// skip `docker compose up` if `make e2e` was already run
const globalSetup: string[] = [];
try {
await fetch('http://127.0.0.1:2285/api/server-info/ping');
await fetch('http://127.0.0.1:2285/api/server/ping');
} catch {
globalSetup.push('src/setup/docker-compose.ts');
globalSetup.push('src/docker-compose.ts');
}
export default defineConfig({
test: {
include: ['src/{api,cli,immich-admin}/specs/*.e2e-spec.ts'],
include: ['src/specs/server/**/*.e2e-spec.ts'],
globalSetup,
testTimeout: 15_000,
pool: 'threads',

View File

@@ -5,7 +5,7 @@
"acknowledge": "ØŖŲØ¯ØąŲƒ Ø°Ų„Ųƒ",
"action": "ØšŲ…Ų„ŲŠØŠ",
"action_common_update": "ØĒØ­Ø¯ŲŠØĢ",
"action_description": "Ų…ØŦŲ…ŲˆØšØŠ Ų…Ų† Ø§Ų„ŲØšØ§Ų„ŲŠØ§ØĒ Ø§Ų„ØĒ؊ ؊ØŦب ØĒŲ†ŲŲŠØ°Ų‡Ø§ ØšŲ„Ų‰ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„ØĒ؊ ØĒŲ… ØĒØĩ؁؊ØĒŲ‡Ø§",
"action_description": "Ų…ØŦŲ…ŲˆØšØŠ Ų…Ų† Ø§Ų„ŲØšØ§Ų„ŲŠØ§ØĒ Ø§Ų„ØĒ؊ ØŗØĒŲ†ŲØ° ØšŲ„Ų‰ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„ØĒ؊ ØĒŲ… ØĒØĩ؁؊ØĒŲ‡Ø§",
"actions": "ØšŲ…Ų„ŲŠØ§ØĒ",
"active": "Ų†Ø´Øˇ",
"active_count": "ŲØšØ§Ų„: {count}",
@@ -272,7 +272,7 @@
"oauth_auto_register": "Ø§Ų„ØĒØŗØŦŲŠŲ„ Ø§Ų„ØĒŲ„Ų‚Ø§ØĻ؊",
"oauth_auto_register_description": "Ø§Ų„ØĒØŗØŦŲŠŲ„ Ø§Ų„ØĒŲ„Ų‚Ø§ØĻ؊ Ų„Ų„Ų…ØŗØĒØŽØ¯Ų…ŲŠŲ† Ø§Ų„ØŦدد بؚد ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„ Ø¨Ø§ØŗØĒØŽØ¯Ø§Ų… OAuth",
"oauth_button_text": "Ų†Øĩ Ø§Ų„Ø˛Øą",
"oauth_client_secret_description": "Ų…ØˇŲ„ŲˆØ¨ اذاPKCE(؅؁ØĒاح Ø§Ų„Ø§ØĢباØĒ Ų„ØĒØ¨Ø§Ø¯Ų„ Ø§Ų„ŲƒŲˆØ¯) Ų„Ų… ؊ØĒŲ… ØĒŲˆŲŲŠØąŲ‡ Ų…Ų† Ų…Ø˛ŲˆØ¯ OAuth",
"oauth_client_secret_description": "Ų…ØˇŲ„ŲˆØ¨ Ų„Ų„ØšŲ…ŲŠŲ„ Ø§Ų„ØŗØąŲŠØŒ Ø§Ųˆ اذا PKCE(؅؁ØĒاح Ø§Ų„Ø§ØĢباØĒ Ų„ØĒØ¨Ø§Ø¯Ų„ Ø§Ų„ŲƒŲˆØ¯) Ų„ŲŠØŗ Ų…Ø¯ØšŲˆŲ… Ų…Ų† Ø§Ų„ØšŲ…ŲŠŲ„ Ø§Ų„ØšØ§Ų….",
"oauth_enable_description": "ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„ Ø¨Ø§ØŗØĒØŽØ¯Ø§Ų… OAuth",
"oauth_mobile_redirect_uri": "ØšŲ†ŲˆØ§Ų† URI Ų„ØĨؚاد؊ Ø§Ų„ØĒ؈ØŦŲŠŲ‡ ØšŲ„Ų‰ Ø§Ų„Ų‡Ø§ØĒ؁",
"oauth_mobile_redirect_uri_override": "ØĒØŦØ§ŲˆØ˛ ØšŲ†ŲˆØ§Ų† URI Ų„ØĨؚاد؊ Ø§Ų„ØĒ؈ØŦŲŠŲ‡ ØšŲ„Ų‰ Ø§Ų„Ų‡Ø§ØĒ؁",
@@ -572,6 +572,9 @@
"asset_list_layout_sub_title": "ØĒØĩŲ…ŲŠŲ…",
"asset_list_settings_subtitle": "ØĨؚداداØĒ ØĒØŽØˇŲŠØˇ Ø´Ø¨ŲƒØŠ Ø§Ų„ØĩŲˆØą",
"asset_list_settings_title": "Ø´Ø¨ŲƒØŠ Ø§Ų„ØĩŲˆØą",
"asset_not_found_on_device_android": "Ø§Ų„Ø§ØĩŲ„ Ų„Ų… ؊ØĒŲ… Ø§ŲŠØŦØ§Ø¯Ų‡ ؁؊ Ø§Ų„ØŦŲ‡Ø§Ø˛",
"asset_not_found_on_device_ios": "Ø§Ų„ØŖØĩŲ„ Ų„Ų… ؊ØĒŲ… Ø§ŲŠØŦØ§Ø¯Ų‡ ؁؊ Ø§Ų„ØŦŲ‡Ø§Ø˛. اذا ØĒØŗØĒØŽØ¯Ų… ØŽØ¯Ų…ØŠ iCloud, ŲØ§Ų„ØŖØĩŲ„ Ų‚Ø¯ Ų„Ø§ ؊ØĒŲ… Ø§Ų„ŲˆØĩŲˆŲ„ Ų„Ų‡ Ø¨ØŗØ¨Ø¨ ؅؄؁ Ų…ØĒØļØ§ØąØ¨ Ų…ØŽØ˛ŲˆŲ† ؁؊ iCloud",
"asset_not_found_on_icloud": "Ø§Ų„ØŖØĩŲ„ Ų„Ų… ؊ØĒŲ… Ø§ŲŠØŦØ§Ø¯Ų‡ ؁؊ Ø§Ų„ØŦŲ‡Ø§Ø˛, Ø§Ų„ØŖØĩŲ„ Ų‚Ø¯ Ų„Ø§ ؊ØĒŲ… Ø§Ų„ŲˆØĩŲˆŲ„ Ų„Ų‡ Ø¨ØŗØ¨Ø¨ ؅؄؁ Ų…ØĒØļØ§ØąØ¨ Ų…ØŽØ˛ŲˆŲ† ؁؊ iCloud",
"asset_offline": "Ø§Ų„Ų…Ø­ØĒŲˆŲ‰ ØēŲŠØą اØĒØĩØ§Ų„",
"asset_offline_description": "Ų„Ų… ŲŠØšØ¯ Ų‡Ø°Ø§ Ø§Ų„ØŖØĩŲ„ Ø§Ų„ØŽØ§ØąØŦ؊ Ų…ŲˆØŦŲˆØ¯Ų‹Ø§ ØšŲ„Ų‰ Ø§Ų„Ų‚ØąØĩ. ŲŠØąØŦŲ‰ Ø§Ų„Ø§ØĒØĩØ§Ų„ Ø¨Ų…ØŗØ¤ŲˆŲ„ Immich Ų„Ų„Ø­ØĩŲˆŲ„ ØšŲ„Ų‰ Ø§Ų„Ų…ØŗØ§ØšØ¯ØŠ.",
"asset_restored_successfully": "ØĒŲ… Ø§ØŗØĒؚاد؊ Ø§Ų„Ø§ØĩŲ„ Ø¨Ų†ØŦاح",
@@ -650,7 +653,7 @@
"backup_controller_page_background_turn_off": "Ų‚Ų… بØĨŲŠŲ‚Ø§Ų ØĒØ´ØēŲŠŲ„ ØŽØ¯Ų…ØŠ Ø§Ų„ØŽŲ„ŲŲŠØŠ",
"backup_controller_page_background_turn_on": "Ų‚Ų… بØĒØ´ØēŲŠŲ„ ØŽØ¯Ų…ØŠ Ø§Ų„ØŽŲ„ŲŲŠØŠ",
"backup_controller_page_background_wifi": "ŲŲ‚Øˇ ØšŲ„Ų‰ Wi-Fi",
"backup_controller_page_backup": "Ø¯ØšŲ…",
"backup_controller_page_backup": "Ų†ØŗØŽ احØĒŲŠØ§ØˇŲŠ",
"backup_controller_page_backup_selected": "Ø§Ų„Ų…Ø­Ø¯Ø¯: ",
"backup_controller_page_backup_sub": "Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ų„Ų„ØĩŲˆØą ŲˆŲ…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ",
"backup_controller_page_created": "Ø§Ų†Ø´ØĻ ؁؊ :{date}",
@@ -779,6 +782,8 @@
"client_cert_import": "Ø§ØŗØĒŲŠØąØ§Ø¯",
"client_cert_import_success_msg": "ØĒŲ… Ø§ØŗØĒŲŠØąØ§Ø¯ Ø´Ų‡Ø§Ø¯ØŠ Ø§Ų„ØšŲ…ŲŠŲ„",
"client_cert_invalid_msg": "؅؄؁ Ø´Ų‡Ø§Ø¯ØŠ ØšŲ…ŲŠŲ„ ØēŲŠØą ØĩØ§Ų„Ø­ØŠ Ø§Ųˆ ŲƒŲ„Ų…ØŠ ØŗØą ØēŲŠØą ØĩØ­ŲŠØ­ØŠ",
"client_cert_password_message": "ØŖØ¯ØŽŲ„ ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą Ø§Ų„ØŽØ§ØĩØŠ Ø¨Ų‡Ø°Ų‡ Ø§Ų„Ø´Ų‡Ø§Ø¯ØŠ",
"client_cert_password_title": "ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą Ø§Ų„ØŽØ§ØĩØŠ Ø¨Ø§Ų„Ø´Ų‡Ø§Ø¯ØŠ",
"client_cert_remove_msg": "ØĒŲ… Ø§Ø˛Ø§Ų„ØŠ Ø´Ų‡Ø§Ø¯ØŠ Ø§Ų„ØšŲ…ŲŠŲ„",
"client_cert_subtitle": "ŲŠØ¯ØšŲ… Øĩ؊Øē PKCS12 (.p12, .pfx)ŲŲ‚Øˇ. Ø§ØŗØĒŲŠØąØ§Ø¯/Ø§Ø˛Ø§Ų„ØŠ Ø§Ų„Ø´Ų‡Ø§Ø¯Ø§ØĒ Ų…ØĒاح ŲŲ‚Øˇ Ų‚Ø¨Ų„ ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„",
"client_cert_title": "Ø´Ų‡Ø§Ø¯ØŠ Ų…ØŗØĒØŽØ¯Ų… SSL [ØĒØŦØąŲŠØ¨ŲŠØŠ]",
@@ -992,6 +997,11 @@
"editor_close_without_save_prompt": "Ų„Ų† ؊ØĒŲ… Ø­ŲØ¸ Ø§Ų„ØĒØēŲŠŲŠØąØ§ØĒ",
"editor_close_without_save_title": "ØĨØēŲ„Ø§Ų‚ Ø§Ų„Ų…Ø­ØąØąØŸ",
"editor_confirm_reset_all_changes": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ Ų…Ų† ØĨؚاد؊ ØļØ¨Øˇ ØŦŲ…ŲŠØš Ø§Ų„ØĒØēŲŠŲŠØąØ§ØĒ؟",
"editor_discard_edits_confirm": "ØĒØŦØ§Ų‡Ų„ Ø§Ų„ØĒØšØ¯ŲŠŲ„Ø§ØĒ",
"editor_discard_edits_prompt": "Ų„Ø¯ŲŠŲƒ ØĒØšØ¯ŲŠŲ„Ø§ØĒ ØēŲŠØą Ų…Ø­ŲŲˆØ¸ØŠ. Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ Ų…Ų† ØąØēبØĒ؃ ؁؊ ØĒØŦØ§Ų‡Ų„Ų‡Ø§ØŸ",
"editor_discard_edits_title": "ØĒØŦØ§Ų‡Ų„ Ø§Ų„ØĒØšØ¯ŲŠŲ„Ø§ØĒ؟",
"editor_edits_applied_error": "ŲØ´Ų„ ØĒØˇØ¨ŲŠŲ‚ Ø§Ų„ØĒØšØ¯ŲŠŲ„Ø§ØĒ",
"editor_edits_applied_success": "ØĒŲ… ØĒØˇØ¨ŲŠŲ‚ Ø§Ų„ØĒØšØ¯ŲŠŲ„Ø§ØĒ Ø¨Ų†ØŦاح",
"editor_flip_horizontal": "Ø§Ų‚Ų„Ø¨ ØŖŲŲ‚ŲŠŲ‹Ø§",
"editor_flip_vertical": "Ø§Ų‚Ų„Ø¨ ØšŲ…ŲˆØ¯ŲŠŲ‹Ø§",
"editor_orientation": "اØĒØŦØ§Ų‡",
@@ -1192,8 +1202,9 @@
"features": "Ø§Ų„Ų…ŲŠØ˛Ø§ØĒ",
"features_in_development": "Ø§Ų„Ų…ŲŠØ˛Ø§ØĒ Ų‚ŲŠØ¯ Ø§Ų„ØĒØˇŲˆŲŠØą",
"features_setting_description": "ØĨØ¯Ø§ØąØŠ Ų…ŲŠØ˛Ø§ØĒ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚",
"file_name": "Ø§ØŗŲ… Ø§Ų„Ų…Ų„Ų: {file_name}",
"file_name_or_extension": "Ø§ØŗŲ… Ø§Ų„Ų…Ų„Ų ØŖŲˆ Ø§Ų…ØĒØ¯Ø§Ø¯Ų‡",
"file_name_text": "ØŖØŗŲ… Ø§Ų„Ų…Ų„Ų",
"file_name_with_value": "Ø§ØŗŲ… Ø§Ų„Ų…Ų„Ų: {file_name}",
"file_size": "Ø­ØŦŲ… Ø§Ų„Ų…Ų„Ų",
"filename": "Ø§ØŗŲ… Ø§Ų„Ų…Ų„Ų",
"filetype": "Ų†ŲˆØš Ø§Ų„Ų…Ų„Ų",
@@ -1602,7 +1613,6 @@
"not_available": "ØēŲŠØą Ų…ØĒاح",
"not_in_any_album": "Ų„ŲŠØŗØĒ ؁؊ ØŖŲŠ ØŖŲ„Ø¨ŲˆŲ…",
"not_selected": "Ų„Ų… ŲŠØŽØĒØ§Øą",
"note_apply_storage_label_to_previously_uploaded assets": "Ų…Ų„Ø§Ø­Ø¸ØŠ: Ų„ØĒØˇØ¨ŲŠŲ‚ ØŗŲ…ØŠ Ø§Ų„ØĒØŽØ˛ŲŠŲ† ØšŲ„Ų‰ Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ Ø§Ų„ØĒ؊ ØĒŲ… ØąŲØšŲ‡Ø§ Ų…ØŗØ¨Ų‚Ų‹Ø§ØŒ Ų‚Ų… بØĒØ´ØēŲŠŲ„",
"notes": "Ų…Ų„Ø§Ø­Ø¸Ø§ØĒ",
"nothing_here_yet": "Ų„Ø§ ؊؈ØŦد Ø´ŲŠØĄ Ų‡Ų†Ø§ بؚد",
"notification_permission_dialog_content": "Ų„ØĒŲ…ŲƒŲŠŲ† Ø§Ų„ØĨØŽØˇØ§ØąØ§ØĒ ، Ø§Ų†ØĒŲ‚Ų„ ØĨŲ„Ų‰ Ø§Ų„ØĨؚداداØĒ ؈ ا؎ØĒØ§Øą Ø§Ų„ØŗŲ…Ø§Ø­.",
@@ -1804,7 +1814,7 @@
"reassigned_assets_to_new_person": "ØĒŲ…ØĒ ØĨؚاد؊ ØĒØšŲŠŲŠŲ† {count, plural, one {# Ø§Ų„Ų…Ø­ØĒŲˆŲ‰} other {# Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ}} ØĨŲ„Ų‰ Ø´ØŽØĩ ØŦØ¯ŲŠØ¯",
"reassing_hint": "ØĒØšŲŠŲŠŲ† Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ Ø§Ų„Ų…Ø­Ø¯Ø¯ØŠ Ų„Ø´ØŽØĩ Ų…ŲˆØŦŲˆØ¯",
"recent": "Ø­Ø¯ŲŠØĢ",
"recent-albums": "ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ Ø§Ų„Ø­Ø¯ŲŠØĢØŠ",
"recent_albums": "ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ Ø§Ų„Ø­Ø¯ŲŠØĢØŠ",
"recent_searches": "ØšŲ…Ų„ŲŠØ§ØĒ Ø§Ų„Ø¨Ø­ØĢ Ø§Ų„ØŖØŽŲŠØąØŠ",
"recently_added": "اØļ؊؁ Ų…Ø¤ØŽØąØ§",
"recently_added_page_title": "ØŖØļ؊؁ Ų…Ø¤ØŽØąØ§",
@@ -2295,6 +2305,7 @@
"upload_details": "ØĒŲØ§ØĩŲŠŲ„ Ø§Ų„ØąŲØš",
"upload_dialog_info": "Ų‡Ų„ ØĒØąŲŠØ¯ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ų„Ų„ØŖØĩŲˆŲ„ (Ø§Ų„ØŖØĩŲˆŲ„) Ø§Ų„Ų…Ø­Ø¯Ø¯ØŠ ØĨŲ„Ų‰ Ø§Ų„ØŽØ§Ø¯Ų…ØŸ",
"upload_dialog_title": "ØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØŖØĩŲˆŲ„",
"upload_error_with_count": "ØŽØˇØŖ ؁؊ ØąŲØš {count, plural, one {# اØĩŲ„} other {# اØĩŲˆŲ„}}",
"upload_errors": "ØĨ؃ØĒŲ…Ų„ Ø§Ų„ØąŲØš Ų…Øš {count, plural, one {# ØŽØˇØŖ} other {# ØŖØŽØˇØ§ØĄ}}, Ų‚Ų… بØĒØ­Ø¯ŲŠØĢ Ø§Ų„ØĩŲØ­ØŠ Ų„ØąØ¤ŲŠØŠ Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ Ø§Ų„ØŦØ¯ŲŠØ¯ØŠ Ø§Ų„ØĒ؊ ØĒŲ… ØąŲØšŲ‡Ø§.",
"upload_finished": "ØĒŲ… Ø§Ų„Ø§Ų†ØĒŲ‡Ø§ØĄ Ų…Ų† Ø§Ų„ØąŲØš",
"upload_progress": "Ų…ØĒØ¨Ų‚ŲŠØŠ {remaining, number} - Ų…ØšØ§Ų„ØŦØŠ {processed, number}/{total, number}",

View File

@@ -380,7 +380,6 @@
"favorite": "ĐŖ Đ°ĐąŅ€Đ°ĐŊŅ‹Đŧ",
"favorite_or_unfavorite_photo": "Đ”Đ°Đ´Đ°Ņ†ŅŒ айО Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Ņ„ĐžŅ‚Đ° С Đ°ĐąŅ€Đ°ĐŊĐ°ĐŗĐ°",
"favorites": "ĐĐąŅ€Đ°ĐŊŅ‹Ņ",
"file_name": "Назва Ņ„Đ°ĐšĐģа: {file_name}",
"filename": "Назва Ņ„Đ°ĐšĐģа",
"filetype": "ĐĸŅ‹Đŋ Ņ„Đ°ĐšĐģа",
"filter": "Đ¤Ņ–ĐģŅŒŅ‚Ņ€",
@@ -458,7 +457,7 @@
"reassign": "ПĐĩŅ€Đ°ĐŋŅ€Ņ‹ĐˇĐŊĐ°Ņ‡Ņ‹Ņ†ŅŒ",
"reassing_hint": "ĐŸŅ€Ņ‹ĐŋŅ–ŅĐ°Ņ†ŅŒ Đ˛Ņ‹ĐąŅ€Đ°ĐŊŅ‹Ņ аĐēŅ‚Ņ‹Đ˛Ņ‹ ҖҁĐŊŅƒŅŽŅ‡Đ°Đš Đ°ŅĐžĐąĐĩ",
"recent": "ĐŅĐ´Đ°ŅžĐŊŅ–",
"recent-albums": "ĐŅĐ´Đ°ŅžĐŊŅ–Ņ аĐģŅŒĐąĐžĐŧŅ‹",
"recent_albums": "ĐŅĐ´Đ°ŅžĐŊŅ–Ņ аĐģŅŒĐąĐžĐŧŅ‹",
"recent_searches": "ĐŅĐ´Đ°ŅžĐŊŅ–Ņ ĐŋĐžŅˆŅƒĐēŅ–",
"recently_added": "ĐŅĐ´Đ°ŅžĐŊа дададСĐĩĐŊа",
"refresh_faces": "АйĐŊĐ°Đ˛Ņ–Ņ†ŅŒ Ņ‚Đ˛Đ°Ņ€Ņ‹",

View File

@@ -272,7 +272,7 @@
"oauth_auto_register": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊа Ņ€ĐĩĐŗĐ¸ŅŅ‚Ņ€Đ°Ņ†Đ¸Ņ",
"oauth_auto_register_description": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž Ņ€ĐĩĐŗĐ¸ŅŅ‚Ņ€Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐŊОви ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи ҁĐģĐĩĐ´ вĐģиСаĐŊĐĩ ҁ OAuth",
"oauth_button_text": "ĐĸĐĩĐēҁ҂ ĐŊа ĐąŅƒŅ‚ĐžĐŊа",
"oauth_client_secret_description": "Đ˜ĐˇĐ¸ŅĐēва ҁĐĩ, ĐēĐžĐŗĐ°Ņ‚Đž Đ´ĐžŅŅ‚Đ°Đ˛Ņ‡Đ¸Đēа ĐŊа OAuth ĐŊĐĩ ĐŋĐžĐ´Đ´ŅŠŅ€Đļа PKCE (Proof Key for Code Exchange)",
"oauth_client_secret_description": "Đ—Đ°Đ´ŅŠĐģĐļĐ¸Ņ‚ĐĩĐģĐŊĐž Са ĐŋОвĐĩŅ€Đ¸Ņ‚ĐĩĐģĐĩĐŊ ĐēĐģиĐĩĐŊŅ‚ иĐģи ĐēĐžĐŗĐ°Ņ‚Đž ĐŊĐĩ ҁĐĩ ĐŋĐžĐ´Đ´ŅŠŅ€Đļа PKCE (Proof Key for Code Exchange) Са ĐŋŅƒĐąĐģĐ¸Ņ‡ĐĩĐŊ ĐēĐģиĐĩĐŊŅ‚.",
"oauth_enable_description": "ВĐģиСаĐŊĐĩ ҁ OAuth",
"oauth_mobile_redirect_uri": "URI Са ĐŧОйиĐģĐŊĐž ĐŋŅ€ĐĩĐŊĐ°ŅĐžŅ‡Đ˛Đ°ĐŊĐĩ",
"oauth_mobile_redirect_uri_override": "URI ĐŋŅ€ĐĩĐŊĐ°ŅĐžŅ‡Đ˛Đ°ĐŊĐĩ Са ĐŧОйиĐģĐŊи ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°",
@@ -383,7 +383,7 @@
"transcoding_hardware_acceleration": "ĐĨĐ°Ņ€Đ´ŅƒĐĩŅ€ĐŊĐž ҃ҁĐēĐžŅ€ĐĩĐŊиĐĩ",
"transcoding_hardware_acceleration_description": "ЕĐēҁĐŋĐĩŅ€Đ¸ĐŧĐĩĐŊŅ‚Đ°ĐģĐŊĐž: ĐŧĐŊĐžĐŗĐž ĐŋĐž-ĐąŅŠŅ€ĐˇĐž Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ, ĐŊĐž ĐŧĐžĐļĐĩ да ĐŋĐžĐŊиĐļи ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛ĐžŅ‚Đž ĐŋŅ€Đ¸ ŅŅŠŅ‰Đ¸Ņ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚",
"transcoding_hardware_decoding": "ĐĨĐ°Ņ€Đ´ŅƒĐĩŅ€ĐŊĐž Đ´ĐĩĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ",
"transcoding_hardware_decoding_setting_description": "ĐŸŅ€Đ¸ĐģĐ°ĐŗĐ° ҁĐĩ ŅĐ°ĐŧĐž Са NVENC, QSV и RKMPP. АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ° ҃ҁĐēĐžŅ€ĐĩĐŊиĐĩ ĐžŅ‚ ĐēŅ€Đ°Đš Đ´Đž ĐēŅ€Đ°Đš, вĐŧĐĩŅŅ‚Đž ŅĐ°ĐŧĐž да ҃ҁĐēĐžŅ€ŅĐ˛Đ° ĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž. МоĐļĐĩ да ĐŊĐĩ Ņ€Đ°ĐąĐžŅ‚Đ¸ ҁ Đ˛ŅĐ¸Ņ‡Đēи видĐĩĐžĐēĐģиĐŋОвĐĩ.",
"transcoding_hardware_decoding_setting_description": "АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ° ҃ҁĐēĐžŅ€ĐĩĐŊиĐĩ ĐžŅ‚ ĐēŅ€Đ°Đš Đ´Đž ĐēŅ€Đ°Đš, вĐŧĐĩŅŅ‚Đž ŅĐ°ĐŧĐž да ҃ҁĐēĐžŅ€ŅĐ˛Đ° ĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž. МоĐļĐĩ да ĐŊĐĩ Ņ€Đ°ĐąĐžŅ‚Đ¸ ҁ Đ˛ŅĐ¸Ņ‡Đēи видĐĩĐžĐēĐģиĐŋОвĐĩ.",
"transcoding_max_b_frames": "МаĐēŅĐ¸ĐŧаĐģĐŊи B-҄ҀĐĩĐšĐŧа",
"transcoding_max_b_frames_description": "По-Đ˛Đ¸ŅĐžĐēĐ¸Ņ‚Đĩ ŅŅ‚ĐžĐšĐŊĐžŅŅ‚Đ¸ ĐŋĐžĐ´ĐžĐąŅ€ŅĐ˛Đ°Ņ‚ ĐĩŅ„ĐĩĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚Ņ‚Đ° ĐŊа ĐēĐžĐŧĐŋŅ€ĐĩŅĐ¸ŅŅ‚Đ°, ĐŊĐž ĐˇĐ°ĐąĐ°Đ˛ŅŅ‚ Ņ€Đ°ĐˇĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž. МоĐļĐĩ да ĐŊĐĩ Đĩ ŅŅŠĐ˛ĐŧĐĩŅŅ‚Đ¸Đŧ ҁ Ņ…Đ°Ņ€Đ´ŅƒĐĩŅ€ĐŊĐžŅ‚Đž ҃ҁĐēĐžŅ€ĐĩĐŊиĐĩ ĐŊа ĐŋĐž-ŅŅ‚Đ°Ņ€Đ¸ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°. 0 Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ° B-҄ҀĐĩĐšĐŧа, Đ´ĐžĐēĐ°Ņ‚Đž -1 Садава Ņ‚Đ°ĐˇĐ¸ ŅŅ‚ĐžĐšĐŊĐžŅŅ‚ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž.",
"transcoding_max_bitrate": "МаĐēŅĐ¸ĐŧаĐģĐĩĐŊ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚",
@@ -572,6 +572,9 @@
"asset_list_layout_sub_title": "РаСĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ",
"asset_list_settings_subtitle": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐŊа ĐŧŅ€ĐĩĐļĐ°Ņ‚Đ° ĐŊа Ņ€Đ°ĐˇĐŋĐžĐģĐ°ĐŗĐ°ĐŊĐĩ ĐŊа ҁĐŊиĐŧĐēи",
"asset_list_settings_title": "РаСĐŋĐžĐģĐ°ĐŗĐ°ĐŊĐĩ ĐŊа ҁĐŊиĐŧĐēи",
"asset_not_found_on_device_android": "ОбĐĩĐēŅ‚ŅŠŅ‚ ĐŊĐĩ Đĩ ĐŊаĐŧĐĩŅ€ĐĩĐŊ ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž",
"asset_not_found_on_device_ios": "ОбĐĩĐēŅ‚ŅŠŅ‚ ĐŊĐĩ Đĩ ĐŊаĐŧĐĩŅ€ĐĩĐŊ ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž. АĐēĐž иСĐŋĐžĐģĐˇĐ˛Đ°Ņ‚Đĩ iCloud, ОйĐĩĐēŅ‚ŅŠŅ‚ ĐŧĐžĐļĐĩ да Đĩ ĐŊĐĩĐ´ĐžŅŅ‚ŅŠĐŋĐĩĐŊ ĐŋĐžŅ€Đ°Đ´Đ¸ ĐŋĐžĐ˛Ņ€ĐĩĐ´ĐĩĐŊ Ņ„Đ°ĐšĐģ, ŅŅŠŅ…Ņ€Đ°ĐŊĐĩĐŊ в iCloud",
"asset_not_found_on_icloud": "ОбĐĩĐēŅ‚ŅŠŅ‚ ĐŊĐĩ Đĩ ĐŊаĐŧĐĩŅ€ĐĩĐŊ в iCloud. ОбĐĩĐēŅ‚ŅŠŅ‚ ĐŧĐžĐļĐĩ да Đĩ ĐŊĐĩĐ´ĐžŅŅ‚ŅŠĐŋĐĩĐŊ ĐŋĐžŅ€Đ°Đ´Đ¸ ĐŋĐžĐ˛Ņ€ĐĩĐ´ĐĩĐŊ Ņ„Đ°ĐšĐģ, ŅŅŠŅ…Ņ€Đ°ĐŊĐĩĐŊ в iCloud",
"asset_offline": "ЕĐģĐĩĐŧĐĩĐŊŅ‚ŅŠŅ‚ Đĩ ĐžŅ„ĐģаКĐŊ",
"asset_offline_description": "ĐĸОСи Đ˛ŅŠĐŊ҈ĐĩĐŊ аĐēŅ‚Đ¸Đ˛ вĐĩ҇Đĩ ĐŊĐĩ ҁĐĩ ĐŊаĐŧĐ¸Ņ€Đ° ĐŊа Đ´Đ¸ŅĐēа. МоĐģŅ, ŅĐ˛ŅŠŅ€ĐļĐĩŅ‚Đĩ ҁĐĩ ҁ адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ° ĐŊа Immich Са ĐŋĐžĐŧĐžŅ‰.",
"asset_restored_successfully": "ĐŖŅĐŋĐĩ҈ĐŊĐž Đ˛ŅŠĐˇŅŅ‚Đ°ĐŊОвĐĩĐŊ ОйĐĩĐēŅ‚",
@@ -779,6 +782,8 @@
"client_cert_import": "ИĐŧĐŋĐžŅ€Ņ‚",
"client_cert_import_success_msg": "КĐģиĐĩĐŊ҂ҁĐēĐ¸Ņ ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ Đĩ иĐŧĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊ",
"client_cert_invalid_msg": "НĐĩваĐģидĐĩĐŊ ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ иĐģи ĐŗŅ€Đĩ҈ĐŊа ĐŋĐ°Ņ€ĐžĐģа",
"client_cert_password_message": "Đ’ŅŠĐ˛ĐĩĐ´ĐĩŅ‚Đĩ ĐŋĐ°Ņ€ĐžĐģа Са Ņ‚ĐžĐˇĐ¸ ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚",
"client_cert_password_title": "ĐŸĐ°Ņ€ĐžĐģа Са ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚",
"client_cert_remove_msg": "КĐģиĐĩĐŊ҂ҁĐēĐ¸Ņ ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ Đĩ ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚",
"client_cert_subtitle": "ĐŸĐžĐ´Đ´ŅŠŅ€Đļа ҁĐĩ ŅĐ°ĐŧĐž Ņ„ĐžŅ€ĐŧĐ°Ņ‚ PKCS12 (.p12, .pfx). ИĐŧĐŋĐžŅ€Ņ‚/ĐŋŅ€ĐĩĐŧĐ°Ņ…Đ˛Đ°ĐŊĐĩ ĐŊа ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ ĐŧĐžĐļĐĩ ŅĐ°ĐŧĐž ĐŋŅ€Đĩди вĐŋĐ¸ŅĐ˛Đ°ĐŊĐĩ в ŅĐ¸ŅŅ‚ĐĩĐŧĐ°Ņ‚Đ°",
"client_cert_title": "КĐģиĐĩĐŊ҂ҁĐēи SSL ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ [ЕКСПЕРИМЕНĐĸАЛНО]",
@@ -992,6 +997,11 @@
"editor_close_without_save_prompt": "ĐŸŅ€ĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ ĐŊŅĐŧа да ĐąŅŠĐ´Đ°Ņ‚ СаĐŋаСĐĩĐŊи",
"editor_close_without_save_title": "Đ—Đ°Ņ‚Đ˛Đ°Ņ€ŅĐŊĐĩ ĐŊа Ņ€ĐĩдаĐēŅ‚ĐžŅ€Đ°?",
"editor_confirm_reset_all_changes": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да Đ˛ŅŠĐˇŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚Đĩ Đ˛ŅĐ¸Ņ‡Đēи ĐŋŅ€ĐžĐŧĐĩĐŊи?",
"editor_discard_edits_confirm": "ĐžŅ‚Ņ…Đ˛ŅŠŅ€Đģи ĐŋŅ€ĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ",
"editor_discard_edits_prompt": "ИĐŧĐ°Ņ‚Đĩ ĐŊĐĩСаĐŋаСĐĩĐŊи ĐŋŅ€ĐžĐŧĐĩĐŊи. ĐĐ°Đ¸ŅŅ‚Đ¸ĐŊа Đģи Đ¸ŅĐēĐ°Ņ‚Đĩ да ĐŗĐ¸ ĐžŅ‚Ņ…Đ˛ŅŠŅ€ĐģĐ¸Ņ‚Đĩ?",
"editor_discard_edits_title": "ĐžŅ‚Ņ…Đ˛ŅŠŅ€ĐģŅĐŧĐĩ Đģи ĐŋŅ€ĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ?",
"editor_edits_applied_error": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ĐŋŅ€Đ¸ĐģĐ°ĐŗĐ°ĐŊĐĩ ĐŊа ĐŋŅ€ĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ",
"editor_edits_applied_success": "ĐŖŅĐŋĐĩ҈ĐŊĐž ĐŋŅ€Đ¸ĐģĐ°ĐŗĐ°ĐŊĐĩ ĐŊа ĐŋŅ€ĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ",
"editor_flip_horizontal": "ĐžĐąŅŠŅ€ĐŊи Ņ…ĐžŅ€Đ¸ĐˇĐžĐŊŅ‚Đ°ĐģĐŊĐž",
"editor_flip_vertical": "ĐžĐąŅŠŅ€ĐŊи вĐĩŅ€Ņ‚Đ¸ĐēаĐģĐŊĐž",
"editor_orientation": "ĐžŅ€Đ¸ĐĩĐŊŅ‚Đ°Ņ†Đ¸Ņ",
@@ -1149,7 +1159,7 @@
},
"errors_text": "Đ“Ņ€Đĩ҈Đēи",
"exclusion_pattern": "ШайĐģĐžĐŊ Са иСĐēĐģŅŽŅ‡ĐĩĐŊиĐĩ",
"exif": "Exif",
"exif": "Еxif",
"exif_bottom_sheet_description": "Добави ОĐŋĐ¸ŅĐ°ĐŊиĐĩ...",
"exif_bottom_sheet_description_error": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ОйĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐĩ ĐŊа ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ",
"exif_bottom_sheet_details": "ПОДРОБНОСĐĸИ",
@@ -1192,8 +1202,9 @@
"features": "Đ¤ŅƒĐŊĐēŅ†Đ¸Đ¸",
"features_in_development": "Đ¤ŅƒĐŊĐēŅ†Đ¸Đ¸ в ĐŋŅ€ĐžŅ†Đĩҁ ĐŊа Ņ€Đ°ĐˇŅ€Đ°ĐąĐžŅ‚Đēа",
"features_setting_description": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа Ņ„ŅƒĐŊĐēŅ†Đ¸Đ¸Ņ‚Đĩ ĐŊа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž",
"file_name": "ИĐŧĐĩ ĐŊа Ņ„Đ°ĐšĐģа: {file_name}",
"file_name_or_extension": "ИĐŧĐĩ ĐŊа Ņ„Đ°ĐšĐģ иĐģи Ņ€Đ°ĐˇŅˆĐ¸Ņ€ĐĩĐŊиĐĩ",
"file_name_text": "ИĐŧe ĐŊа Ņ„Đ°ĐšĐģ",
"file_name_with_value": "ИĐŧĐĩ ĐŊа Ņ„Đ°ĐšĐģ: {file_name}",
"file_size": "РаСĐŧĐĩŅ€ ĐŊа Ņ„Đ°ĐšĐģа",
"filename": "ИĐŧĐĩ ĐŊа Ņ„Đ°ĐšĐģ",
"filetype": "ĐĸиĐŋ ĐŊа Ņ„Đ°ĐšĐģ",
@@ -1215,7 +1226,7 @@
"free_up_space_description": "ĐŸŅ€ĐĩĐŧĐĩҁ҂ĐĩŅ‚Đĩ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐ¸Ņ‚Đĩ ҁĐŊиĐŧĐēи и видĐĩа в ĐēĐžŅˆŅ‡ĐĩŅ‚Đž ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž, Са да ĐžŅĐ˛ĐžĐąĐžĐ´Đ¸Ņ‚Đĩ ĐŧŅŅŅ‚Đž. КоĐŋĐ¸ŅŅ‚Đ° ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ° ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ СаĐŋаСĐĩĐŊи.",
"free_up_space_settings_subtitle": "ĐžŅĐ˛ĐžĐąĐžĐļдаваĐŊĐĩ ĐŊа ĐŧŅŅŅ‚Đž Са ŅŅŠŅ…Ņ€Đ°ĐŊĐĩĐŊиĐĩ ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž",
"full_path": "ĐŸŅŠĐģĐĩĐŊ ĐŋŅŠŅ‚: {path}",
"gcast_enabled": "Google Cast",
"gcast_enabled": "GООgle Cast",
"gcast_enabled_description": "За да Ņ€Đ°ĐąĐžŅ‚Đ¸ Ņ‚Đ°ĐˇĐ¸ Ņ„ŅƒĐŊĐēŅ†Đ¸Ņ ĐˇĐ°Ņ€ĐĩĐļда Đ˛ŅŠĐŊ҈ĐŊи Ņ€ĐĩŅŅƒŅ€ŅĐ¸ ĐžŅ‚ Google.",
"general": "ĐžĐąŅ‰Đ¸",
"geolocation_instruction_location": "ИСйĐĩŅ€ĐĩŅ‚Đĩ ОйĐĩĐēŅ‚ ҁ GPS ĐēĐžĐžŅ€Đ´Đ¸ĐŊĐ°Ņ‚Đ¸ Са да иСĐŋĐžĐģĐˇĐ˛Đ°Ņ‚Đĩ Ņ‚ŅŅ… иĐģи иСйĐĩŅ€ĐĩŅ‚Đĩ ĐŧŅŅŅ‚Đž Đ´Đ¸Ņ€ĐĩĐēŅ‚ĐŊĐž ĐžŅ‚ ĐēĐ°Ņ€Ņ‚Đ°Ņ‚Đ°",
@@ -1404,7 +1415,7 @@
"login_form_api_exception": "Đ“Ņ€Đĩ҈Đēа в ĐēĐžĐŧ҃ĐŊиĐēĐ°Ņ†Đ¸ŅŅ‚Đ°. МоĐģŅ, ĐŋŅ€ĐžĐ˛ĐĩŅ€Đ¸ URL ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ° и ĐžĐŋĐ¸Ņ‚Đ°Đš ĐŋаĐē.",
"login_form_back_button_text": "ĐžĐąŅ€Đ°Ņ‚ĐŊĐž",
"login_form_email_hint": "youremail@email.com",
"login_form_endpoint_hint": "http://your-server-ip:port",
"login_form_endpoint_hint": "http://yĐžur-server-ip:port",
"login_form_endpoint_url": "URL Đ°Đ´Ņ€Đĩҁ ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ°",
"login_form_err_http": "МоĐģŅ, ĐžĐŋŅ€ĐĩĐ´ĐĩĐģи ĐŋŅ€ĐžŅ‚ĐžĐēĐžĐģа http:// иĐģи https://",
"login_form_err_invalid_email": "НĐĩваĐģидĐĩĐŊ иĐŧĐĩĐšĐģ Đ°Đ´Ņ€Đĩҁ",
@@ -1602,7 +1613,6 @@
"not_available": "НĐĩĐŊаĐģĐ¸Ņ‡ĐŊĐž",
"not_in_any_album": "НĐĩ Đĩ в ĐŊиĐēОК аĐģĐąŅƒĐŧ",
"not_selected": "НĐĩ Đĩ Đ¸ĐˇĐąŅ€Đ°ĐŊĐž",
"note_apply_storage_label_to_previously_uploaded assets": "ЗабĐĩĐģĐĩĐļĐēа: За да ĐŋŅ€Đ¸ĐģĐžĐļĐ¸Ņ‚Đĩ ĐĩŅ‚Đ¸ĐēĐĩŅ‚Đ° Са ŅŅŠŅ…Ņ€Đ°ĐŊĐĩĐŊиĐĩ ĐēҊĐŧ ĐŋŅ€ĐĩĐ´Đ˛Đ°Ņ€Đ¸Ņ‚ĐĩĐģĐŊĐž ĐēĐ°Ņ‡ĐĩĐŊи аĐēŅ‚Đ¸Đ˛Đ¸, ŅŅ‚Đ°Ņ€Ņ‚Đ¸Ņ€Đ°ĐšŅ‚Đĩ",
"notes": "БĐĩĐģĐĩĐļĐēи",
"nothing_here_yet": "Đ—Đ°ŅĐĩĐŗĐ° Ņ‚ŅƒĐē ĐŊŅĐŧа ĐŊĐ¸Ņ‰Đž",
"notification_permission_dialog_content": "За да вĐēĐģŅŽŅ‡Đ¸Ņˆ иСвĐĩŅŅ‚Đ¸ŅŅ‚Đ°, ĐžŅ‚Đ¸Đ´Đ¸ в ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи и иСйĐĩŅ€Đ¸ Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸.",
@@ -1804,7 +1814,7 @@
"reassigned_assets_to_new_person": "ĐŸŅ€ĐĩĐŊаСĐŊĐ°Ņ‡ĐĩĐŊи {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°}} ĐŊа ĐŊОв Ņ‡ĐžĐ˛ĐĩĐē",
"reassing_hint": "НазĐŊĐ°Ņ‡Đ¸ Đ¸ĐˇĐąŅ€Đ°ĐŊĐ¸Ņ‚Đĩ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐŊа ŅŅŠŅ‰ĐĩŅŅ‚Đ˛ŅƒĐ˛Đ°Ņ‰Đž ĐģĐ¸Ņ†Đĩ",
"recent": "ĐĄĐēĐžŅ€ĐžŅˆĐŊи",
"recent-albums": "ĐĄĐēĐžŅ€ĐžŅˆĐŊи АĐģĐąŅƒĐŧи",
"recent_albums": "ĐĄĐēĐžŅ€ĐžŅˆĐŊи АĐģĐąŅƒĐŧи",
"recent_searches": "ĐĄĐēĐžŅ€ĐžŅˆĐŊи Ņ‚ŅŠŅ€ŅĐĩĐŊĐ¸Ņ",
"recently_added": "ĐĐ°ŅĐēĐžŅ€Đž дОйавĐĩĐŊĐž",
"recently_added_page_title": "ĐĐ°ŅĐēĐžŅ€Đž дОйавĐĩĐŊĐž",
@@ -2070,7 +2080,7 @@
"shared_link_edit_expire_after_option_year": "{count} ĐŗĐžĐ´Đ¸ĐŊи",
"shared_link_edit_password_hint": "Đ’ŅŠĐ˛Đĩди ĐŋĐ°Ņ€ĐžĐģа Са Đ´ĐžŅŅ‚ŅŠĐŋ Đ´Đž ҁĐŋОдĐĩĐģĐĩĐŊ Ņ€ĐĩŅŅƒŅ€Ņ",
"shared_link_edit_submit_button": "ОбĐŊОви Đ˛Ņ€ŅŠĐˇĐēĐ°Ņ‚Đ°",
"shared_link_error_server_url_fetch": "НĐĩ ĐŧĐžĐļĐĩ да ҁĐĩ иСвĐģĐĩ҇Đĩ URL Đ°Đ´Ņ€ĐĩŅŅŠŅ‚ ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ°",
"shared_link_error_server_url_fetch": "НĐĩ ĐŧĐžĐļĐĩ да ҁĐĩ иСвĐģĐĩ҇Đĩ url-Đ°Đ´Ņ€ĐĩŅŅŠŅ‚ ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ°",
"shared_link_expires_day": "Đ˜ĐˇŅ‚Đ¸Ņ‡Đ° ҁĐģĐĩĐ´ {count} Đ´ĐĩĐŊ",
"shared_link_expires_days": "Đ˜ĐˇŅ‚Đ¸Ņ‡Đ° ҁĐģĐĩĐ´ {count} Đ´ĐŊи",
"shared_link_expires_hour": "Đ˜ĐˇŅ‚Đ¸Ņ‡Đ° ҁĐģĐĩĐ´ {count} Ņ‡Đ°Ņ",
@@ -2295,6 +2305,7 @@
"upload_details": "ДĐĩŅ‚Đ°ĐšĐģи Са ĐēĐ°Ņ‡Đ˛Đ°ĐŊĐĩŅ‚Đž",
"upload_dialog_info": "Đ˜ŅĐēĐ°Ņ‚Đĩ Đģи да Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°Ņ‚Đĩ ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ° Đ¸ĐˇĐąŅ€Đ°ĐŊĐ¸Ņ‚Đĩ ОйĐĩĐēŅ‚Đ¸?",
"upload_dialog_title": "ĐšĐ°Ņ‡Đ¸ ОйĐĩĐēŅ‚",
"upload_error_with_count": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа {count, plural, one {# ОйĐĩĐēŅ‚} other {# ОйĐĩĐēŅ‚Đ°}}",
"upload_errors": "ĐšĐ°Ņ‡Đ˛Đ°ĐŊĐĩŅ‚Đž Đĩ ĐˇĐ°Đ˛ŅŠŅˆĐĩĐŊĐž ҁ {count, plural, one {# ĐŗŅ€Đĩ҈Đēа} other {# ĐŗŅ€Đĩ҈Đēи}}, ОйĐŊОвĐĩŅ‚Đĩ ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Đ°Ņ‚Đ° Са да Đ˛Đ¸Đ´Đ¸Ņ‚Đĩ ĐŊĐžĐ˛Đ¸Ņ‚Đĩ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸.",
"upload_finished": "ĐšĐ°Ņ‡Đ˛Đ°ĐŊĐĩŅ‚Đž ĐˇĐ°Đ˛ŅŠŅ€ŅˆĐ¸",
"upload_progress": "ĐžŅŅ‚Đ°Đ˛Đ°Ņ‚ {remaining, number} - ĐžĐąŅ€Đ°ĐąĐžŅ‚ĐĩĐŊи {processed, number}/{total, number}",

View File

@@ -17,7 +17,7 @@
"readonly_mode_enabled": "Mod blo yu no save janjem i on",
"reassigned_assets_to_new_person": "Janjem{count, plural, one {# asset} other {# assets}} blo nu man",
"reassing_hint": "janjem ol sumtin yu bin joos i go blo wan man",
"recent-albums": "album i no old tu mas",
"recent_albums": "album i no old tu mas",
"recent_searches": "lukabout wea i no old tu mas",
"time_based_memories_duration": "hao mus second blo wan wan imij i stap lo scrin.",
"timezone": "taemzon",

View File

@@ -40,7 +40,9 @@
"add_to_albums_count": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧋āĻ— āĻ•āϰ⧁āύ ({count})",
"add_to_bottom_bar": "āĻ āϝ⧋āĻ— āĻ•āϰ⧁āύ",
"add_to_shared_album": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧋āĻ— āĻ•āϰ⧁āύ",
"add_upload_to_stack": "āφāĻĒāϞ⧋āĻĄ āĻ¸ā§āĻŸā§āϝāĻžāϕ⧇ āϝ⧋āĻ— āĻ•āϰ⧁āύ",
"add_url": "āϞāĻŋāĻ™ā§āĻ• āϝ⧋āĻ— āĻ•āϰ⧁āύ",
"add_workflow_step": "āĻ•āĻžāĻœā§‡āϰ āϧāĻžāĻĒ āϝ⧋āĻ— āĻ•āϰ⧁āύ",
"added_to_archive": "āφāĻ°ā§āĻ•āĻžāχāĻ­ āĻ āϝ⧋āĻ— āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇",
"added_to_favorites": "āĻĢ⧇āĻ­āĻžāϰāĻŋāĻŸā§‡ āϝ⧋āĻ— āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇",
"added_to_favorites_count": "āĻĒāĻ›āĻ¨ā§āĻĻ⧇āϰ āϤāĻžāϞāĻŋāĻ•āĻžā§Ÿ {count, number} āϝ⧋āĻ— āĻ•āϰāĻž āĻšā§Ÿā§‡āϛ⧇",
@@ -73,6 +75,7 @@
"confirm_reprocess_all_faces": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āϏāĻŽāĻ¸ā§āϤ āĻŽā§āĻ– āĻĒ⧁āύāϰāĻžāϝāĻŧ āĻĒā§āϰāĻ•ā§āϰāĻŋāϝāĻŧāĻž āĻ•āϰāϤ⧇ āϚāĻžāύ? āĻāϟāĻŋ āύāĻžāĻŽāϝ⧁āĻ•ā§āϤ āĻŦā§āϝāĻ•ā§āϤāĻŋāĻĻ⧇āϰāĻ“ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻŦ⧇āĨ¤",
"confirm_user_password_reset": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ {user} āĻāϰ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āϰāĻŋāϏ⧇āϟ āĻ•āϰāϤ⧇ āϚāĻžāύ?",
"confirm_user_pin_code_reset": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ {user} āĻāϰ āĻĒāĻŋāύ āϕ⧋āĻĄ āϰāĻŋāϏ⧇āϟ āĻ•āϰāϤ⧇ āϚāĻžāύ?",
"copy_config_to_clipboard_description": "āĻŦāĻ°ā§āϤāĻŽāĻžāύ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻ•āύāĻĢāĻŋāĻ—āĻžāϰ⧇āĻļāύ āĻāĻ•āϟāĻŋ JSON āĻ…āĻŦāĻœā§‡āĻ•ā§āϟ āĻšāĻŋāϏ⧇āĻŦ⧇ āĻ•ā§āϞāĻŋāĻĒāĻŦā§‹āĻ°ā§āĻĄā§‡ āĻ•āĻĒāĻŋ āĻ•āϰ⧁āύ",
"create_job": "job āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ",
"cron_expression": "āĻ•ā§āϰ⧋āύ āĻāĻ•ā§āϏāĻĒā§āϰ⧇āĻļāύ",
"cron_expression_description": "āĻ•ā§āϰ⧋āύ āĻĢāĻ°ā§āĻŽā§āϝāĻžāϟ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻ¸ā§āĻ•ā§āϝāĻžāύāĻŋāĻ‚ āĻŦā§āϝāĻŦāϧāĻžāύ āϏ⧇āϟ āĻ•āϰ⧁āύāĨ¤ āφāϰāĻ“ āϤāĻĨā§āϝ⧇āϰ āϜāĻ¨ā§āϝ āĻĻāϝāĻŧāĻž āĻ•āϰ⧇ āĻĻ⧇āϖ⧁āύ āϝ⧇āĻŽāύ <link>Crontab Guru</link>",
@@ -80,6 +83,8 @@
"disable_login": "āϞāĻ—āχāύ āĻ…āĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ",
"duplicate_detection_job_description": "āĻ…āύ⧁āϰ⧂āĻĒ āĻ›āĻŦāĻŋ āϏāύāĻžāĻ•ā§āϤ āĻ•āϰāϤ⧇ āϏāĻŽā§āĻĒāĻĻāϗ⧁āϞāĻŋāϤ⧇ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āϚāĻžāϞāĻžāύāĨ¤ āĻ¸ā§āĻŽāĻžāĻ°ā§āϟ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ⧇āϰ āωāĻĒāϰ āύāĻŋāĻ°ā§āĻ­āϰ āĻ•āϰ⧇",
"exclusion_pattern_description": "āĻāĻ•ā§āϏāĻ•ā§āϞ⧁āĻļāύ āĻĒā§āϝāĻžāϟāĻžāĻ°ā§āύ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āφāĻĒāύāĻŋ āφāĻĒāύāĻžāϰ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āĻĢāĻžāχāϞ āĻāĻŦāĻ‚ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰāϗ⧁āϞāĻŋāϕ⧇ āωāĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŦ⧇āύāĨ¤ āϝāĻĻāĻŋ āφāĻĒāύāĻžāϰ āĻāĻŽāύ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻĨāĻžāϕ⧇ āϝ⧇āĻ–āĻžāύ⧇ āĻāĻŽāύ āĻĢāĻžāχāϞ āĻĨāĻžāϕ⧇ āϝāĻž āφāĻĒāύāĻŋ āφāĻŽāĻĻāĻžāύāĻŋ āĻ•āϰāϤ⧇ āϚāĻžāύ āύāĻž, āϝ⧇āĻŽāύ RAW āĻĢāĻžāχāϞāĨ¤",
"export_config_as_json_description": "āĻŦāĻ°ā§āϤāĻŽāĻžāύ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻ•āύāĻĢāĻŋāĻ—āĻžāϰ⧇āĻļāύ āĻāĻ•āϟāĻŋ JSON āĻĢāĻžāχāϞ āĻšāĻŋāϏ⧇āĻŦ⧇ āĻĄāĻžāωāύāϞ⧋āĻĄ āĻ•āϰ⧁āύ",
"external_libraries_page_description": "āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ external āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻĒ⧇āϜ",
"face_detection": "āĻŽā§āĻ– āϏāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ",
"face_detection_description": "āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡ āĻĨāĻžāĻ•āĻž āĻŽā§āĻ–/āĻšā§‡āĻšāĻžāϰāĻž āϗ⧁āϞāĻŋ āϏāύāĻžāĻ•ā§āϤ āĻ•āϰ⧁āύāĨ¤ āĻ­āĻŋāĻĄāĻŋāĻ“ āϗ⧁āϞāĻŋāϰ āϜāĻ¨ā§āϝ, āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āĻŦāĻŋāĻŦ⧇āϚāύāĻž āĻ•āϰāĻž āĻšāϝāĻŧāĨ¤ \"āϰāĻŋāĻĢā§āϰ⧇āĻļ\" (āĻĒ⧁āύāϰāĻžāϝāĻŧ) āϏāĻŽāĻ¸ā§āϤ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĒā§āϰāĻ•ā§āϰāĻŋāϝāĻŧāĻž āĻ•āϰ⧇āĨ¤ \"āϰāĻŋāϏ⧇āϟ\" āĻ•āϰāĻžāϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āĻ…āϤāĻŋāϰāĻŋāĻ•ā§āϤāĻ­āĻžāĻŦ⧇ āϏāĻŽāĻ¸ā§āϤ āĻŦāĻ°ā§āϤāĻŽāĻžāύ āĻŽā§āϖ⧇āϰ āĻĄā§‡āϟāĻž āϏāĻžāĻĢ āĻ•āϰ⧇āĨ¤ \"āĻ…āύ⧁āĻĒāĻ¸ā§āĻĨāĻŋāϤ\" āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞāĻŋāϕ⧇ āϏāĻžāϰāĻŋāĻŦāĻĻā§āϧ āĻ•āϰ⧇ āϝāĻž āĻāĻ–āύāĻ“ āĻĒā§āϰāĻ•ā§āϰāĻŋāϝāĻŧāĻž āĻ•āϰāĻž āĻšāϝāĻŧāύāĻŋāĨ¤ āϏāύāĻžāĻ•ā§āϤ āĻ•āϰāĻž āĻŽā§āĻ–āϗ⧁āϞāĻŋāϕ⧇ āĻĢ⧇āϏāĻŋāϝāĻŧāĻžāϞ āϰāĻŋāĻ•āĻ—āύāĻŋāĻļāύ⧇āϰ āϜāĻ¨ā§āϝ āϏāĻžāϰāĻŋāĻŦāĻĻā§āϧ āĻ•āϰāĻž āĻšāĻŦ⧇, āĻĢ⧇āϏāĻŋāϝāĻŧāĻžāϞ āĻĄāĻŋāĻŸā§‡āĻ•āĻļāύ āϏāĻŽā§āĻĒā§‚āĻ°ā§āĻŖ āĻšāĻ“āϝāĻŧāĻžāϰ āĻĒāϰ⧇, āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ āĻŦāĻž āύāϤ⧁āύ āĻŦā§āϝāĻ•ā§āϤāĻŋāĻĻ⧇āϰ āĻŽāĻ§ā§āϝ⧇ āĻ—ā§‹āĻˇā§āĻ ā§€āĻŦāĻĻā§āϧ āĻ•āϰ⧇āĨ¤",
"facial_recognition_job_description": "āĻļāύāĻžāĻ•ā§āϤ āĻ•āϰāĻž āĻŽā§āĻ–āϗ⧁āϞāĻŋāϕ⧇ āĻŽāĻžāύ⧁āώ⧇āϰ āĻŽāĻ§ā§āϝ⧇ āĻ—ā§‹āĻˇā§āĻ ā§€āϭ⧁āĻ•ā§āϤ/āĻ—ā§āϰ⧁āĻĒ āĻ•āϰ⧁āύāĨ¤ āĻŽā§āĻ– āϏāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ āϏāĻŽā§āĻĒā§‚āĻ°ā§āĻŖ āĻšāĻ“āϝāĻŧāĻžāϰ āĻĒāϰ⧇ āĻāχ āϧāĻžāĻĒāϟāĻŋ āϚāϞ⧇āĨ¤ \"āϰāĻŋāϏ⧇āϟ\" (āĻĒ⧁āύāϰāĻžāϝāĻŧ) āϏāĻŽāĻ¸ā§āϤ āĻŽā§āĻ–āϕ⧇ āĻ•ā§āϞāĻžāĻ¸ā§āϟāĻžāϰ āĻ•āϰ⧇āĨ¤ \"āĻ…āύ⧁āĻĒāĻ¸ā§āĻĨāĻŋāϤ/āĻŽāĻŋāϏāĻŋāĻ‚\" āĻŽā§āĻ–āϗ⧁āϞāĻŋāϕ⧇ āϏāĻžāϰāĻŋāϤ⧇ āϰāĻžāϖ⧇ āϝ⧇āϗ⧁āϞ⧋ āϕ⧋āύāĻ“ āĻŦā§āϝāĻ•ā§āϤāĻŋāϕ⧇ āĻāϏāĻžāχāύ/āĻŦāϰāĻžāĻĻā§āĻĻ āĻ•āϰāĻž āĻšāϝāĻŧāύāĻŋāĨ¤",
@@ -99,6 +104,8 @@
"image_preview_description": "āĻ¸ā§āĻŸā§āϰāĻŋāĻĒāĻĄ āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āϏāĻš āĻŽāĻžāĻāĻžāϰāĻŋ āφāĻ•āĻžāϰ⧇āϰ āĻ›āĻŦāĻŋ, āĻāĻ•āϟāĻŋ āĻāĻ•āĻ• āϏāĻŽā§āĻĒāĻĻ āĻĻ⧇āĻ–āĻžāϰ āϏāĻŽāϝāĻŧ āĻāĻŦāĻ‚ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚āϝāĻŧ⧇āϰ āϜāĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšā§ƒāϤ āĻšāϝāĻŧ",
"image_preview_quality_description": "ā§§-ā§§ā§Ļā§Ļ āĻāϰ āĻŽāĻ§ā§āϝ⧇ āĻĒā§āϰāĻŋāĻ­āĻŋāω āϕ⧋āϝāĻŧāĻžāϞāĻŋāϟāĻŋāĨ¤ āĻŦ⧇āĻļāĻŋ āĻšāϞ⧇ āĻ­āĻžāϞ⧋, āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻŦāĻĄāĻŧ āĻĢāĻžāχāϞ āϤ⧈āϰāĻŋ āĻšāϝāĻŧ āĻāĻŦāĻ‚ āĻ…ā§āϝāĻžāĻĒ⧇āϰ āĻĒā§āϰāϤāĻŋāĻ•ā§āϰāĻŋāϝāĻŧāĻžāĻļā§€āϞāϤāĻž āĻ•āĻŽāĻžāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤ āĻ•āĻŽ āĻŽāĻžāύ āϏ⧇āϟ āĻ•āϰāϞ⧇ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āϕ⧋āϝāĻŧāĻžāϞāĻŋāϟāĻŋāϰ āωāĻĒāϰ āĻĒā§āϰāĻ­āĻžāĻŦ āĻĒāĻĄāĻŧāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤",
"image_preview_title": "āĻĒā§āϰāĻŋāĻ­āĻŋāω āϏ⧇āϟāĻŋāĻ‚āϏ",
"image_progressive": "āĻĒā§āϰāĻ—ā§āϰ⧇āϏāĻŋāĻ­",
"image_progressive_description": "āϧ⧀āϰ⧇ āϧ⧀āϰ⧇ āϞ⧋āĻĄ āĻšāĻ“ā§ŸāĻžāϰ āϏ⧁āĻŦāĻŋāϧāĻžāĻ°ā§āĻĨ⧇ JPEG āĻ›āĻŦāĻŋāϗ⧁āϞ⧋ āĻĒā§āϰāĻ—ā§āϰ⧇āϏāĻŋāĻ­āĻ­āĻžāĻŦ⧇ āĻāύāϕ⧋āĻĄ āĻ•āϰ⧁āύāĨ¤ WebP āĻ›āĻŦāĻŋāϰ āĻ•ā§āώ⧇āĻ¤ā§āϰ⧇ āĻāϟāĻŋ āϕ⧋āύ⧋ āĻĒā§āϰāĻ­āĻžāĻŦ āĻĢ⧇āϞāĻŦ⧇ āύāĻž",
"image_quality": "āϗ⧁āĻŖāĻŽāĻžāύ",
"image_resolution": "āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ",
"image_resolution_description": "āωāĻšā§āϚ āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ⧇āϰ āĻ•ā§āώ⧇āĻ¤ā§āϰ⧇ āφāϰāĻ“ āĻŦāĻŋāĻ¸ā§āϤāĻžāϰāĻŋāϤ āϤāĻĨā§āϝ āϏāĻ‚āϰāĻ•ā§āώāĻŖ āĻ•āϰāĻž āϏāĻŽā§āĻ­āĻŦ āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻāύāϕ⧋āĻĄ āĻ•āϰāϤ⧇ āĻŦ⧇āĻļāĻŋ āϏāĻŽāϝāĻŧ āϞāĻžāϗ⧇, āĻĢāĻžāχāϞ⧇āϰ āφāĻ•āĻžāϰ āĻŦāĻĄāĻŧ āĻšāϝāĻŧ āĻāĻŦāĻ‚ āĻ…ā§āϝāĻžāĻĒ⧇āϰ āĻĒā§āϰāϤāĻŋāĻ•ā§āϰāĻŋāϝāĻŧāĻžāĻļā§€āϞāϤāĻž āĻ•āĻŽāĻžāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤",
@@ -107,6 +114,7 @@
"image_thumbnail_description": "āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āĻŦāĻžāĻĻ āĻĻ⧇āĻ“ā§ŸāĻž āϛ⧋āϟ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ, āĻŽā§‚āϞ āϟāĻžāχāĻŽāϞāĻžāχāύ⧇āϰ āĻŽāϤ⧋ āĻ›āĻŦāĻŋāϰ āĻ—ā§āϰ⧁āĻĒ āĻĻ⧇āĻ–āĻžāϰ āϏāĻŽāϝāĻŧ āĻŦā§āϝāĻŦāĻšā§ƒāϤ āĻšā§Ÿ",
"image_thumbnail_quality_description": "āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ⧇āϰ āĻŽāĻžāύ ā§§-ā§§ā§Ļā§ĻāĨ¤ āĻŦ⧇āĻļāĻŋ āĻšāϞ⧇ āĻ­āĻžāϞ⧋, āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻŦāĻĄāĻŧ āĻĢāĻžāχāϞ āϤ⧈āϰāĻŋ āĻšāϝāĻŧ āĻāĻŦāĻ‚ āĻ…ā§āϝāĻžāĻĒ⧇āϰ āĻĒā§āϰāϤāĻŋāĻ•ā§āϰāĻŋāϝāĻŧāĻžāĻļā§€āϞāϤāĻž āĻ•āĻŽāĻžāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤",
"image_thumbnail_title": "āĻĨāĻžāĻŽā§āĻŦāύ⧇āϞ āϏ⧇āϟāĻŋāĻ‚āϏ",
"import_config_from_json_description": "āĻāĻ•āϟāĻŋ JSON āĻ•āύāĻĢāĻŋāĻ— āĻĢāĻžāχāϞ āφāĻĒāϞ⧋āĻĄ āĻ•āϰ⧇ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻ•āύāĻĢāĻŋāĻ—āĻžāϰ⧇āĻļāύ āχāĻŽāĻĒā§‹āĻ°ā§āϟ āĻ•āϰ⧁āύāĨ¤",
"job_concurrency": "{job} āĻ•āύāĻ•āĻžāϰ⧇āĻ¨ā§āϏāĻŋ",
"job_created": "Job āϤ⧈āϰāĻŋ āĻšāϝāĻŧ⧇āϛ⧇",
"job_not_concurrency_safe": "āĻāχ āĻ•āĻžāϜāϟāĻŋ āϏāĻŽāĻžāĻ¨ā§āϤāϰāĻžāϞāĻ­āĻžāĻŦ⧇ āϚāĻžāϞāĻžāύ⧋ āύāĻŋāϰāĻžāĻĒāĻĻ āύ⧟",
@@ -114,14 +122,20 @@
"job_settings_description": "āĻ•āĻžāĻœā§‡āϰ āϏāĻŽāĻžāĻ¨ā§āϤāϰāĻžāϞāϤāĻž āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ",
"jobs_delayed": "{jobCount, plural, other {# āĻŦāĻŋāϞāĻŽā§āĻŦāĻŋāϤ}}",
"jobs_failed": "{jobCount, plural, other {# āĻŦā§āϝāĻ°ā§āĻĨ}}",
"jobs_over_time": "āϏāĻŽā§Ÿ āĻ…āύ⧁āϝāĻžā§Ÿā§€ āĻ•āĻžāϜāϏāĻŽā§‚āĻš",
"library_created": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇āσ {library}",
"library_deleted": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāϝāĻŧ⧇āϛ⧇",
"library_details": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋāϰ āĻŦāĻŋāĻŦāϰāĻŖ",
"library_folder_description": "āχāĻŽāĻĒā§‹āĻ°ā§āϟ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āĻāĻ•āϟāĻŋ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻ•āϰ⧁āύāĨ¤ āĻāχ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻāĻŦāĻ‚ āĻāϰ āϭ⧇āϤāϰ⧇āϰ āϏāĻŽāĻ¸ā§āϤ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“āϰ āϜāĻ¨ā§āϝ āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤",
"library_remove_exclusion_pattern_prompt": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āĻāχ āĻāĻ•ā§āϏāĻ•ā§āϞ⧁āĻļāύ āĻĒā§āϝāĻžāϟāĻžāĻ°ā§āύāϟāĻŋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ?",
"library_remove_folder_prompt": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āĻāχ āχāĻŽāĻĒā§‹āĻ°ā§āϟ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰāϟāĻŋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ?",
"library_scanning": "āĻĒāĻ°ā§āϝāĻžāϝāĻŧāĻ•ā§āϰāĻŽāĻŋāĻ• āĻ¸ā§āĻ•ā§āϝāĻžāύāĻŋāĻ‚",
"library_scanning_description": "āĻĒāĻ°ā§āϝāĻžāϝāĻŧāĻ•ā§āϰāĻŽāĻŋāĻ• āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻ¸ā§āĻ•ā§āϝāĻžāύāĻŋāĻ‚ āĻ•āύāĻĢāĻŋāĻ—āĻžāϰ āĻ•āϰ⧁āύ",
"library_scanning_enable_description": "āĻĒāĻ°ā§āϝāĻžāϝāĻŧāĻ•ā§āϰāĻŽāĻŋāĻ• āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻ¸ā§āĻ•ā§āϝāĻžāύāĻŋāĻ‚ āϏāĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ",
"library_settings": "āĻŦāĻšāĻŋāϰāĻžāĻ—āϤ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ",
"library_settings_description": "āĻŦāĻšāĻŋāϰāĻžāĻ—āϤ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ",
"library_tasks_description": "āύāϤ⧁āύ āĻāĻŦāĻ‚/āĻ…āĻĨāĻŦāĻž āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāĻŋāϤ āϏāĻŽā§āĻĒāĻĻ⧇āϰ āϜāĻ¨ā§āϝ āĻŦāĻšāĻŋāϰāĻžāĻ—āϤ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰ⧁āύ",
"library_updated": "āφāĻĒāĻĄā§‡āϟāĻ•ā§ƒāϤ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋāĨ¤",
"library_watching_enable_description": "āĻĢāĻžāχāϞ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ⧇āϰ āϜāĻ¨ā§āϝ āĻŦāĻšāĻŋāϰāĻžāĻ—āϤ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋāϗ⧁āϞāĻŋ āĻĻ⧇āϖ⧁āύ",
"library_watching_settings": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻĻ⧇āĻ–āĻž (āĻĒāϰ⧀āĻ•ā§āώāĻžāĻŽā§‚āϞāĻ•)",
"library_watching_settings_description": "āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāĻŋāϤ āĻĢāĻžāχāϞāϗ⧁āϞāĻŋāϰ āϜāĻ¨ā§āϝ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āύāϜāϰ āϰāĻžāϖ⧁āύ",
@@ -133,9 +147,199 @@
"machine_learning_availability_checks_enabled": "āĻĒā§āϰāĻžāĻĒā§āϝāϤāĻž āĻĒāϰ⧀āĻ•ā§āώāĻž āϏāĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ",
"machine_learning_availability_checks_interval": "āĻšā§‡āĻ• āĻŦā§āϝāĻŦāϧāĻžāύ",
"machine_learning_availability_checks_interval_description": "āĻĒā§āϰāĻžāĻĒā§āϝāϤāĻž āĻĒāϰ⧀āĻ•ā§āώāĻžāϗ⧁āϞāĻŋāϰ āĻŽāĻ§ā§āϝ⧇ āĻŦā§āϝāĻŦāϧāĻžāύ āĻŽāĻŋāϞāĻŋāϏ⧇āϕ⧇āĻ¨ā§āĻĄā§‡",
"machine_learning_availability_checks_timeout": "āĻ…āύ⧁āϰ⧋āϧ⧇āϰ āϏāĻŽā§ŸāϏ⧀āĻŽāĻž āĻļ⧇āώ",
"machine_learning_availability_checks_timeout_description": "āĻĒā§āϰāĻžāĻĒā§āϝāϤāĻžāϰ āĻĒāϰ⧀āĻ•ā§āώāĻžāϰ āϜāĻ¨ā§āϝ āĻŽāĻŋāϞāĻŋāϏ⧇āϕ⧇āĻ¨ā§āĻĄā§‡ āϏāĻŽā§ŸāϏ⧀āĻŽāĻžāĨ¤",
"machine_learning_clip_model": "CLIP āĻŽāĻĄā§‡āϞ",
"machine_learning_clip_model_description": "<link>āĻāĻ–āĻžāύ⧇</link> āϤāĻžāϞāĻŋāĻ•āĻžāϭ⧁āĻ•ā§āϤ āĻāĻ•āϟāĻŋ CLIP āĻŽāĻĄā§‡āϞ⧇āϰ āύāĻžāĻŽāĨ¤ āĻŽāύ⧇ āϰāĻžāĻ–āĻŦ⧇āύ, āĻŽāĻĄā§‡āϞ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ⧇āϰ āĻĒāϰ āϏāĻŦ āĻ›āĻŦāĻŋāϰ āϜāĻ¨ā§āϝ āĻ…āĻŦāĻļā§āϝāχ ‘Smart Search’ āĻ•āĻžāϜāϟāĻŋ āφāĻŦāĻžāϰ āϚāĻžāϞāĻžāϤ⧇ āĻšāĻŦ⧇āĨ¤",
"machine_learning_duplicate_detection": "āĻĒ⧁āύāϰāĻžāĻŦ⧃āĻ¤ā§āϤāĻŋ āϏāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ",
"machine_learning_duplicate_detection_enabled": "āĻĒ⧁āύāϰāĻžāĻŦ⧃āĻ¤ā§āϤāĻŋ āĻļāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ"
}
"machine_learning_duplicate_detection_enabled": "āĻĒ⧁āύāϰāĻžāĻŦ⧃āĻ¤ā§āϤāĻŋ āĻļāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ",
"machine_learning_duplicate_detection_enabled_description": "āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋ⧟ āĻĨāĻžāĻ•āϞ⧇āĻ“ āĻšā§āĻŦāĻšā§ āĻāĻ•āχ āϏāĻŽā§āĻĒāĻĻāϗ⧁āϞ⧋āϰ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āϏāϰāĻŋā§Ÿā§‡ āĻĢ⧇āϞāĻž āĻšāĻŦ⧇āĨ¤",
"machine_learning_duplicate_detection_setting_description": "āϏāĻŽā§āĻ­āĻžāĻŦā§āϝ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āϖ⧁āρāĻœā§‡ āĻŦ⧇āϰ āĻ•āϰāϤ⧇ CLIP āĻāĻŽā§āĻŦ⧇āĻĄāĻŋāĻ‚ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύāĨ¤",
"machine_learning_enabled": "Machine Learning āϏāĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ",
"machine_learning_enabled_description": "āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻĨāĻžāĻ•āϞ⧇ āύāĻŋāĻšā§‡āϰ āϏ⧇āϟāĻŋāĻ‚āϏ āύāĻŋāĻ°ā§āĻŦāĻŋāĻļ⧇āώ⧇ āϏāĻŽāĻ¸ā§āϤ ML āĻŦ⧈āĻļāĻŋāĻˇā§āĻŸā§āϝ āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤",
"machine_learning_facial_recognition": "āĻĢ⧇āϏāĻŋāϝāĻŧāĻžāϞ āϰāĻŋāĻ•āĻ—āύāĻŋāĻļāύ",
"machine_learning_facial_recognition_description": "āĻ›āĻŦāĻŋāϤ⧇ āĻŽā§āĻ– āϏāύāĻžāĻ•ā§āϤ āĻ•āϰ⧁āύ, āϚāĻŋāύ⧁āύ āĻāĻŦāĻ‚ āĻ—ā§āϰ⧁āĻĒ āĻ•āϰ⧁āύāĨ¤",
"machine_learning_facial_recognition_model": "āĻĢ⧇āϏāĻŋāϝāĻŧāĻžāϞ āϰāĻŋāĻ•āĻ—āύāĻŋāĻļāύ āĻŽāĻĄā§‡āϞ",
"machine_learning_facial_recognition_model_description": "āĻŽāĻĄā§‡āϞāϗ⧁āϞāĻŋ āφāĻ•āĻžāϰ⧇āϰ āĻ…āϧāσāĻ•ā§āϰāĻŽ āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āϤāĻžāϞāĻŋāĻ•āĻžāϭ⧁āĻ•ā§āϤ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇āĨ¤ āĻŦ⧜ āĻŽāĻĄā§‡āϞāϗ⧁āϞāĻŋ āϧ⧀āϰāĻ—āϤāĻŋāϰ āĻāĻŦāĻ‚ āĻŦ⧇āĻļāĻŋ āĻŽā§‡āĻŽāϰāĻŋ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇, āϤāĻŦ⧇ āωāĻ¨ā§āύāϤ āĻĢāϞāĻžāĻĢāϞ āĻĒā§āϰāĻĻāĻžāύ āĻ•āϰ⧇āĨ¤ āĻŽāύ⧇ āϰāĻžāĻ–āĻŦ⧇āύ āϝ⧇ āĻāĻ•āϟāĻŋ āĻŽāĻĄā§‡āϞ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻžāϰ āĻĒāϰ āφāĻĒāύāĻžāϕ⧇ āϏāĻŽāĻ¸ā§āϤ āĻ›āĻŦāĻŋāϰ āϜāĻ¨ā§āϝ āĻĢ⧇āϏ āĻĄāĻŋāĻŸā§‡āĻ•āĻļāύ (Face Detection) āĻ•āĻžāϜāϟāĻŋ āĻĒ⧁āύāϰāĻžāϝāĻŧ āϚāĻžāϞāĻžāϤ⧇ āĻšāĻŦ⧇āĨ¤",
"machine_learning_facial_recognition_setting": "āĻĢ⧇āϏāĻŋāϝāĻŧāĻžāϞ āϰāĻŋāĻ•āĻ—āύāĻŋāĻļāύ āϏāĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ",
"machine_learning_facial_recognition_setting_description": "āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋ⧟ āĻĨāĻžāĻ•āϞ⧇, āĻĢ⧇āϏāĻŋāϝāĻŧāĻžāϞ āϰāĻŋāĻ•āĻ—āύāĻŋāĻļāύ⧇āϰ āϜāĻ¨ā§āϝ āĻ›āĻŦāĻŋāϗ⧁āϞ⧋ āĻāύāϕ⧋āĻĄ āĻ•āϰāĻž āĻšāĻŦ⧇ āύāĻž āĻāĻŦāĻ‚ āĻāĻ•ā§āϏāĻĒā§āϞ⧋āϰ āĻĒ⧇āĻœā§‡āϰ āĻĒāĻŋāĻĒāϞ (People) āϏ⧇āĻ•āĻļāύāϟāĻŋ āĻĒā§‚āĻ°ā§āĻŖ āĻšāĻŦ⧇ āύāĻžāĨ¤",
"machine_learning_max_detection_distance": "āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āĻļāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ āĻĻā§‚āϰāĻ¤ā§āĻŦ",
"machine_learning_max_detection_distance_description": "āĻĻ⧁āϟāĻŋ āĻ›āĻŦāĻŋāϕ⧇ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āĻšāĻŋāϏ⧇āĻŦ⧇ āĻ—āĻŖā§āϝ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āϤāĻžāĻĻ⧇āϰ āĻŽāĻ§ā§āϝāĻ•āĻžāϰ āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āĻĻā§‚āϰāĻ¤ā§āĻŦ, āϝāĻžāϰ āĻĒāϰāĻŋāϏ⧀āĻŽāĻž ā§Ļ.ā§Ļā§Ļā§§-ā§Ļ.ā§§āĨ¤ āĻŽāĻžāύ āϝāϤ āĻŦ⧇āĻļāĻŋ āĻšāĻŦ⧇ āϤāϤ āĻŦ⧇āĻļāĻŋ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āĻļāύāĻžāĻ•ā§āϤ āĻšāĻŦ⧇, āϤāĻŦ⧇ āĻāϤ⧇ āϭ⧁āϞ āĻļāύāĻžāĻ•ā§āϤāĻ•āϰāϪ⧇āϰ (false positives) āϏāĻŽā§āĻ­āĻžāĻŦāύāĻž āĻĨāĻžāĻ•āϤ⧇ āĻĒāĻžāϰ⧇āĨ¤",
"machine_learning_max_recognition_distance": "āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āϚāĻŋāĻšā§āύāĻŋāϤāĻ•āϰāĻŖ āĻĻā§‚āϰāĻ¤ā§āĻŦ",
"machine_learning_max_recognition_distance_description": "āĻĻ⧁āϟāĻŋ āĻŽā§āĻ–āϕ⧇ āĻāĻ•āχ āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻšāĻŋāϏ⧇āĻŦ⧇ āĻ—āĻŖā§āϝ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āϤāĻžāĻĻ⧇āϰ āĻŽāĻ§ā§āϝāĻ•āĻžāϰ āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āĻĻā§‚āϰāĻ¤ā§āĻŦ, āϝāĻžāϰ āĻĒāϰāĻŋāϏ⧀āĻŽāĻž ā§Ļ-⧍āĨ¤ āĻāχ āĻŽāĻžāύ āĻ•āĻŽāĻžāϞ⧇ āĻĻā§â€™āϜāύ āĻ­āĻŋāĻ¨ā§āύ āĻŦā§āϝāĻ•ā§āϤāĻŋāϕ⧇ āĻāĻ•āχ āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻšāĻŋāϏ⧇āĻŦ⧇ āϚāĻŋāĻšā§āύāĻŋāϤ āĻ•āϰāĻžāϰ āϏāĻŽā§āĻ­āĻžāĻŦāύāĻž āĻ•āĻŽā§‡, āφāϰ āĻŽāĻžāύ āĻŦāĻžā§œāĻžāϞ⧇ āĻāĻ•āχ āĻŦā§āϝāĻ•ā§āϤāĻŋāϕ⧇ āĻĻā§â€™āϜāύ āĻ­āĻŋāĻ¨ā§āύ āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻšāĻŋāϏ⧇āĻŦ⧇ āϚāĻŋāĻšā§āύāĻŋāϤ āĻ•āϰāĻžāϰ āϏāĻŽā§āĻ­āĻžāĻŦāύāĻž āĻ•āĻŽā§‡āĨ¤ āĻŽāύ⧇ āϰāĻžāĻ–āĻŦ⧇āύ āϝ⧇, āĻĻā§â€™āϜāύ āĻŦā§āϝāĻ•ā§āϤāĻŋāϕ⧇ āĻāĻ•āĻ¤ā§āϰāĻŋāϤ āĻ•āϰāĻž (merge) āĻ…āĻĒ⧇āĻ•ā§āώāĻžāĻ•ā§ƒāϤ āϏāĻšāϜ āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻāĻ•āϜāύāϕ⧇ āĻĻā§â€™āĻ­āĻžāϗ⧇ āĻ­āĻžāĻ— āĻ•āϰāĻž āĻ•āĻ āĻŋāύ, āϤāĻžāχ āϏāĻŽā§āĻ­āĻŦ āĻšāϞ⧇ āĻĨā§āϰ⧇āĻļāĻšā§‹āĻ˛ā§āĻĄ (threshold) āĻ•āĻŽ āϰāĻžāĻ–āĻžāχ āĻ­āĻžāϞ⧋āĨ¤",
"machine_learning_min_detection_score": "āϏāĻ°ā§āĻŦāύāĻŋāĻŽā§āύ āĻļāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ āĻ¸ā§āϕ⧋āϰ",
"machine_learning_min_detection_score_description": "āĻ›āĻŦāĻŋāϤ⧇ āĻŽā§āĻ– āĻļāύāĻžāĻ•ā§āϤ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ ā§Ļ-ā§§ āĻāϰ āĻŽāĻ§ā§āϝ⧇ āϏāĻ°ā§āĻŦāύāĻŋāĻŽā§āύ āĻ•āύāĻĢāĻŋāĻĄā§‡āĻ¨ā§āϏ āĻ¸ā§āϕ⧋āϰāĨ¤ āĻŽāĻžāύ āϝāϤ āĻ•āĻŽ āĻšāĻŦ⧇ āϤāϤ āĻŦ⧇āĻļāĻŋ āĻŽā§āĻ– āĻļāύāĻžāĻ•ā§āϤ āĻšāĻŦ⧇, āϤāĻŦ⧇ āĻāϤ⧇ āϭ⧁āϞ āĻļāύāĻžāĻ•ā§āϤāĻ•āϰāϪ⧇āϰ (false positives) āϏāĻŽā§āĻ­āĻžāĻŦāύāĻž āĻĨāĻžāĻ•āϤ⧇ āĻĒāĻžāϰ⧇āĨ¤",
"machine_learning_min_recognized_faces": "āϏāĻ°ā§āĻŦāύāĻŋāĻŽā§āύ āĻ¸ā§āĻŦā§€āĻ•ā§ƒāϤ āĻŽā§āϖ⧇āϰ āϏāĻ‚āĻ–ā§āϝāĻž",
"machine_learning_min_recognized_faces_description": "āĻāĻ•āϜāύ āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻšāĻŋāϏ⧇āĻŦ⧇ āϤ⧈āϰāĻŋ āĻšāĻ“āϝāĻŧāĻžāϰ āϜāĻ¨ā§āϝ āĻ¸ā§āĻŦā§€āĻ•ā§ƒāϤ āĻŽā§āϖ⧇āϰ āϏāĻ°ā§āĻŦāύāĻŋāĻŽā§āύ āϏāĻ‚āĻ–ā§āϝāĻžāĨ¤ āĻāϟāĻŋ āĻŦāĻžāĻĄāĻŧāĻžāϞ⧇ āĻĢ⧇āϏāĻŋāϝāĻŧāĻžāϞ āϰāĻŋāĻ•āĻ—āύāĻŋāĻļāύ āφāϰāĻ“ āύāĻŋāϖ⧁āρāϤ āĻšāϝāĻŧ, āϤāĻŦ⧇ āĻāϤ⧇ āϕ⧋āύ⧋ āĻŽā§āĻ– āϕ⧋āύ⧋ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ āϏāĻžāĻĨ⧇ āϏāĻ‚āϝ⧁āĻ•ā§āϤ āύāĻž āĻšāĻ“āϝāĻŧāĻžāϰ āϏāĻŽā§āĻ­āĻžāĻŦāύāĻžāĻ“ āĻŦ⧃āĻĻā§āϧāĻŋ āĻĒāĻžāϝāĻŧāĨ¤",
"machine_learning_ocr": "OCR",
"machine_learning_ocr_description": "āĻ›āĻŦāĻŋāϤ⧇ āĻŸā§‡āĻ•ā§āϏāϟ (Text) āĻļāύāĻžāĻ•ā§āϤ āĻ•āϰāϤ⧇ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύāĨ¤",
"machine_learning_ocr_enabled": "OCR āϏāĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ",
"machine_learning_ocr_enabled_description": "āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻĨāĻžāĻ•āϞ⧇, āĻ›āĻŦāĻŋāϗ⧁āϞ⧋āϤ⧇ āĻŸā§‡āĻ•ā§āϏāϟ āĻļāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ āĻ•āϰāĻž āĻšāĻŦ⧇ āύāĻžāĨ¤",
"machine_learning_ocr_max_resolution": "āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ(Resolution)",
"machine_learning_ocr_max_resolution_description": "āĻāχ āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ⧇āϰ āωāĻĒāϰ⧇āϰ āĻĒā§āϰāĻŋāĻ­āĻŋāωāϗ⧁āϞ⧋āϰ āĻ…ā§āϝāĻžāϏāĻĒ⧇āĻ•ā§āϟ āϰ⧇āĻļāĻŋāĻ“ (āφāĻ•āĻžāϰ āĻ“ āĻ…āύ⧁āĻĒāĻžāϤ) āĻ āĻŋāĻ• āϰ⧇āϖ⧇ āϰāĻŋāϏāĻžāχāϜ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤ āĻŽāĻžāύ āϝāϤ āĻŦ⧇āĻļāĻŋ āĻšāĻŦ⧇ āĻĢāϞāĻžāĻĢāϞ āϤāϤ āĻŦ⧇āĻļāĻŋ āύāĻŋāϖ⧁āρāϤ āĻšāĻŦ⧇, āϤāĻŦ⧇ āĻāϟāĻŋ āĻĒā§āϰāϏ⧇āϏ āĻ•āϰāϤ⧇ āϏāĻŽā§Ÿ āĻŦ⧇āĻļāĻŋ āϞāĻžāĻ—āĻŦ⧇ āĻāĻŦāĻ‚ āĻŽā§‡āĻŽāϰāĻŋ āĻŦ⧇āĻļāĻŋ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāĻŦ⧇āĨ¤",
"machine_learning_ocr_min_detection_score": "āϏāĻ°ā§āĻŦāύāĻŋāĻŽā§āύ āĻļāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ āĻ¸ā§āϕ⧋āϰ",
"machine_learning_ocr_min_detection_score_description": "āĻŸā§‡āĻ•ā§āϏāϟ āĻļāύāĻžāĻ•ā§āϤ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ ā§Ļ-ā§§ āĻāϰ āĻŽāĻ§ā§āϝ⧇ āĻ¨ā§āϝ⧂āύāϤāĻŽ āĻ•āύāĻĢāĻŋāĻĄā§‡āĻ¨ā§āϏ āĻ¸ā§āϕ⧋āϰāĨ¤ āĻŽāĻžāύ āϝāϤ āĻ•āĻŽ āĻšāĻŦ⧇ āϤāϤ āĻŦ⧇āĻļāĻŋ āĻŸā§‡āĻ•ā§āϏāϟ āĻļāύāĻžāĻ•ā§āϤ āĻšāĻŦ⧇, āϤāĻŦ⧇ āĻāϤ⧇ āϭ⧁āϞ āĻļāύāĻžāĻ•ā§āϤāĻ•āϰāϪ⧇āϰ (false positives) āϏāĻŽā§āĻ­āĻžāĻŦāύāĻž āĻĨāĻžāĻ•āϤ⧇ āĻĒāĻžāϰ⧇āĨ¤",
"machine_learning_ocr_min_recognition_score": "āϏāĻ°ā§āĻŦāύāĻŋāĻŽā§āύ āϚāĻŋāĻšā§āύāĻŋāϤāĻ•āϰāĻŖ (Recognition)āĻ¸ā§āϕ⧋āϰ",
"machine_learning_ocr_min_score_recognition_description": "āĻļāύāĻžāĻ•ā§āϤāĻ•ā§ƒāϤ āĻŸā§‡āĻ•ā§āϏāϟ āϚāĻŋāĻšā§āύāĻŋāϤ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ ā§Ļ-ā§§ āĻāϰ āĻŽāĻ§ā§āϝ⧇ āĻ¨ā§āϝ⧂āύāϤāĻŽ āĻ•āύāĻĢāĻŋāĻĄā§‡āĻ¨ā§āϏ āĻ¸ā§āϕ⧋āϰāĨ¤ āĻŽāĻžāύ āϝāϤ āĻ•āĻŽ āĻšāĻŦ⧇ āϤāϤ āĻŦ⧇āĻļāĻŋ āĻŸā§‡āĻ•ā§āϏāϟ āϚāĻŋāĻšā§āύāĻŋāϤ āĻšāĻŦ⧇, āϤāĻŦ⧇ āĻāϤ⧇ āϭ⧁āϞ āĻļāύāĻžāĻ•ā§āϤāĻ•āϰāϪ⧇āϰ (false positives) āϏāĻŽā§āĻ­āĻžāĻŦāύāĻž āĻĨāĻžāĻ•āϤ⧇ āĻĒāĻžāϰ⧇āĨ¤",
"machine_learning_ocr_model": "OCR āĻŽāĻĄā§‡āϞ",
"machine_learning_ocr_model_description": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻŽāĻĄā§‡āϞāϗ⧁āϞ⧋ āĻŽā§‹āĻŦāĻžāχāϞ āĻŽāĻĄā§‡āϞ⧇āϰ āϤ⧁āϞāύāĻžā§Ÿ āĻŦ⧇āĻļāĻŋ āύāĻŋāĻ°ā§āϭ⧁āϞ, āϤāĻŦ⧇ āĻāϗ⧁āϞ⧋ āĻĒā§āϰāϏ⧇āϏ āĻ•āϰāϤ⧇ āϏāĻŽā§Ÿ āĻŦ⧇āĻļāĻŋ āϞāĻžāϗ⧇ āĻāĻŦāĻ‚ āĻŽā§‡āĻŽāϰāĻŋ āĻŦ⧇āĻļāĻŋ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇āĨ¤",
"machine_learning_settings": "āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āϏ⧇āϟāĻŋāĻ‚āϏ (Machine Learning Settings)",
"machine_learning_settings_description": "āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āĻŦ⧈āĻļāĻŋāĻˇā§āĻŸā§āϝ āĻāĻŦāĻ‚ āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ",
"machine_learning_smart_search": "āĻ¸ā§āĻŽāĻžāĻ°ā§āϟ āϏāĻžāĻ°ā§āϚ (Smart Search)",
"machine_learning_smart_search_description": "CLIP āĻāĻŽāĻŦ⧇āĻĄāĻŋāĻ‚ (embeddings) āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻ›āĻŦāĻŋāϰ āĻŦāĻŋāώ⧟āĻŦāĻ¸ā§āϤ⧁ āĻ…āύ⧁āϝāĻžā§Ÿā§€ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ",
"machine_learning_smart_search_enabled": "āĻ¸ā§āĻŽāĻžāĻ°ā§āϟ āϏāĻžāĻ°ā§āϚ āϏāĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ",
"machine_learning_smart_search_enabled_description": "āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻĨāĻžāĻ•āϞ⧇, āĻ¸ā§āĻŽāĻžāĻ°ā§āϟ āϏāĻžāĻ°ā§āĻšā§‡āϰ āϜāĻ¨ā§āϝ āĻ›āĻŦāĻŋāϗ⧁āϞ⧋ āĻāύāϕ⧋āĻĄ (encode) āĻ•āϰāĻž āĻšāĻŦ⧇ āύāĻžāĨ¤",
"machine_learning_url_description": "āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇āϰ URLāĨ¤ āϝāĻĻāĻŋ āĻāϕ⧇āϰ āĻŦ⧇āĻļāĻŋ URL āĻĒā§āϰāĻĻāĻžāύ āĻ•āϰāĻž āĻšā§Ÿ, āϤāĻŦ⧇ āĻāĻ•āϟāĻŋ āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āϏāĻžā§œāĻž āύāĻž āĻĻ⧇āĻ“ā§ŸāĻž āĻĒāĻ°ā§āϝāĻ¨ā§āϤ āĻĒā§āϰāϤāĻŋāϟāĻŋ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇ āĻāĻ• āĻāĻ• āĻ•āϰ⧇ āĻšā§‡āĻˇā§āϟāĻž āĻ•āϰāĻž āĻšāĻŦ⧇ (āĻĒā§āϰāĻĨāĻŽ āĻĨ⧇āϕ⧇ āĻļ⧇āώ āĻ•ā§āϰāĻŽāĻžāύ⧁āϏāĻžāϰ⧇)āĨ¤ āϝ⧇ āϏāĻžāĻ°ā§āĻ­āĻžāϰāϗ⧁āϞ⧋ āϏāĻžā§œāĻž āĻĻ⧇āĻŦ⧇ āύāĻž, āϏ⧇āϗ⧁āϞ⧋ āĻĒ⧁āύāϰāĻžā§Ÿ āϏāϚāϞ āĻšāĻ“ā§ŸāĻž āĻĒāĻ°ā§āϝāĻ¨ā§āϤ āϏāĻžāĻŽā§ŸāĻŋāĻ•āĻ­āĻžāĻŦ⧇ āωāĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤",
"maintenance_delete_backup": "āĻŦā§āϝāĻžāĻ•āφāĻĒ (Backup)āĻŽā§āϛ⧁āύ",
"maintenance_delete_backup_description": "āĻāχ āĻĢāĻžāχāϞāϟāĻŋ āϚāĻŋāϰāϤāϰ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāĻŦ⧇āĨ¤",
"maintenance_delete_error": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻŽā§āĻ›āϤ⧇ āĻŦā§āϝāĻ°ā§āĻĨ āĻšā§Ÿā§‡āϛ⧇āĨ¤",
"maintenance_restore_backup": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ(Restore) āĻ•āϰ⧁āύ",
"maintenance_restore_backup_description": "Immich āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāĻŦ⧇ āĻāĻŦāĻ‚ āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĨ⧇āϕ⧇ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤ āĻ•āĻžāĻ°ā§āϝāĻ•ā§āϰāĻŽ āϚāĻžāϞāĻŋā§Ÿā§‡ āϝāĻžāĻ“ā§ŸāĻžāϰ āφāϗ⧇ āĻāĻ•āϟāĻŋ āĻŦā§āϝāĻžāĻ•āφāĻĒ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤",
"maintenance_restore_backup_different_version": "āĻāχ āĻŦā§āϝāĻžāĻ•āφāĻĒāϟāĻŋ Immich-āĻāϰ āĻāĻ•āϟāĻŋ āĻ­āĻŋāĻ¨ā§āύ āϏāĻ‚āĻ¸ā§āĻ•āϰāϪ⧇āϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āĻšā§Ÿā§‡āĻ›āĻŋāϞ!",
"maintenance_restore_backup_unknown_version": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āϏāĻ‚āĻ¸ā§āĻ•āϰāĻŖ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰāĻž āϏāĻŽā§āĻ­āĻŦ āĻšāϝāĻŧāύāĻŋāĨ¤",
"maintenance_restore_database_backup": "āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰ⧁āύ",
"maintenance_restore_database_backup_description": "āĻāĻ•āϟāĻŋ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĢāĻžāχāϞ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻĄā§‡āϟāĻžāĻŦ⧇āϏāϕ⧇ āĻĒā§‚āĻ°ā§āĻŦāĻŦāĻ°ā§āϤ⧀ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāϝāĻŧ āĻĢāĻŋāϰāĻŋā§Ÿā§‡ āφāύ⧁āύāĨ¤",
"maintenance_settings": "āϰāĻ•ā§āώāĻŖāĻžāĻŦ⧇āĻ•ā§āώāĻŖ (Maintenance)",
"maintenance_settings_description": "Immich-āϕ⧇ āϰāĻ•ā§āώāĻŖāĻžāĻŦ⧇āĻ•ā§āώāĻŖ āĻŽā§‹āĻĄā§‡ (maintenance mode) āϰāĻžāϖ⧁āύāĨ¤",
"maintenance_start": "āϰāĻ•ā§āώāĻŖāĻžāĻŦ⧇āĻ•ā§āώāĻŖ āĻŽā§‹āĻĄā§‡ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ",
"maintenance_start_error": "āϰāĻ•ā§āώāĻŖāĻžāĻŦ⧇āĻ•ā§āώāĻŖ āĻŽā§‹āĻĄ āϚāĻžāϞ⧁ āĻ•āϰāϤ⧇ āĻŦā§āϝāĻ°ā§āĻĨ āĻšā§Ÿā§‡āϛ⧇āĨ¤",
"maintenance_upload_backup": "āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĢāĻžāχāϞ āφāĻĒāϞ⧋āĻĄ āĻ•āϰ⧁āύ",
"maintenance_upload_backup_error": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āϝāĻžā§ŸāύāĻŋ, āĻāϟāĻŋ āĻ•āĻŋ āϕ⧋āύ⧋ .sql/.sql.gz āĻĢāĻžāχāϞ?",
"manage_concurrency": "āĻ•āύāĻ•āĻžāϰ⧇āĻ¨ā§āϏāĻŋ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ (Manage Concurrency)",
"manage_concurrency_description": "āϜāĻŦ āĻ•āύāĻ•āĻžāϰ⧇āĻ¨ā§āϏāĻŋ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰāϤ⧇ 'āϜāĻŦāϏ' (Jobs) āĻĒāĻžāϤāĻžāϝāĻŧ āϝāĻžāύāĨ¤",
"manage_log_settings": "āϞāĻ— āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ",
"map_dark_style": "āĻĄāĻžāĻ°ā§āĻ• āĻ¸ā§āϟāĻžāχāϞ (Dark style)",
"map_enable_description": "āĻŽā§āϝāĻžāĻĒ āĻĢāĻŋāϚāĻžāϰāϗ⧁āϞ⧋ āϏāĻ•ā§āϰāĻŋ⧟ āĻ•āϰ⧁āύ (Enable map features)",
"map_gps_settings": "āĻŽā§āϝāĻžāĻĒ āĻāĻŦāĻ‚ āϜāĻŋāĻĒāĻŋāĻāϏ āϏ⧇āϟāĻŋāĻ‚āϏ (Map & GPS Settings)",
"map_gps_settings_description": "āĻŽā§āϝāĻžāĻĒ āĻāĻŦāĻ‚ āϜāĻŋāĻĒāĻŋāĻāϏ (āϰāĻŋāĻ­āĻžāĻ°ā§āϏ āϜāĻŋāĻ“āϕ⧋āĻĄāĻŋāĻ‚) āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ (Manage Map & GPS (Reverse Geocoding) Settings)",
"map_implications": "āĻŽā§āϝāĻžāĻĒ āĻĢāĻŋāϚāĻžāϰāϟāĻŋ āĻāĻ•āϟāĻŋ āĻāĻ•ā§āϏāϟāĻžāĻ°ā§āύāĻžāϞ āϟāĻžāχāϞ āϏāĻžāĻ°ā§āĻ­āĻŋāϏ⧇āϰ (tiles.immich.cloud) āĻ“āĻĒāϰ āύāĻŋāĻ°ā§āĻ­āϰ āĻ•āϰ⧇āĨ¤",
"map_light_style": "āϞāĻžāχāϟ āĻ¸ā§āϟāĻžāχāϞ (Light style)",
"map_manage_reverse_geocoding_settings": "<link>āϰāĻŋāĻ­āĻžāĻ°ā§āϏ āϜāĻŋāĻ“āϕ⧋āĻĄāĻŋāĻ‚</link> āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ",
"map_reverse_geocoding": "āϰāĻŋāĻ­āĻžāĻ°ā§āϏ āϜāĻŋāĻ“āϕ⧋āĻĄāĻŋāĻ‚ (Reverse Geocoding)",
"map_reverse_geocoding_enable_description": "āϰāĻŋāĻ­āĻžāĻ°ā§āϏ āϜāĻŋāĻ“āϕ⧋āĻĄāĻŋāĻ‚ āϏāĻ•ā§āϰāĻŋ⧟ āĻ•āϰ⧁āύ (Enable reverse geocoding)",
"map_reverse_geocoding_settings": "āϰāĻŋāĻ­āĻžāĻ°ā§āϏ āϜāĻŋāĻ“āϕ⧋āĻĄāĻŋāĻ‚ āϏ⧇āϟāĻŋāĻ‚āϏ (Reverse Geocoding Settings)",
"map_settings": "āĻŽāĻžāύāϚāĻŋāĻ¤ā§āϰ (Map)",
"map_settings_description": "āĻŽāĻžāύāϚāĻŋāĻ¤ā§āϰ⧇āϰ āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ (Manage map settings)",
"map_style_description": "āĻāĻ•āϟāĻŋ style.json āĻŽā§āϝāĻžāĻĒ āĻĨāĻŋāĻŽā§‡āϰ URL (URL to a style.json map theme)",
"memory_cleanup_job": "āĻŽā§‡āĻŽāϰāĻŋ āĻ•ā§āϞāĻŋāύāφāĻĒ (Memory cleanup)",
"memory_generate_job": "āĻ¸ā§āĻŽā§ƒāϤāĻŋ āϤ⧈āϰāĻŋ āĻ•āϰāĻž(Memory generation)",
"metadata_extraction_job": "āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āĻāĻ•ā§āϏāĻŸā§āĻ°ā§āϝāĻžāĻ•ā§āϟ āĻ•āϰ⧁āύ (Extract metadata)",
"metadata_extraction_job_description": "āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ (Asset) āĻĨ⧇āϕ⧇ āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āϤāĻĨā§āϝ āĻāĻ•ā§āϏāĻŸā§āĻ°ā§āϝāĻžāĻ•ā§āϟ āĻ•āϰ⧁āύ, āϝ⧇āĻŽāύ: āϜāĻŋāĻĒāĻŋāĻāϏ (GPS), āĻšā§‡āĻšāĻžāϰāĻž (faces) āĻāĻŦāĻ‚ āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ (resolution)āĨ¤",
"metadata_faces_import_setting": "āĻĢ⧇āϏ āχāĻŽā§āĻĒā§‹āĻ°ā§āϟ āϏāĻ•ā§āϰāĻŋ⧟ āĻ•āϰ⧁āύ (Enable face import)",
"metadata_faces_import_setting_description": "āĻ›āĻŦāĻŋāϰ EXIF āĻĄā§‡āϟāĻž āĻāĻŦāĻ‚ āϏāĻžāχāĻĄāĻ•āĻžāϰ (sidecar) āĻĢāĻžāχāϞ āĻĨ⧇āϕ⧇ āĻšā§‡āĻšāĻžāϰāĻž (faces) āχāĻŽā§āĻĒā§‹āĻ°ā§āϟ āĻ•āϰ⧁āύāĨ¤",
"metadata_settings": "āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āϏ⧇āϟāĻŋāĻ‚āϏ (Metadata Settings)",
"metadata_settings_description": "āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ (Manage metadata settings)",
"migration_job": "āĻŽāĻžāχāĻ—ā§āϰ⧇āĻļāύ (Migration)",
"migration_job_description": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻāĻŦāĻ‚ āĻĢ⧇āϏ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞāϗ⧁āϞ⧋āϕ⧇ āϏāĻ°ā§āĻŦāĻļ⧇āώ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻ¸ā§āĻŸā§āϰāĻžāĻ•āϚāĻžāϰ⧇ āĻŽāĻžāχāĻ—ā§āϰ⧇āϟ āĻ•āϰ⧁āύāĨ¤ (Migrate thumbnails for assets and faces to the latest folder structure)",
"nightly_tasks_database_cleanup_setting": "āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āĻ•ā§āϞāĻŋāύāφāĻĒ āϟāĻžāĻ¸ā§āĻ•āϏāĻŽā§‚āĻš (Database cleanup tasks)",
"nightly_tasks_database_cleanup_setting_description": "āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āĻĨ⧇āϕ⧇ āĻĒ⧁āϰ⧋āύ⧋ āĻāĻŦāĻ‚ āĻŽā§‡ā§ŸāĻžāĻĻā§‹āĻ¤ā§āϤ⧀āĻ°ā§āĻŖ āĻĄā§‡āϟāĻž āĻŽā§āϛ⧇ āĻĢ⧇āϞ⧁āύ",
"nightly_tasks_generate_memories_setting": "āĻŽā§‡āĻŽā§‹āϰāĻŋāϜ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ (Generate memories)",
"nightly_tasks_generate_memories_setting_description": "āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋ āĻĨ⧇āϕ⧇ āύāϤ⧁āύ āĻŽā§‡āĻŽā§‹āϰāĻŋāϜ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ",
"nightly_tasks_missing_thumbnails_setting": "āĻšāĻžāϰāĻŋāϝāĻŧ⧇ āϝāĻžāĻ“āϝāĻŧāĻž āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞāϗ⧁āϞ⧋ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ",
"nightly_tasks_missing_thumbnails_setting_description": "āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āύ⧇āχ āĻāĻŽāύ āĻĢāĻžāχāϞāϗ⧁āϞ⧋āϕ⧇ āĻ•āĻŋāωāϤ⧇ (Queue) āϝ⧋āĻ— āĻ•āϰ⧁āύ",
"nightly_tasks_settings": "āύāĻžāχāϟāϞāĻŋ āϟāĻžāĻ¸ā§āĻ• āϏ⧇āϟāĻŋāĻ‚āϏ (Nightly Tasks Settings)",
"nightly_tasks_settings_description": "āύāĻžāχāϟāϞāĻŋ āϟāĻžāĻ¸ā§āĻ• āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ (Manage nightly tasks)",
"nightly_tasks_start_time_setting": "āĻļ⧁āϰ⧁ āĻ•āϰāĻžāϰ āϏāĻŽā§Ÿ (Start time)",
"nightly_tasks_start_time_setting_description": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āϝāĻ–āύ āύāĻžāχāϟāϞāĻŋ āϟāĻžāĻ¸ā§āĻ• (nightly tasks) āϚāĻžāϞāĻžāύ⧋ āĻļ⧁āϰ⧁ āĻ•āϰ⧇ āϏ⧇āχ āϏāĻŽāϝāĻŧ",
"nightly_tasks_sync_quota_usage_setting": "āϕ⧋āϟāĻž āĻŦā§āϝāĻŦāĻšāĻžāϰ⧇āϰ āϤāĻĨā§āϝ āϏāĻŋāĻ™ā§āĻ• āĻ•āϰ⧁āύ (Sync quota usage)",
"nightly_tasks_sync_quota_usage_setting_description": "āĻŦāĻ°ā§āϤāĻŽāĻžāύ āĻŦā§āϝāĻŦāĻšāĻžāϰ⧇āϰ āĻ“āĻĒāϰ āĻ­āĻŋāĻ¤ā§āϤāĻŋ āĻ•āϰ⧇ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āϕ⧋āϟāĻž āφāĻĒāĻĄā§‡āϟ āĻ•āϰ⧁āύāĨ¤",
"no_paths_added": "āϕ⧋āύ⧋ āĻĒāĻžāĻĨ āϝ⧋āĻ— āĻ•āϰāĻž āĻšāϝāĻŧāύāĻŋ (No paths added)",
"no_pattern_added": "āϕ⧋āύ⧋ āĻĒā§āϝāĻžāϟāĻžāĻ°ā§āύ āϝ⧋āĻ— āĻ•āϰāĻž āĻšāϝāĻŧāύāĻŋ (No pattern added)",
"note_apply_storage_label_previous_assets": "āĻĻā§āϰāĻˇā§āϟāĻŦā§āϝ: āĻĒā§‚āĻ°ā§āĻŦ⧇ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋āϤ⧇ āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āϞ⧇āĻŦ⧇āϞ (Storage Label) āĻĒā§āϰāϝāĻŧā§‹āĻ— āĻ•āϰāϤ⧇ āύāĻŋāĻšā§‡āϰ āĻ•āĻŽāĻžāĻ¨ā§āĻĄāϟāĻŋ āϰāĻžāύ āĻ•āϰ⧁āĻ¨â€”",
"note_cannot_be_changed_later": "āϏāϤāĻ°ā§āĻ•āĻŦāĻžāĻ°ā§āϤāĻž: āĻāϟāĻŋ āĻĒāϰāĻŦāĻ°ā§āϤ⧀āϤ⧇ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻž āϝāĻžāĻŦ⧇ āύāĻž!",
"notification_email_from_address": "āĻĒā§āϰ⧇āϰāϕ⧇āϰ āĻ āĻŋāĻ•āĻžāύāĻž (From address)",
"notification_email_from_address_description": "āĻĒā§āϰ⧇āϰāϕ⧇āϰ āχāĻŽā§‡āϞ āĻ āĻŋāĻ•āĻžāύāĻž, āωāĻĻāĻžāĻšāϰāĻŖāĻ¸ā§āĻŦāϰ⧂āĻĒ: \"Immich Photo Server noreply@example.com\"āĨ¤ āύāĻŋāĻļā§āϚāĻŋāϤ āĻ•āϰ⧁āύ āϝ⧇ āφāĻĒāύāĻŋ āĻāĻŽāύ āĻāĻ•āϟāĻŋ āĻ āĻŋāĻ•āĻžāύāĻž āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāϛ⧇āύ āϝāĻž āĻĨ⧇āϕ⧇ āχāĻŽā§‡āϞ āĻĒāĻžāĻ āĻžāύ⧋āϰ āĻ…āύ⧁āĻŽāϤāĻŋ āφāĻĒāύāĻžāϰ āφāϛ⧇āĨ¤",
"notification_email_host_description": "āχāĻŽā§‡āϞ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇āϰ āĻšā§‹āĻ¸ā§āϟ (āϝ⧇āĻŽāύ: smtp.immich.app)",
"notification_email_ignore_certificate_errors": "āϏāĻžāĻ°ā§āϟāĻŋāĻĢāĻŋāϕ⧇āϟ āĻ¤ā§āϰ⧁āϟāĻŋāϗ⧁āϞ⧋ āωāĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰ⧁āύ (Ignore certificate errors)",
"notification_email_ignore_certificate_errors_description": "TLS āϏāĻžāĻ°ā§āϟāĻŋāĻĢāĻŋāϕ⧇āϟ āĻ­ā§āϝāĻžāϞāĻŋāĻĄā§‡āĻļāύ āĻ¤ā§āϰ⧁āϟāĻŋāϗ⧁āϞ⧋ āωāĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰ⧁āύ (āĻĒā§āϰāĻ¸ā§āϤāĻžāĻŦāĻŋāϤ āύāϝāĻŧ)",
"notification_email_password_description": "āχāĻŽā§‡āϞ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇ āĻ…āĻĨ⧇āĻ¨ā§āϟāĻŋāϕ⧇āĻļāύ āĻŦāĻž āϏāĻ¤ā§āϝāϤāĻž āϝāĻžāϚāĻžāĻ‡ā§Ÿā§‡āϰ āϜāĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšā§ƒāϤ āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ",
"notification_email_port_description": "āχāĻŽā§‡āϞ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇āϰ āĻĒā§‹āĻ°ā§āϟ (āϝ⧇āĻŽāύ: ⧍ā§Ģ, ā§Ēā§Ŧā§Ģ, āĻ…āĻĨāĻŦāĻž ā§Ģā§Žā§­)",
"notification_email_secure": "SMTPS (āĻ¸ā§āĻŽāĻžāĻ°ā§āϟ āĻŽā§‡āχāϞ āĻŸā§āϰāĻžāĻ¨ā§āϏāĻĢāĻžāϰ āĻĒā§āϰ⧋āĻŸā§‹āĻ•āϞ āϏāĻŋāĻ•āĻŋāωāϰ)",
"notification_email_secure_description": "SMTPS (SMTP over TLS) āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ",
"notification_email_sent_test_email_button": "āĻŸā§‡āĻ¸ā§āϟ āχāĻŽā§‡āϞ āĻĒāĻžāĻ āĻžāύ āĻāĻŦāĻ‚ āϏ⧇āĻ­ āĻ•āϰ⧁āύ",
"oauth_enable_description": "OAuth-āĻāϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āϞāĻ—āχāύ āĻ•āϰ⧁āύ",
"oauth_mobile_redirect_uri": "āĻŽā§‹āĻŦāĻžāχāϞ āϰāĻŋāĻĄāĻžāχāϰ⧇āĻ•ā§āϟ āχāωāφāϰāφāχ (URI)",
"oauth_mobile_redirect_uri_override": "āĻŽā§‹āĻŦāĻžāχāϞ āϰāĻŋāĻĄāĻžāχāϰ⧇āĻ•ā§āϟ āχāωāφāϰāφāχ (URI) āĻ“āĻ­āĻžāϰāϰāĻžāχāĻĄ",
"oauth_mobile_redirect_uri_override_description": "āϝāĻ–āύ OAuth āĻĒā§āϰ⧋āĻ­āĻžāχāĻĄāĻžāϰ āĻŽā§‹āĻŦāĻžāχāϞ āχāωāφāϰāφāχ (URI) āĻ…āύ⧁āĻŽāϤāĻŋ āĻĻā§‡ā§Ÿ āύāĻž, āϝ⧇āĻŽāύ ''{callback}'', āϤāĻ–āύ āĻāϟāĻŋ āϏāĻ•ā§āϰāĻŋ⧟ āĻ•āϰ⧁āύāĨ¤",
"oauth_role_claim": "āϰ⧋āϞ āĻ•ā§āϞ⧇āχāĻŽ (Role Claim)",
"oauth_role_claim_description": "āĻāχ āĻ•ā§āϞ⧇āχāĻŽāϟāĻŋāϰ āωāĻĒāĻ¸ā§āĻĨāĻŋāϤāĻŋāϰ āĻ“āĻĒāϰ āĻ­āĻŋāĻ¤ā§āϤāĻŋ āĻ•āϰ⧇ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϏ āĻĒā§āϰāĻĻāĻžāύ āĻ•āϰ⧁āύāĨ¤ āĻ•ā§āϞ⧇āχāĻŽāϟāĻŋāϤ⧇ 'user' āĻ…āĻĨāĻŦāĻž 'admin' āϝ⧇āϕ⧋āύ⧋ āĻāĻ•āϟāĻŋ āĻĨāĻžāĻ•āϤ⧇ āĻĒāĻžāϰ⧇āĨ¤",
"oauth_settings": "OAuth",
"oauth_settings_description": "OAuth āϞāĻ—āχāύ āϏ⧇āϟāĻŋāĻ‚āϏ āĻŽā§āϝāĻžāύ⧇āϜ āĻ•āϰ⧁āύ",
"oauth_settings_more_details": "āĻāχ āĻĢāĻŋāϚāĻžāϰ⧇āϰ āĻŦā§āϝāĻžāĻĒāĻžāϰ⧇ āφāϰāĻ“ āĻŦāĻŋāĻ¸ā§āϤāĻžāϰāĻŋāϤ āϜāĻžāύāϤ⧇, <link>āĻĄāϕ⧁āĻŽā§‡āĻ¨ā§āϟāϏ</link> āĻĻ⧇āϖ⧁āύāĨ¤",
"oauth_storage_label_claim": "āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āϞ⧇āĻŦ⧇āϞ āĻ•ā§āϞ⧇āχāĻŽ (Storage label claim)",
"oauth_storage_label_claim_description": "āĻāχ āĻ•ā§āϞ⧇āχāĻŽ-āĻāϰ āĻ­ā§āϝāĻžāϞ⧁ āĻ…āύ⧁āϝāĻžā§Ÿā§€ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āϞ⧇āĻŦ⧇āϞ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āϏ⧇āϟ āĻ•āϰ⧁āύāĨ¤",
"oauth_storage_quota_claim": "āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āϕ⧋āϟāĻž āĻ•ā§āϞ⧇āχāĻŽ (Storage quota claim)",
"oauth_storage_quota_claim_description": "āĻāχ āĻ•ā§āϞ⧇āχāĻŽ-āĻāϰ āĻ­ā§āϝāĻžāϞ⧁ āĻ…āύ⧁āϝāĻžā§Ÿā§€ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āϕ⧋āϟāĻž āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āϏ⧇āϟ āĻ•āϰ⧁āύāĨ¤",
"oauth_storage_quota_default": "āĻĄāĻŋāĻĢāĻ˛ā§āϟ āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āϕ⧋āϟāĻž (GiB)",
"oauth_storage_quota_default_description": "āĻ•ā§āϞ⧇āχāĻŽ āύāĻž āĻĻ⧇āĻ“ā§ŸāĻž āĻĨāĻžāĻ•āϞ⧇ āϝ⧇ āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āϕ⧋āϟāĻž (GiB-āϤ⧇) āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤",
"oauth_timeout": "āϰāĻŋāϕ⧋āϝāĻŧ⧇āĻ¸ā§āϟ āϟāĻžāχāĻŽ-āφāωāϟ (Request Timeout)",
"oauth_timeout_description": "āĻŽāĻŋāϞāĻŋāϏ⧇āϕ⧇āĻ¨ā§āĻĄā§‡ āϰāĻŋāĻ•ā§‹ā§Ÿā§‡āĻ¸ā§āĻŸā§‡āϰ āϟāĻžāχāĻŽ-āφāωāϟ (Timeout for requests in milliseconds)",
"ocr_job_description": "āĻ›āĻŦāĻŋ āĻĨ⧇āϕ⧇ āĻŸā§‡āĻ•ā§āϏāϟ āĻļāύāĻžāĻ•ā§āϤ āĻ•āϰāϤ⧇ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ",
"password_enable_description": "āχāĻŽā§‡āϞ āĻāĻŦāĻ‚ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻĻāĻŋāϝāĻŧ⧇ āϞāĻ—āχāύ āĻ•āϰ⧁āύ",
"password_settings": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āϞāĻ—āχāύ (Password Login)",
"password_settings_description": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āϞāĻ—āχāύ āϏ⧇āϟāĻŋāĻ‚āϏ āĻŽā§āϝāĻžāύ⧇āϜ āĻ•āϰ⧁āύ",
"paths_validated_successfully": "āϏāĻŦāϗ⧁āϞ⧋ āĻĒāĻžāĻĨ (path) āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āϝāĻžāϚāĻžāχ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇",
"person_cleanup_job": "āĻĒāĻžāϰāϏāύ āĻ•ā§āϞāĻŋāύāφāĻĒ (Person Cleanup)",
"queue_details": "āĻ•āĻŋāω āĻĄāĻŋāĻŸā§‡āχāϞāϏ (Queue Details)",
"queues": "āϜāĻŦ āĻ•āĻŋāω (Job Queues)",
"queues_page_description": "āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ āϜāĻŦ āĻ•āĻŋāω (Job Queues) āĻĒ⧇āϜ",
"quota_size_gib": "āϕ⧋āϟāĻž āϏāĻžāχāϜ (GiB)",
"refreshing_all_libraries": "āϏāĻŦāϗ⧁āϞ⧋ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āϰāĻŋāĻĢā§āϰ⧇āĻļ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇",
"registration": "āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ āϰ⧇āϜāĻŋāĻ¸ā§āĻŸā§āϰ⧇āĻļāύ (Admin Registration)",
"registration_description": "āϝ⧇āĻšā§‡āϤ⧁ āφāĻĒāύāĻŋ āĻāχ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽā§‡āϰ āĻĒā§āϰāĻĨāĻŽ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀, āϤāĻžāχ āφāĻĒāύāĻžāϕ⧇ āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ (Admin) āĻšāĻŋāϏ⧇āĻŦ⧇ āύāĻŋāϝ⧁āĻ•ā§āϤ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤ āφāĻĒāύāĻŋ āϏāĻŽāĻ¸ā§āϤ āĻĒā§āϰāĻļāĻžāϏāύāĻŋāĻ• āĻ•āĻžāĻœā§‡āϰ āϜāĻ¨ā§āϝ āĻĻāĻžāϝāĻŧā§€ āĻĨāĻžāĻ•āĻŦ⧇āύ āĻāĻŦāĻ‚ āĻĒāϰāĻŦāĻ°ā§āϤ⧀ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰāĻž āφāĻĒāύāĻžāϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡āχ āϤ⧈āϰāĻŋ āĻšāĻŦ⧇āĨ¤",
"remove_failed_jobs": "āĻŦā§āϝāĻ°ā§āĻĨ āĻšāĻ“āϝāĻŧāĻž āĻ•āĻžāϜāϗ⧁āϞ⧋ āĻŽā§āϛ⧇ āĻĢ⧇āϞ⧁āύ (Remove failed jobs)",
"require_password_change_on_login": "āĻĒā§āϰāĻĨāĻŽāĻŦāĻžāϰ āϞāĻ—āχāύ āĻ•āϰāĻžāϰ āϏāĻŽā§Ÿ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻž āĻŦāĻžāĻ§ā§āϝāϤāĻžāĻŽā§‚āϞāĻ• āĻ•āϰ⧁āύ",
"reset_settings_to_default": "āϏ⧇āϟāĻŋāĻ‚āϏ āϰāĻŋāϏ⧇āϟ āĻ•āϰ⧇ āĻĄāĻŋāĻĢāĻ˛ā§āϟ āĻ…āĻŦāĻ¸ā§āĻĨāĻžā§Ÿ āĻĢāĻŋāϰāĻŋā§Ÿā§‡ āφāύ⧁āύ (Reset settings to default)",
"reset_settings_to_recent_saved": "āϏāĻŽā§āĻĒā§āϰāϤāĻŋ āϏ⧇āĻ­ āĻ•āϰāĻž āϏ⧇āϟāĻŋāĻ‚āϏ⧇ āϰāĻŋāϏ⧇āϟ āĻ•āϰ⧁āύ (Reset settings to the recent saved settings)",
"scanning_library": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇ (Scanning library)",
"search_jobs": "āϜāĻŦ āϏāĻžāĻ°ā§āϚ āĻ•āϰ⧁āύâ€Ļ",
"send_welcome_email": "āĻ¸ā§āĻŦāĻžāĻ—āϤ āχāĻŽā§‡āϞ āĻĒāĻžāĻ āĻžāύ",
"server_external_domain_settings": "āĻāĻ•ā§āϏāϟāĻžāĻ°ā§āύāĻžāϞ āĻĄā§‹āĻŽā§‡āχāύ (External Domain)",
"server_external_domain_settings_description": "āĻĒāĻžāĻŦāϞāĻŋāĻ• āĻļ⧇āϝāĻŧāĻžāϰāĻŋāĻ‚ āϞāĻŋāĻ™ā§āϕ⧇āϰ āϜāĻ¨ā§āϝ āĻĄā§‹āĻŽā§‡āχāύ (http(s):// āϏāĻš)",
"server_public_users": "āĻĒāĻžāĻŦāϞāĻŋāĻ• āχāωāϜāĻžāϰ (Public Users)",
"server_public_users_description": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϕ⧋āύ⧋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϕ⧇ āϝ⧋āĻ— āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āϏāĻŽāĻ¸ā§āϤ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ (āύāĻžāĻŽ āĻāĻŦāĻ‚ āχāĻŽā§‡āϞ) āϤāĻžāϞāĻŋāĻ•āĻž āĻĻ⧇āĻ–āĻžāύ⧋ āĻšāϝāĻŧāĨ¤ āĻāϟāĻŋ āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ (Disabled) āĻ•āϰāĻž āĻšāϞ⧇, āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āϤāĻžāϞāĻŋāĻ•āĻž āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύāĻĻ⧇āϰ āϜāĻ¨ā§āϝ āωāĻĒāϞāĻŦā§āϧ āĻšāĻŦ⧇āĨ¤",
"server_settings": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āϏ⧇āϟāĻŋāĻ‚āϏ (Server Settings)",
"server_settings_description": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āϏ⧇āϟāĻŋāĻ‚āϏ āĻŽā§āϝāĻžāύ⧇āϜ āĻ•āϰ⧁āύ (Manage server settings)",
"server_stats_page_description": "āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻ¸ā§āĻŸā§āϝāĻžāϟāĻŋāĻ¸ā§āϟāĻŋāĻ•āϏ (Server Statistics) āĻĒ⧇āϜ",
"server_welcome_message": "āĻ¸ā§āĻŦāĻžāĻ—āϤ āĻŦāĻžāĻ°ā§āϤāĻž (Welcome message)",
"server_welcome_message_description": "āϞāĻ—āχāύ āĻĒ⧇āĻœā§‡ āĻĒā§āϰāĻĻāĻ°ā§āĻļāĻŋāϤ āĻāĻ•āϟāĻŋ āĻŦāĻžāĻ°ā§āϤāĻžāĨ¤",
"settings_page_description": "āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒ⧇āϜ",
"sidecar_job": "āϏāĻžāχāĻĄāĻ•āĻžāϰ āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž (Sidecar Metadata)",
"sidecar_job_description": "āĻĢāĻžāχāϞāϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻĨ⧇āϕ⧇ āϏāĻžāχāĻĄāĻ•āĻžāϰ āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻŦāĻž āϏāĻŋāĻ™ā§āĻ•ā§āϰ⧋āύāĻžāχāϜ āĻ•āϰ⧁āύ",
"slideshow_duration_description": "āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻ›āĻŦāĻŋ āĻĻ⧇āĻ–āĻžāύ⧋āϰ āϏāĻŽā§ŸāĻ•āĻžāϞ (āϏ⧇āϕ⧇āĻ¨ā§āĻĄā§‡)",
"smart_search_job_description": "āĻ¸ā§āĻŽāĻžāĻ°ā§āϟ āϏāĻžāĻ°ā§āĻšā§‡āϰ āϏ⧁āĻŦāĻŋāϧāĻžāĻ°ā§āĻĨ⧇ āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋āϰ āĻ“āĻĒāϰ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ",
"storage_template_date_time_description": "āĻ…ā§āϝāĻžāϏ⧇āϟ āϤ⧈āϰāĻŋāϰ āϏāĻŽā§ŸāĻ•āĻžāϞ (Timestamp) āϤāĻžāϰāĻŋāĻ– āĻ“ āϏāĻŽā§Ÿā§‡āϰ āϤāĻĨā§āϝ⧇āϰ āϜāĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšā§ƒāϤ āĻšā§Ÿ",
"storage_template_date_time_sample": "āύāĻŽā§āύāĻž āϏāĻŽā§Ÿ {date}",
"storage_template_enable_description": "āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ āχāĻžā§āϜāĻŋāύ āϏāĻ•ā§āϰāĻŋ⧟ āĻ•āϰ⧁āύ",
"storage_template_hash_verification_enabled": "āĻšā§āϝāĻžāĻļ āϭ⧇āϰāĻŋāĻĢāĻŋāϕ⧇āĻļāύ (Hash Verification) āϏāĻ•ā§āϰāĻŋ⧟ āĻ•āϰāĻž āĻšā§Ÿā§‡āϛ⧇",
"storage_template_hash_verification_enabled_description": "āĻšā§āϝāĻžāĻļ āϭ⧇āϰāĻŋāĻĢāĻŋāϕ⧇āĻļāύ (Hash Verification) āϏāĻ•ā§āϰāĻŋ⧟ āĻ•āϰ⧇; āĻāϰ āĻĒā§āϰāĻ­āĻžāĻŦ āϏāĻŽā§āĻĒāĻ°ā§āϕ⧇ āύāĻŋāĻļā§āϚāĻŋāϤ āύāĻž āĻšā§Ÿā§‡ āĻāϟāĻŋ āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋ⧟ āĻ•āϰāĻŦ⧇āύ āύāĻž",
"storage_template_migration": "āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ āĻŽāĻžāχāĻ—ā§āϰ⧇āĻļāύ (Storage Template Migration)",
"storage_template_migration_description": "āĻĒā§‚āĻ°ā§āĻŦ⧇ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋āϤ⧇ āĻŦāĻ°ā§āϤāĻŽāĻžāύ <link>{template}</link> āĻĒā§āϰāϝāĻŧā§‹āĻ— āĻ•āϰ⧁āύ",
"storage_template_migration_info": "āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟāϟāĻŋ āϏāĻŽāĻ¸ā§āϤ āĻāĻ•ā§āϏāĻŸā§‡āύāĻļāύāϕ⧇ āϛ⧋āϟ āĻšāĻžāϤ⧇āϰ āĻ…āĻ•ā§āώāϰ⧇ (lowercase) āϰ⧂āĻĒāĻžāĻ¨ā§āϤāϰ āĻ•āϰāĻŦ⧇āĨ¤ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āĻŸā§‡āϰ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύāϗ⧁āϞ⧋ āϕ⧇āĻŦāϞ āύāϤ⧁āύ āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋āϰ āĻ•ā§āώ⧇āĻ¤ā§āϰ⧇ āĻĒā§āϰāϝ⧋āĻœā§āϝ āĻšāĻŦ⧇āĨ¤ āĻĒā§‚āĻ°ā§āĻŦ⧇ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋āϤ⧇ āĻāχ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟāϟāĻŋ āĻ­ā§‚āϤāĻžāĻĒ⧇āĻ•ā§āώāĻ­āĻžāĻŦ⧇ (retroactively) āĻĒā§āϰāϝāĻŧā§‹āĻ— āĻ•āϰāϤ⧇ <link>{job}</link> āϰāĻžāύ āĻ•āϰ⧁āύāĨ¤",
"storage_template_migration_job": "āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ āĻŽāĻžāχāĻ—ā§āϰ⧇āĻļāύ āϜāĻŦ",
"storage_template_more_details": "āĻāχ āĻĢāĻŋāϚāĻžāϰāϟāĻŋ āϏāĻŽā§āĻĒāĻ°ā§āϕ⧇ āφāϰāĻ“ āĻŦāĻŋāĻ¸ā§āϤāĻžāϰāĻŋāϤ āϜāĻžāύāϤ⧇, <template-link>Storage Template</template-link> āĻāĻŦāĻ‚ āĻāϰ <implications-link>āĻĒā§āϰāĻ­āĻžāĻŦāϗ⧁āϞ⧋ (implications)</implications-link> āĻĻ⧇āϖ⧁āύāĨ¤",
"storage_template_onboarding_description_v2": "āĻāϟāĻŋ āϏāĻ•ā§āϰāĻŋ⧟ āĻĨāĻžāĻ•āϞ⧇, āĻĢāĻŋāϚāĻžāϰāϟāĻŋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŋāϤ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ āĻ…āύ⧁āϝāĻžā§Ÿā§€ āĻĢāĻžāχāϞāϗ⧁āϞ⧋āϕ⧇ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āĻ…āĻ°ā§āĻ—āĻžāύāĻžāχāϜ (Auto-organize) āĻ•āϰāĻŦ⧇āĨ¤ āφāϰāĻ“ āϤāĻĨā§āϝ⧇āϰ āϜāĻ¨ā§āϝ āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ <link>āĻĄāϕ⧁āĻŽā§‡āĻ¨ā§āĻŸā§‡āĻļāύ</link> āĻĻ⧇āϖ⧁āύāĨ¤",
"storage_template_path_length": "āφāύ⧁āĻŽāĻžāύāĻŋāĻ• āĻĒāĻžāĻĨ āϞ⧇āĻ¨ā§āĻĨ āϞāĻŋāĻŽāĻŋāϟ (Path length limit): <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ (Storage Template)",
"storage_template_settings_description": "āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡āϰ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻ¸ā§āĻŸā§āϰāĻžāĻ•āϚāĻžāϰ āĻāĻŦāĻ‚ āĻĢāĻžāχāϞ āύ⧇āĻŽ āĻŽā§āϝāĻžāύ⧇āϜ āĻ•āϰ⧁āύ",
"storage_template_user_label": "<code>{label}</code> āĻšāϞ⧋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āϞ⧇āĻŦ⧇āϞ (Storage Label)",
"theme_settings_description": "āχāĻŽāĻŋāϚ (Immich) āĻ“āϝāĻŧ⧇āĻŦ āχāĻ¨ā§āϟāĻžāϰāĻĢ⧇āϏ⧇āϰ āĻ•āĻžāĻ¸ā§āϟāĻŽāĻžāχāĻœā§‡āĻļāύ āĻŽā§āϝāĻžāύ⧇āϜ āĻ•āϰ⧁āύ",
"thumbnail_generation_job": "āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ (Generate Thumbnails)",
"thumbnail_generation_job_description": "āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡āϰ āϜāĻ¨ā§āϝ āĻŦ⧜, āϛ⧋āϟ āĻāĻŦāĻ‚ āĻŦā§āϞāĻžāϰ (āĻ…āĻ¸ā§āĻĒāĻˇā§āϟ) āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ, āϏ⧇āχ āϏāĻžāĻĨ⧇ āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ āϜāĻ¨ā§āϝāĻ“ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύāĨ¤",
"transcoding_acceleration_api": "āĻ…ā§āϝāĻžāĻ•ā§āϏāĻŋāϞāĻžāϰ⧇āϟ āĻāĻĒāĻŋāφāχ (Acceleration API)",
"transcoding_acceleration_api_description": "āĻŸā§āϰāĻžāύāϏāϕ⧋āĻĄāĻŋāĻ‚ (transcoding) āĻĻā§āϰ⧁āϤ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āφāĻĒāύāĻžāϰ āĻĄāĻŋāĻ­āĻžāχāϏ⧇āϰ āϏāĻžāĻĨ⧇ āϝ⧇ API āχāĻ¨ā§āϟāĻžāϰāĻ…ā§āϝāĻžāĻ•ā§āϟ āĻ•āϰāĻŦ⧇āĨ¤ āĻāχ āϏ⧇āϟāĻŋāĻ‚āϏāϟāĻŋ 'āϏāĻžāĻ§ā§āϝāĻŽāϤ⧋' (best effort) āĻ•āĻžāϜ āĻ•āϰāĻŦ⧇: āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϞ⧇ āĻāϟāĻŋ āĻĒ⧁āύāϰāĻžā§Ÿ āϏāĻĢāϟāĻ“ā§Ÿā§āϝāĻžāϰ āĻŸā§āϰāĻžāύāϏāϕ⧋āĻĄāĻŋāĻ‚ā§Ÿā§‡ āĻĢāĻŋāϰ⧇ āφāϏāĻŦ⧇āĨ¤ āĻšāĻžāĻ°ā§āĻĄāĻ“ā§Ÿā§āϝāĻžāϰ⧇āϰ āĻ“āĻĒāϰ āĻ­āĻŋāĻ¤ā§āϤāĻŋ āĻ•āϰ⧇ VP9 āĻ•āĻžāϜ āĻ•āϰāϤ⧇āĻ“ āĻĒāĻžāϰ⧇, āφāĻŦāĻžāϰ āύāĻžāĻ“ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤",
"transcoding_acceleration_nvenc": "NVENC (NVIDIA GPU āĻĒā§āĻ°ā§Ÿā§‹āϜāύ)",
"transcoding_acceleration_qsv": "Quick Sync (ā§­āĻŽ āĻĒā§āϰāϜāĻ¨ā§āĻŽā§‡āϰ āχāύāĻŸā§‡āϞ CPU āĻŦāĻž āĻĒāϰāĻŦāĻ°ā§āϤ⧀ āĻ­āĻžāĻ°ā§āϏāύ āĻĒā§āĻ°ā§Ÿā§‹āϜāύ)",
"transcoding_acceleration_rkmpp": "RKMPP (āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ Rockchip SOC-āĻāϰ āϜāĻ¨ā§āϝ)",
"transcoding_acceleration_vaapi": "VA-API (āĻ­āĻŋāĻĄāĻŋāĻ“ āĻ…ā§āϝāĻžāĻ•ā§āϏāĻŋāϞāĻžāϰ⧇āĻļāύ āĻāĻĒāĻŋāφāχ)",
"transcoding_accepted_audio_codecs": "āĻ—ā§āϰāĻšāĻŖāϝ⧋āĻ—ā§āϝ āĻ…āĻĄāĻŋāĻ“ āϕ⧋āĻĄā§‡āĻ•āϏāĻŽā§‚āĻš (Accepted audio codecs)",
"transcoding_accepted_audio_codecs_description": "āϕ⧋āύ āĻ…āĻĄāĻŋāĻ“ āϕ⧋āĻĄā§‡āĻ•āϗ⧁āϞ⧋ āĻŸā§āϰāĻžāύāϏāϕ⧋āĻĄ āĻ•āϰāĻžāϰ āĻĒā§āĻ°ā§Ÿā§‹āϜāύ āύ⧇āχ āϤāĻž āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύāĨ¤ āĻāϟāĻŋ āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻŸā§āϰāĻžāύāϏāϕ⧋āĻĄ āĻĒāϞāĻŋāϏāĻŋāϰ (transcode policies) āϜāĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšā§ƒāϤ āĻšā§ŸāĨ¤",
"transcoding_accepted_containers": "āĻ—ā§āϰāĻšāĻŖāϝ⧋āĻ—ā§āϝ āĻ•āĻ¨ā§āĻŸā§‡āχāύāĻžāϰāϏāĻŽā§‚āĻš (Accepted containers)"
},
"yes": "āĻšā§āϝāĻžāρ",
"you_dont_have_any_shared_links": "āφāĻĒāύāĻžāϰ āϕ⧋āύ⧋ āĻļā§‡ā§ŸāĻžāϰ āĻ•āϰāĻž āϞāĻŋāĻ™ā§āĻ• āύ⧇āχ (You don't have any shared links)",
"your_wifi_name": "āφāĻĒāύāĻžāϰ āĻ“āϝāĻŧāĻžāχ-āĻĢāĻžāχ āĻāϰ āύāĻžāĻŽ (Your Wi-Fi name)",
"zero_to_clear_rating": "āĻ…ā§āϝāĻžāϏ⧇āϟ āϰ⧇āϟāĻŋāĻ‚ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ ā§Ļ āϚāĻžāĻĒ⧁āύ",
"zoom_image": "āĻ›āĻŦāĻŋ āϜ⧁āĻŽ āĻ•āϰ⧁āύ (Zoom Image)",
"zoom_to_bounds": "āĻŦāĻžāωāĻ¨ā§āĻĄāϏ āĻ…āύ⧁āϝāĻžā§Ÿā§€ āϜ⧁āĻŽ āĻ•āϰ⧁āύ (Zoom to bounds)"
}

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