Compare commits

..

12 Commits

Author SHA1 Message Date
Hosted Weblate 40fe199994 chore(web): update translations
Co-authored-by: Alexis Rossfelder <rossfelderalexis@gmail.com>
Co-authored-by: DevServs <bonov@mail.ru>
Co-authored-by: HackingAll <hacking.all.YT@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Hồ Nhất Duy <axicenia@gmail.com>
Co-authored-by: Indrek Haav <indrekhaav@users.noreply.hosted.weblate.org>
Co-authored-by: Jordy H <jordy@hoebergen.net>
Co-authored-by: Ketan Avhad <ketaavha@gmail.com>
Co-authored-by: Klenner Martins Barros <klenne.al@gmail.com>
Co-authored-by: Nagy Krisztián <nkgy17@gmail.com>
Co-authored-by: Pavel Miniutka <pavel.miniutka@gmail.com>
Co-authored-by: Tushar Harsora <tusharharsora95@gmail.com>
Co-authored-by: vatahome <vataszilamer@pm.me>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Translate-URL: https://hosted.weblate.org/projects/immich/immich/be/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/
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/gl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/mr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/
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/vi/
Translation: Immich/immich
2026-07-02 11:01:22 +02:00
Ben Beckford 237734bb26 feat(web): recently added link in sidebar (#29039)
* feat(web): recently added link in sidebar

* chore(mobile): update openapi patches
2026-07-01 23:12:50 +00:00
Weblate (bot) 4b54fef82e chore(web): update translations (#29410)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ar/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/be/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/bg/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ca/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/da/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
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/gu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/he/
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/pt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ro/
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/vi/
Translation: Immich/immich

Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Charles IdB <charles.issert2braux@gmail.com>
Co-authored-by: Dmitry Banny <dj.icecore@gmail.com>
Co-authored-by: Enric Pagès i Gassull <enricpages@hotmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: HackingAll <hacking.all.YT@gmail.com>
Co-authored-by: Harsh Kevadia <kevadiyaharsh@gmail.com>
Co-authored-by: Hosted Weblate user 156232 <53017937+parol100@users.noreply.github.com>
Co-authored-by: Hurricane_32 <rodrigorimo@hotmail.com>
Co-authored-by: Hồ Nhất Duy <axicenia@gmail.com>
Co-authored-by: Ivan Dimitrov <idimitrov08@gmail.com>
Co-authored-by: Jeppe Nellemann <jepnel@proton.me>
Co-authored-by: Mahmoud Dwidar <modydodo2055@gmail.com>
Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Co-authored-by: Pavel Miniutka <pavel.miniutka@gmail.com>
Co-authored-by: Yago Raña Gayoso <yago.rana.gayoso@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: ikeno-web <ikeno@nextcore-consulting.com>
Co-authored-by: rubes <mail@armd.one>
2026-07-01 11:05:25 +00:00
Daniel Dietzler 0050332391 fix: e2e version test (#29412) 2026-07-01 12:41:45 +02:00
github-actions 05d838b560 chore: version v3.0.0 2026-06-30 20:03:47 +00:00
Weblate (bot) 82b70c1ab6 chore(web): update translations (#29347)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ar/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/be/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/bg/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ca/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/da/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
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/gu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/he/
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/pt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ro/
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/vi/
Translation: Immich/immich

Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Charles IdB <charles.issert2braux@gmail.com>
Co-authored-by: Dmitry Banny <dj.icecore@gmail.com>
Co-authored-by: Enric Pagès i Gassull <enricpages@hotmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: HackingAll <hacking.all.YT@gmail.com>
Co-authored-by: Harsh Kevadia <kevadiyaharsh@gmail.com>
Co-authored-by: Hosted Weblate user 156232 <53017937+parol100@users.noreply.github.com>
Co-authored-by: Hurricane_32 <rodrigorimo@hotmail.com>
Co-authored-by: Hồ Nhất Duy <axicenia@gmail.com>
Co-authored-by: Ivan Dimitrov <idimitrov08@gmail.com>
Co-authored-by: Jeppe Nellemann <jepnel@proton.me>
Co-authored-by: Mahmoud Dwidar <modydodo2055@gmail.com>
Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Co-authored-by: Pavel Miniutka <pavel.miniutka@gmail.com>
Co-authored-by: Yago Raña Gayoso <yago.rana.gayoso@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: ikeno-web <ikeno@nextcore-consulting.com>
Co-authored-by: rubes <mail@armd.one>
2026-06-30 20:01:21 +00:00
Daniel Dietzler 02506424a7 feat: integrity checks admin settings (#29406) 2026-06-30 14:58:51 -05:00
Ben Beckford 6a7a34d294 chore: make webhooks workflow-agnostic (#29404) 2026-06-30 15:27:30 -04:00
Alex 165bca4b0a feat: new feature message (#29388)
* feat: new feature board

* wip

* wip

* wip

* lint

* lint

* pr feedback

* pr feedback

* i18n

* i18n
2026-06-30 13:53:13 -05:00
Brandon Wees d4b994301f fix: version compatability check (#29405) 2026-06-30 18:44:53 +00:00
Daniel Dietzler deeb042a9e feat: honor album access permissions in search endpoints (#29352) 2026-06-29 22:27:22 +02:00
Daniel Dietzler b4cc406a3f fix!: search endpoints visibility can be omitted (#29385) 2026-06-29 22:00:02 +02:00
141 changed files with 3016 additions and 1339 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ DB_DATA_LOCATION=./postgres
# TZ=Etc/UTC
# The Immich version to use. You can pin this to a specific version like "v2.1.0"
IMMICH_VERSION=v2
IMMICH_VERSION=v3
# Connection secret for postgres. You should change it to a random password
# Please use only the characters `A-Za-z0-9`, without special characters or spaces
+1 -1
View File
@@ -19,7 +19,7 @@ If this does not work, try running `docker compose up -d --force-recreate`.
| Variable | Description | Default | Containers |
| :----------------- | :------------------------------ | :-----: | :----------------------- |
| `IMMICH_VERSION` | Image tags | `v2` | server, machine learning |
| `IMMICH_VERSION` | Image tags | `v3` | server, machine learning |
| `UPLOAD_LOCATION` | Host path for uploads | | server |
| `DB_DATA_LOCATION` | Host path for Postgres database | | database |
+1 -1
View File
@@ -29,7 +29,7 @@ docker image prune
## 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`.
You can configure your Docker image to point to the current major version by using a metatag, such as `:v3`.
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.
+2 -2
View File
@@ -1,7 +1,7 @@
[
{
"label": "v3.0.0-rc.4",
"url": "https://docs.v3.0.0-rc.4.archive.immich.app"
"label": "v3.0.0",
"url": "https://docs.v3.0.0.archive.immich.app"
},
{
"label": "v2.7.5",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "immich-e2e",
"version": "3.0.0-rc.4",
"version": "3.0.0",
"description": "",
"main": "index.js",
"type": "module",
+8 -6
View File
@@ -91,12 +91,14 @@ describe('/server', () => {
it('should respond with the server version', async () => {
const { status, body } = await request(app).get('/server/version');
expect(status).toBe(200);
expect(body).toEqual({
major: expect.any(Number),
minor: expect.any(Number),
patch: expect.any(Number),
prerelease: expect.anything(),
});
expect(body).toEqual(
expect.objectContaining({
major: expect.any(Number),
minor: expect.any(Number),
patch: expect.any(Number),
}),
);
expect(Object.keys(body)).toEqual(expect.arrayContaining(['major', 'minor', 'patch', 'prerelease']));
});
});
+3
View File
@@ -82,6 +82,9 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI
cast: {
gCastEnabled: false,
},
recentlyAdded: {
sidebarWeb: false,
},
},
});
});
+1
View File
@@ -110,6 +110,7 @@ export async function enableTagsPreference(context: BrowserContext) {
download: { archiveSize: 4_294_967_296, includeEmbeddedVideos: false },
purchase: { showSupportBadge: true, hideBuyButtonUntil: '2100-02-12T00:00:00.000Z' },
cast: { gCastEnabled: false },
recentlyAdded: { sidebarWeb: false },
},
});
});
+1 -1
View File
@@ -1304,7 +1304,7 @@
"login_form_handshake_exception": "كان هناك استثناء مصافحة مع الخادم.تمكين دعم الشهادة الموقعة ذاتيا في الإعدادات إذا كنت تستخدم شهادة موقعة ذاتيا.",
"login_form_password_hint": "كلمة المرور",
"login_form_server_empty": "أدخل عنوان URL الخادم.",
"login_form_server_error": "لا يمكن الاتصال بالخادم.",
"login_form_server_error": "تعذر الاتصال بالخادم.",
"login_has_been_disabled": "تم تعطيل تسجيل الدخول.",
"login_password_changed_error": "كان هناك خطأ في تحديث كلمة المرور الخاصة بك",
"login_password_changed_success": "تم تحديث كلمة السر بنجاح",
+8 -6
View File
@@ -108,6 +108,8 @@
"image_thumbnail_quality_description": "Якасць мініяцюр ад 1 да 100. Чым вышэй, тым лепш, але файлы становяцца большымі, што можа знізіць хуткасць працы праграмы.",
"image_thumbnail_title": "Налады мініяцюр",
"import_config_from_json_description": "Імпартаваць канфігурацыю сістэмы праз запампоўванне JSON файла настроек",
"integrity_checks_checksum_files_description": "Наладжванне праверкі кантрольнай сумы",
"integrity_checks_checksum_files_enable_description": "Уключыць праверку кантрольнай сумы",
"job_concurrency": "Колькасць паралельных патокаў задання {job}",
"job_created": "Заданне створана",
"job_not_concurrency_safe": "Гэта заданне небяспечнае для паралельнага выканання.",
@@ -250,7 +252,7 @@
"nightly_tasks_sync_quota_usage_setting_description": "Абнаўляць квоту сховішча карыстальніка на аснове бягучага выкарыстання",
"no_paths_added": "Не дададзена аніводнага шляху",
"no_pattern_added": "Не дададзена аніводнага патэрну",
"note_apply_storage_label_previous_assets": "Заўвага: Каб ужыць Маркіроўку сховішча да раней загружаных аб'ектаў, запусціце",
"note_apply_storage_label_previous_assets": "Заўвага: Каб ужыць Маркіроўку сховішча да раней запампаваных аб'ектаў, запусціце",
"note_cannot_be_changed_later": "УВАГА: Гэта нельга будзе змяніць пазней!",
"notification_email_from_address": "З адрасоў",
"notification_email_from_address_description": "Электронны адрас адпраўніка, для прыкладу: \"Immich Photo Server <noreply@example.com>\". Пераканайцеся, што выкарыстоўваееца адрас, якому дазволена адпраўляць электронныя лісты.",
@@ -1275,9 +1277,9 @@
"login_form_failed_login": "Памылка ўваходу, праверце URL-адрас сервера, электронную пошту і пароль",
"login_form_handshake_exception": "Адбылася памылка ўзаемадзеяння з серверам. Уключыце падтрымку самападпісаных сертыфікатаў у наладах, калі вы карыстаецеся самападпісаным сертыфікатам.",
"login_form_password_hint": "пароль",
"login_form_server_empty": "Увядзіце URL-адрас сервера.",
"login_form_server_error": "Не ўдалося падключыцца да сервера.",
"login_has_been_disabled": "Уваход быў адключаны.",
"login_form_server_empty": "Увядзіце URL-адрас сервера",
"login_form_server_error": "Не ўдалося падключыцца да сервера",
"login_has_been_disabled": "Уваход быў адключаны",
"login_password_changed_error": "Адбылася памылка пры абнаўленні пароля",
"login_password_changed_success": "Пароль паспяхова абноўлены",
"logout_all_device_confirmation": "Вы ўпэўнены, што хочаце выйсці з усіх прылад?",
@@ -2059,9 +2061,9 @@
"trash_page_info": "Элементы ў сметніцы будуць канчаткова выдалены праз {days} дзён",
"trashed_items_will_be_permanently_deleted_after": "Элементы ў сметніцы будуць канчаткова выдалены праз {days, plural, one {# дзень} few {# дні} many {# дзён} other {# дня}}.",
"trigger": "Трыгер",
"trigger_asset_uploaded": "Аб’ект запампаваны",
"trigger_asset_uploaded": "Запампаваны аб’ект",
"trigger_asset_uploaded_description": "Спрацоўвае пры запампоўванні новага аб’екта",
"trigger_person_recognized": "Асоба распазнана",
"trigger_person_recognized": "Распазнана асоба",
"trigger_person_recognized_description": "Спрацоўвае пры выяўленні асобы",
"troubleshoot": "Пошук і выпраўленне непаладак",
"type": "Тып",
+3
View File
@@ -1507,6 +1507,9 @@
"notes": "Бележки",
"nothing_here_yet": "Засега тук няма нищо",
"notification_backup_reliability": "Позволете известията, за да подобрите надеждността на архивиране във фонов режим",
"notification_enabled_list_tile_content": "Immich използва механизма за известия при архивиране във фонов режим. Управлявайте ги от менюто за настройки на вашето устройство.",
"notification_enabled_list_tile_open_button": "Отвори настройки",
"notification_enabled_list_tile_title": "Известията са активирани",
"notification_permission_dialog_content": "За да включиш известията, отиди в Настройки и избери Разреши.",
"notification_permission_list_tile_content": "Дай разрешение за активиране на известията.",
"notification_permission_list_tile_enable_button": "Разреши известията",
+4 -1
View File
@@ -53,7 +53,7 @@
"backup_onboarding_1_description": "còpia externa al núvol o en una altra ubicació física.",
"backup_onboarding_2_description": "còpies locals en diferents dispositius. Això inclou els fitxers principals i una còpia de seguretat d'aquests fitxers localment.",
"backup_onboarding_3_description": "còpies totals de les vostres dades, inclosos els fitxers originals. Això inclou 1 còpia externa i 2 còpies locals.",
"backup_onboarding_description": "Es recomana una <backblaze-link>estratègia de còpia de seguretat 3-2-1</backblaze-link> per protegir les vostres dades. Hauríeu de conservar còpies de les vostres fotos/vídeos penjats, així com de la base de dades Immich per obtenir una solució de còpia de seguretat completa.",
"backup_onboarding_description": "Es recomana una <backblaze-link>estratègia de còpia de seguretat 3-2-1</backblaze-link> per protegir les vostres dades. Hauríeu de conservar còpies de les vostres fotos/vídeos penjats, així com de la base de dades d'Immich per obtenir una solució de còpia de seguretat completa.",
"backup_onboarding_footer": "Per obtenir més informació sobre com fer còpies de seguretat d'Immich, consulteu la <link>documentation</link>.",
"backup_onboarding_parts_title": "Una còpia de seguretat 3-2-1 inclou:",
"backup_onboarding_title": "Còpies de seguretat",
@@ -1507,6 +1507,9 @@
"notes": "Notes",
"nothing_here_yet": "No hi ha res encara",
"notification_backup_reliability": "Activa les notificacions per millorar la fiabilitat de les còpies de seguretat en segon pla",
"notification_enabled_list_tile_content": "Immich utilitza les notificacions per a les còpies de seguretat en segon pla. Gestioneu-les a la configuració del vostre dispositiu.",
"notification_enabled_list_tile_open_button": "Obre la configuració",
"notification_enabled_list_tile_title": "Notificacions activades",
"notification_permission_dialog_content": "Per activar les notificacions, aneu a Configuració i seleccioneu permet.",
"notification_permission_list_tile_content": "Atorga permís per a activar les notificacions.",
"notification_permission_list_tile_enable_button": "Activa les notificacions",
+35 -2
View File
@@ -108,6 +108,21 @@
"image_thumbnail_quality_description": "Kvalita miniatur od 1 do 100. Vyšší je lepší, ale vytváří větší soubory a může snížit odezvu aplikace.",
"image_thumbnail_title": "Miniatury",
"import_config_from_json_description": "Importujte konfiguraci systému nahráním konfiguračního JSON souboru",
"integrity_checks_checksum_files": "Soubory s kontrolními součty",
"integrity_checks_checksum_files_description": "Nastavení kontroly kontrolního součtu",
"integrity_checks_checksum_files_enable_description": "Zapnout kontrolu kontrolního součtu",
"integrity_checks_checksum_files_percentage_limit": "Procentuální limit",
"integrity_checks_checksum_files_percentage_limit_description": "Nastavte maximální procentuální hodnotu v rozmezí 0.01 až 1, která určuje, jak často se má v daném intervalu provádět kontrola kontrolního součtu.",
"integrity_checks_checksum_files_time_limit": "Časový limit",
"integrity_checks_checksum_files_time_limit_description": "Nastavte maximální dobu, po kterou by měla v každém intervalu probíhat kontrola kontrolního součtu. (ms)",
"integrity_checks_missing_files": "Chybějící soubory",
"integrity_checks_missing_files_description": "Nastavte frekvenci a zapněte nebo vypněte kontrolu chybějících souborů",
"integrity_checks_missing_files_enable_description": "Zapnout kontrolu chybějících souborů",
"integrity_checks_settings": "Kontroly integrity",
"integrity_checks_settings_description": "Správa intervalů kontrol integrity",
"integrity_checks_untracked_files": "Nesledované soubory",
"integrity_checks_untracked_files_description": "Nastavte frekvenci a zapněte nebo vypněte kontrolu nesledovaných souborů",
"integrity_checks_untracked_files_enable_description": "Zapnout kontrolu nesledovaných souborů",
"job_concurrency": "Souběžnost úlohy {job}",
"job_created": "Úloha vytvořena",
"job_not_concurrency_safe": "Tato úloha není bezpečená pro souběh.",
@@ -1461,6 +1476,7 @@
"never": "Nikdy",
"new_album": "Nové album",
"new_api_key": "Nový API klíč",
"new_feature": "Nová funkce",
"new_password": "Nové heslo",
"new_person": "Nová osoba",
"new_pin_code": "Nový PIN kód",
@@ -1521,6 +1537,8 @@
"obtainium_configurator": "Konfigurátor Obtainium",
"obtainium_configurator_instructions": "Pomocí aplikace Obtainium nainstalujte a aktualizujte aplikaci pro Android přímo z vydání na GitHubu Immich. Vytvořte API klíč a vyberte variantu pro vytvoření konfiguračního odkazu pro Obtainium",
"ocr": "OCR",
"ocr_body": "Immich nyní rozpoznává text na vašich fotografiích, takže je můžete vyhledávat podle toho, co je na nich napsáno.",
"ocr_title": "Vyhledávání textu ve vašich fotografiích",
"official_immich_resources": "Oficiální zdroje Immich",
"offline": "Offline",
"offset": "Posun",
@@ -1539,6 +1557,8 @@
"open": "Otevřít",
"open_calendar": "Otevřít kalendář",
"open_in_browser": "Otevřít v prohlížeči",
"open_in_immich_body": "Nastavte si v systému Android aplikaci Immich jako svou galerii, abyste mohli otevírat fotografie přímo z jiných aplikací.",
"open_in_immich_title": "Otevřít fotografie v Immichi",
"open_in_map_view": "Otevřít v zobrazení mapy",
"open_in_openstreetmap": "Otevřít v OpenStreetMap",
"open_the_search_filters": "Otevřít vyhledávací filtry",
@@ -1697,7 +1717,10 @@
"recent": "Nedávné",
"recent_searches": "Nedávná vyhledávání",
"recently_added": "Nedávno přidané",
"recently_added_body": "Přejděte přímo na speciální stránku, kde najdete vše, co jste v poslední době přidali.",
"recently_added_description": "Prohlížejte si svá média seřazená podle data, kdy byla nahrána do Immiche",
"recently_added_page_title": "Nedávno přidané",
"recently_added_title": "Nedávno přidané",
"recently_taken": "Nedávno pořízené",
"refresh": "Obnovit",
"refresh_encoded_videos": "Obnovit kódovaná videa",
@@ -1879,7 +1902,7 @@
"set_slideshow_to_fullscreen": "Nastavit prezentaci na celou obrazovku",
"set_stack_primary_asset": "Nastavit jako hlavní položku",
"setting_image_navigation_enable_subtitle": "Pokud je zapnuto, budete moci přejít na předchozí/další obrázek klepnutím do levé/pravé čtvrtiny obrazovky.",
"setting_image_navigation_enable_title": "Klepněte pro navigaci",
"setting_image_navigation_enable_title": "Klepnutí pro navigaci",
"setting_image_navigation_title": "Navigace mezi obrázky",
"setting_image_viewer_help": "V prohlížeči detailů se nejprve načte malá miniatura, poté se načte náhled střední velikosti (je-li povolen) a nakonec se načte originál (je-li povolen).",
"setting_image_viewer_original_subtitle": "Umožňuje načíst původní obrázek v plném rozlišení (velký!). Zakažte pro snížení využití dat (v síti i v mezipaměti zařízení).",
@@ -1904,6 +1927,8 @@
"share_link": "Sdílet odkaz",
"share_original": "Použít originál (velký)",
"share_preview": "Použít miniaturu (malý)",
"share_quality_body": "Při sdílení stiskněte a podržte tlačítko sdílení pro výběr kvality obrázku.",
"share_quality_title": "Vyberte si kvalitu sdílení",
"shared": "Sdílené",
"shared_album_activities_input_disable": "Komentář je vypnutý",
"shared_album_activity_remove_content": "Chcete odstranit tuto aktivitu?",
@@ -1985,16 +2010,19 @@
"sign_out": "Odhlásit se",
"sign_up": "Zaregistrovat se",
"size": "Velikost",
"skip": "Přeskočit",
"skip_to_content": "Přejít na obsah",
"skip_to_folders": "Přeskočit na složky",
"skip_to_tags": "Přeskočit na značky",
"slideshow": "Prezentace",
"slideshow_body": "Posaďte se a sledujte, jak se vaše fotografie promítají v prezentaci na celé obrazovce.",
"slideshow_metadata_overlay_mode": "Obsah překryvného panelu",
"slideshow_metadata_overlay_mode_description_only": "Pouze popis",
"slideshow_metadata_overlay_mode_full": "Úplný",
"slideshow_repeat": "Opakovat prezentaci",
"slideshow_repeat_description": "Po skončení prezentace se vrátit na začátek",
"slideshow_settings": "Nastavení prezentace",
"slideshow_title": "Prezentace",
"smart_album": "Chytré album",
"some_assets_already_have_a_location_warning": "Některé z vybraných položek již mají polohu",
"sort_albums_by": "Seřadit alba podle...",
@@ -2070,7 +2098,7 @@
"theme_setting_primary_color_subtitle": "Zvolte barvu pro hlavní akce a zvýraznění.",
"theme_setting_primary_color_title": "Hlavní barva",
"theme_setting_system_primary_color_title": "Použít systémovou barvu",
"theme_setting_system_theme_switch": "Automaticky (podle systemového nastavení)",
"theme_setting_system_theme_switch": "Automaticky (podle systému)",
"then": "Pak",
"they_will_be_merged_together": "Budou sloučeny dohromady",
"third_party_resources": "Zdroje třetích stran",
@@ -2157,6 +2185,8 @@
"upload_status_errors": "Chyby",
"upload_status_uploaded": "Nahráno",
"upload_success": "Nahrání proběhlo úspěšně, obnovením stránky se zobrazí nově nahrané položky.",
"upload_to_album_body": "Uživatelé, kteří nevyužívají funkci ručního nahrávání, si nyní mohou zvolit, zda chtějí lokální fotografie přidávat přímo do alba již při jejich nahrávání, již není nutné je nejprve nahrát a teprve poté přidat do alba.",
"upload_to_album_title": "Nahrát přímo do alba",
"upload_to_immich": "Nahrát do Immich ({count})",
"uploading": "Nahrávání",
"uploading_media": "Nahrávání médií",
@@ -2224,6 +2254,9 @@
"week": "Týden",
"welcome": "Vítejte",
"welcome_to_immich": "Vítejte v Immichi",
"whats_new": "Co je nového",
"whats_new_settings_subtitle": "Podívejte se, co je nového v Immichi",
"whats_new_version": "Verze {version}",
"when": "Kdy",
"width": "Šířka",
"wifi_name": "Název Wi-Fi",
+36 -4
View File
@@ -73,6 +73,7 @@
"cron_expression_description": "Indstil skannings intervallet i cron format. For mere information se: <link>Crontab Guru</link>",
"cron_expression_presets": "cron predefinerede indstillinger",
"disable_login": "Deaktiver login",
"download_csv": "Hent CSV",
"duplicate_detection_job_description": "Kør maskinlæring på mediefiler for at opdage lignende billeder. Er afhængig af Smart Søgning",
"exclusion_pattern_description": "Ekskluderingsmønstre lader dig ignorere filer og mapper, når du scanner dit bibliotek. Dette er nyttigt, hvis du har mapper, der indeholder filer, du ikke vil importere, såsom RAW-filer.",
"export_config_as_json_description": "Download den aktuelle systemkonfiguration som en JSON-fil",
@@ -182,9 +183,25 @@
"machine_learning_smart_search_enabled": "Aktiver smart søgning",
"machine_learning_smart_search_enabled_description": "Hvis deaktiveret, vil billeder ikke blive kodet til smart søgning.",
"machine_learning_url_description": "URLen for maskinlæringsserveren. Hvis mere end én URL angives, vil hver server blive forsøgt én ad gangen, indtil en svarer succesfuldt, i rækkefølge fra første til sidste. Servere, der ikke svarer, vil midlertidigt blive ignoreret, indtil de kommer online igen.",
"maintenance_backup_management": "Backup administration",
"maintenance_delete_backup": "Slet Backup",
"maintenance_delete_backup_description": "Denne fil vil blive slettet permanent.",
"maintenance_delete_error": "Sletning af backup fejlede.",
"maintenance_integrity_check": "Kontroller",
"maintenance_integrity_check_all": "Kontroller alle",
"maintenance_integrity_checksum_mismatch": "Checksum uoverensstemmelse",
"maintenance_integrity_checksum_mismatch_description": "Filer hvis checksum på disken ikke stemmer overens med den checksum, Immich har gemt i sin database.",
"maintenance_integrity_checksum_mismatch_job": "Kontroller for uoverensstemmelser i checksum",
"maintenance_integrity_checksum_mismatch_refresh_job": "Opdater rapporter om uoverensstemmelser i checksum",
"maintenance_integrity_missing_file": "Manglende filer",
"maintenance_integrity_missing_file_description": "Filer som Immich har i sin database, men som ikke findes i filsystemet.",
"maintenance_integrity_missing_file_job": "Tjek for manglende filer",
"maintenance_integrity_missing_file_refresh_job": "Opdater rapporter om manglende filer",
"maintenance_integrity_report": "Integritetsrapport",
"maintenance_integrity_untracked_file": "Ikke-registreret filer",
"maintenance_integrity_untracked_file_description": "Filer i Immichs mapper, som Immich ikke har nogen registrering af.",
"maintenance_integrity_untracked_file_job": "Tjek for ikke-registreret filer",
"maintenance_integrity_untracked_file_refresh_job": "Opdater rapporter om ikke-registreret filer",
"maintenance_restore_backup": "Genskab backup",
"maintenance_restore_backup_description": "Immich bliver slettet og genskabt fra den valgte backup. Der vil blive taget en backup før du fortsætter.",
"maintenance_restore_backup_different_version": "Denne backup blev lavet med en anden version af Immich!",
@@ -794,6 +811,8 @@
"day": "Dag",
"days": "Dage",
"deduplicate_all": "Dedubliker alle",
"default_quality_subtitle": "Kvalitet brugt ved tryk på 'del'. Tryk længe på dele-knappen for at vælge hver gang.",
"default_share_quality": "Standard delingskvalitet",
"delete": "Slet",
"delete_action_confirmation_message": "Er du sikker på, at du vil slette dette objekt? Denne handling vil flytte objektet til serverens papirkurv, og vil spørge dig, om du vil slette den lokalt",
"delete_action_prompt": "{count} slettet",
@@ -1076,6 +1095,7 @@
"failed": "Fejlet",
"failed_count": "Fejlede: {count}",
"failed_to_authenticate": "Kunne ikke godkendes",
"failed_to_delete_file": "Kunne ikke slette filen",
"failed_to_load_assets": "Kunne ikke indlæse mediefiler",
"failed_to_load_folder": "Kunne ikke indlæse mappe",
"favorite": "Favorit",
@@ -1180,6 +1200,7 @@
"individual_share": "Individuel andel",
"individual_shares": "Individuelle delinger",
"info": "Info",
"integrity_checks": "Integritetstjek",
"interval": {
"day_at_onepm": "Hver dag kl. 13",
"hours": "Hver {hours, plural, one {time} other {{hours, number} timer}}",
@@ -1241,6 +1262,7 @@
"linked_oauth_account": "Tilsluttet OAuth-konto",
"list": "Liste",
"live": "Live",
"load_more": "Indlæs flere",
"loading": "Indlæser",
"loading_search_results_failed": "Indlæsning af søgeresultater fejlede",
"local": "Lokal",
@@ -1281,9 +1303,9 @@
"login_form_failed_login": "Der opstod en vejl ved at logge ind. Tjek server webadressen, e-mailen og kodeordet",
"login_form_handshake_exception": "Der opstod en fejl med at oprette forbindelse til serveren. Aktiver selvsignerede certifikater i indstillingerne, hvis du bruger et selv signeret certifikat.",
"login_form_password_hint": "kodeord",
"login_form_server_empty": "Indtast server-URL.",
"login_form_server_error": "Kunne ikke forbinde til serveren.",
"login_has_been_disabled": "Login er blevet deaktiveret.",
"login_form_server_empty": "Indtast server-URL",
"login_form_server_error": "Kunne ikke forbinde til serveren",
"login_has_been_disabled": "Login er blevet deaktiveret",
"login_password_changed_error": "Der opstod en fejl i opdateringen af dit kodeord",
"login_password_changed_success": "Kodeordet blev opdateret",
"logout_all_device_confirmation": "Er du sikker på, at du vil logge ud af alle enheder?",
@@ -1334,6 +1356,7 @@
"map_location_picker_page_use_location": "Brug denne placering",
"map_location_service_disabled_content": "Placeringstjenesten skal aktiveres for at vise elementer fra din nuværende placering. Vil du aktivere den nu?",
"map_location_service_disabled_title": "Placeringstjenesten er deaktiveret",
"map_marker_for_image": "Kortmarkør for billede taget i {city}, {country}",
"map_marker_with_image": "Kortmarkør med billede",
"map_no_location_permission_content": "Der kræves tilladelse til placeringen for at vise elementer fra din nuværende placering. Vil du give tilladelse?",
"map_no_location_permission_title": "Placeringstilladelse blev afvist",
@@ -1484,6 +1507,9 @@
"notes": "Noter",
"nothing_here_yet": "Intet her endnu",
"notification_backup_reliability": "Aktivér notifikationer for at forbedre pålideligheden af backup i baggrunden",
"notification_enabled_list_tile_content": "Immich bruger notifikationer til baggrundsbackup. Administrer dem i dine enhedsindstillinger.",
"notification_enabled_list_tile_open_button": "Åbn indstillinger",
"notification_enabled_list_tile_title": "Notifikationer aktiveret",
"notification_permission_dialog_content": "Gå til indstillinger for at slå notifikationer til.",
"notification_permission_list_tile_content": "Tillad at bruge notifikationer.",
"notification_permission_list_tile_enable_button": "Slå notifikationer til",
@@ -1826,6 +1852,7 @@
"select_person": "Vælg person",
"select_person_to_tag": "Vælg en person at tagge",
"select_photos": "Vælg billeder",
"select_quality": "Vælg kvalitet",
"select_trash_all": "Vælg smid alle ud",
"selected": "Valgt",
"selected_count": "{count, plural, one {# valgt} other {# valgte}}",
@@ -1875,6 +1902,8 @@
"share": "Del",
"share_dialog_preparing": "Forbereder...",
"share_link": "Del link",
"share_original": "Brug original (stor)",
"share_preview": "Brug miniaturebillede (lille)",
"shared": "Delt",
"shared_album_activities_input_disable": "Kommentarer er deaktiveret",
"shared_album_activity_remove_content": "Vil du slette denne aktivitet?",
@@ -1967,6 +1996,7 @@
"slideshow_repeat_description": "Hop tilbage til begyndelsen når diasshow stopper",
"slideshow_settings": "Diasshowindstillinger",
"smart_album": "Smart album",
"some_assets_already_have_a_location_warning": "Nogle af de valgte aktiver har allerede en placering",
"sort_albums_by": "Sortér albummer efter...",
"sort_created": "Dato oprettet",
"sort_items": "Antal genstande",
@@ -2071,10 +2101,12 @@
"trash_page_info": "Slettede elementer vil blive slettet permanent efter {days} dage",
"trashed_items_will_be_permanently_deleted_after": "Mediefiler i papirkurven vil blive slettet permanent efter {days, plural, one {# dag} other {# dage}}.",
"trigger": "Udløser",
"trigger_asset_metadata_extraction": "Udtrækning af metadata for mediefil",
"trigger_asset_metadata_extraction_description": "Udløses, når EXIF-metadataene for en mediefil udtrækkes",
"trigger_asset_uploaded": "Mediefil upload",
"trigger_asset_uploaded_description": "Udløses, når et nyt asset bliver uploaded",
"trigger_person_recognized": "Peron genkendt",
"trigger_person_recognized_description": "Udløses, når en person er detekteret",
"trigger_person_recognized_description": "Udløses, når en person er genkendt",
"troubleshoot": "Fejlfinding",
"type": "Type",
"unable_to_change_pin_code": "Kunne ikke ændre PIN kode",
+33
View File
@@ -108,6 +108,21 @@
"image_thumbnail_quality_description": "Thumbnail quality from 1-100. Higher is better, but produces larger files and can reduce app responsiveness.",
"image_thumbnail_title": "Thumbnail Settings",
"import_config_from_json_description": "Import system config by uploading a JSON config file",
"integrity_checks_checksum_files": "Checksum files",
"integrity_checks_checksum_files_description": "Configure the checksum check",
"integrity_checks_checksum_files_enable_description": "Enable the checksum check",
"integrity_checks_checksum_files_percentage_limit": "Percentage limit",
"integrity_checks_checksum_files_percentage_limit_description": "Configure the maximum percentage between 0.01 and 1 for how much the checksum check should run each interval.",
"integrity_checks_checksum_files_time_limit": "Time limit",
"integrity_checks_checksum_files_time_limit_description": "Configure the maximum duration for which the checksum check should run each interval. (ms)",
"integrity_checks_missing_files": "Missing files",
"integrity_checks_missing_files_description": "Configure the frequency and enable or disable the missing files check",
"integrity_checks_missing_files_enable_description": "Enable the missing files check",
"integrity_checks_settings": "Integrity checks",
"integrity_checks_settings_description": "Manage integrity checks intervals",
"integrity_checks_untracked_files": "Untracked files",
"integrity_checks_untracked_files_description": "Configure the frequency and enable or disable the untracked files check",
"integrity_checks_untracked_files_enable_description": "Enable the untracked files check",
"job_concurrency": "{job} concurrency",
"job_created": "Job created",
"job_not_concurrency_safe": "This job is not concurrency-safe.",
@@ -1461,6 +1476,7 @@
"never": "Never",
"new_album": "New Album",
"new_api_key": "New API Key",
"new_feature": "New Feature",
"new_password": "New password",
"new_person": "New person",
"new_pin_code": "New PIN code",
@@ -1521,6 +1537,8 @@
"obtainium_configurator": "Obtainium Configurator",
"obtainium_configurator_instructions": "Use Obtainium to install and update the Android app directly from Immich GitHub's release. Create an API key and select a variant to create your Obtainium configuration link",
"ocr": "OCR",
"ocr_body": "Immich now reads the text inside your photos, so you can search for them by what they say.",
"ocr_title": "Search text in your photos",
"official_immich_resources": "Official Immich Resources",
"offline": "Offline",
"offset": "Offset",
@@ -1539,6 +1557,8 @@
"open": "Open",
"open_calendar": "Open calendar",
"open_in_browser": "Open in browser",
"open_in_immich_body": "Set Immich as your gallery on Android to open photos straight from other apps.",
"open_in_immich_title": "Open photos in Immich",
"open_in_map_view": "Open in map view",
"open_in_openstreetmap": "Open in OpenStreetMap",
"open_the_search_filters": "Open the search filters",
@@ -1697,7 +1717,10 @@
"recent": "Recent",
"recent_searches": "Recent searches",
"recently_added": "Recently added",
"recently_added_body": "Jump straight to everything you've added lately on a dedicated page.",
"recently_added_description": "Browse your assets sorted by when they were uploaded to Immich",
"recently_added_page_title": "Recently Added",
"recently_added_title": "Recently added",
"recently_taken": "Recently taken",
"refresh": "Refresh",
"refresh_encoded_videos": "Refresh encoded videos",
@@ -1904,6 +1927,8 @@
"share_link": "Share Link",
"share_original": "Use original (large)",
"share_preview": "Use thumbnail (small)",
"share_quality_body": "Press and hold the share button to choose the image quality before you share.",
"share_quality_title": "Choose your share quality",
"shared": "Shared",
"shared_album_activities_input_disable": "Comment is disabled",
"shared_album_activity_remove_content": "Do you want to delete this activity?",
@@ -1985,16 +2010,19 @@
"sign_out": "Sign Out",
"sign_up": "Sign up",
"size": "Size",
"skip": "Skip",
"skip_to_content": "Skip to content",
"skip_to_folders": "Skip to folders",
"skip_to_tags": "Skip to tags",
"slideshow": "Slideshow",
"slideshow_body": "Sit back and watch your photos play in a full-screen slideshow.",
"slideshow_metadata_overlay_mode": "Overlay content",
"slideshow_metadata_overlay_mode_description_only": "Description only",
"slideshow_metadata_overlay_mode_full": "Full",
"slideshow_repeat": "Repeat slideshow",
"slideshow_repeat_description": "Loop back to beginning when slideshow ends",
"slideshow_settings": "Slideshow settings",
"slideshow_title": "Slideshow",
"smart_album": "Smart album",
"some_assets_already_have_a_location_warning": "Some of the selected assets already have a location",
"sort_albums_by": "Sort albums by...",
@@ -2157,6 +2185,8 @@
"upload_status_errors": "Errors",
"upload_status_uploaded": "Uploaded",
"upload_success": "Upload success, refresh the page to see new upload assets.",
"upload_to_album_body": "For users that don't utilize the manual upload feature, you can now choose to add local photos directly into an album as you upload them, no need to upload then add to an album later anymore.",
"upload_to_album_title": "Upload straight to an album",
"upload_to_immich": "Upload to Immich ({count})",
"uploading": "Uploading",
"uploading_media": "Uploading media",
@@ -2224,6 +2254,9 @@
"week": "Week",
"welcome": "Welcome",
"welcome_to_immich": "Welcome to Immich",
"whats_new": "What's new",
"whats_new_settings_subtitle": "See what's new in Immich",
"whats_new_version": "Version {version}",
"when": "When",
"width": "Width",
"wifi_name": "Wi-Fi Name",
+6 -3
View File
@@ -1303,9 +1303,9 @@
"login_form_failed_login": "Error al iniciar sesión, comprueba la URL del servidor, el correo electrónico y la contraseña",
"login_form_handshake_exception": "Hubo una excepción de handshake con el servidor. Activa la compatibilidad con certificados autofirmados en la configuración si estás utilizando un certificado autofirmado.",
"login_form_password_hint": "contraseña",
"login_form_server_empty": "Introduce la URL del servidor.",
"login_form_server_error": "No se pudo conectar al servidor.",
"login_has_been_disabled": "El inicio de sesión ha sido deshabilitado.",
"login_form_server_empty": "Introduce la URL del servidor",
"login_form_server_error": "No se pudo conectar al servidor",
"login_has_been_disabled": "El inicio de sesión ha sido deshabilitado",
"login_password_changed_error": "Hubo un error actualizando la contraseña",
"login_password_changed_success": "Contraseña cambiado con éxito",
"logout_all_device_confirmation": "¿Estás seguro de que quieres cerrar sesión en todos los dispositivos?",
@@ -1507,6 +1507,9 @@
"notes": "Notas",
"nothing_here_yet": "Sin nada aún",
"notification_backup_reliability": "Activa las notificaciones para mejorar el funcionamiento de las copias de seguridad en segundo plano",
"notification_enabled_list_tile_content": "Immich usa notificaciones para las copias de seguridad en segundo plano. Gestiónelas en los ajustes de su dispositivo.",
"notification_enabled_list_tile_open_button": "Abrir ajustes",
"notification_enabled_list_tile_title": "Notificaciones activadas",
"notification_permission_dialog_content": "Para activar las notificaciones, ve a Configuración y selecciona permitir.",
"notification_permission_list_tile_content": "Concede permiso para habilitar las notificaciones.",
"notification_permission_list_tile_enable_button": "Permitir notificaciones",
+28
View File
@@ -108,6 +108,18 @@
"image_thumbnail_quality_description": "Pisipildi kvaliteet vahemikus 1-100. Kõrgem väärtus on parem, aga tekitab suuremaid faile ning võib mõjutada rakenduse töökiirust.",
"image_thumbnail_title": "Pisipildi seaded",
"import_config_from_json_description": "Impordi süsteemi seadistus JSON-faili üleslaadimise teel",
"integrity_checks_checksum_files_description": "Seadista kontrollsumma kontroll",
"integrity_checks_checksum_files_enable_description": "Luba kontrollsumma kontroll",
"integrity_checks_checksum_files_percentage_limit": "Protsendi piirang",
"integrity_checks_checksum_files_percentage_limit_description": "Seadista maksimaalne protsent 0.01 ja 1 vahel, kui palju peaks kontrollsumma kontroll iga intervalli jooksul käivituma.",
"integrity_checks_checksum_files_time_limit": "Ajaline piirang",
"integrity_checks_checksum_files_time_limit_description": "Seadista maksimaalne kestus, kui kaua peaks kontrollsumma kontroll iga intervalli jooksul käivituma. (ms)",
"integrity_checks_missing_files": "Puuduvad failid",
"integrity_checks_missing_files_description": "Luba või keela puuduvate failide kontroll ning seadista selle sagedus",
"integrity_checks_missing_files_enable_description": "Luba puuduvate failide kontroll",
"integrity_checks_untracked_files": "Mittejälgitud failid",
"integrity_checks_untracked_files_description": "Luba või keela mittejälgitud failide kontroll ning seadista selle sagedus",
"integrity_checks_untracked_files_enable_description": "Luba mittejälgitud failide kontroll",
"job_concurrency": "{job} samaaegsus",
"job_created": "Tööde lisatud",
"job_not_concurrency_safe": "Seda töödet pole ohutu samaaegselt käivitada.",
@@ -1453,6 +1465,7 @@
"never": "Mitte kunagi",
"new_album": "Uus album",
"new_api_key": "Uus API võti",
"new_feature": "Uus funktsionaalsus",
"new_password": "Uus parool",
"new_person": "Uus isik",
"new_pin_code": "Uus PIN-kood",
@@ -1513,6 +1526,8 @@
"obtainium_configurator": "Obtainiumi seadistamine",
"obtainium_configurator_instructions": "Androidi rakenduse otse GitHub'ist paigaldamiseks ja uuendamiseks kasuta Obtainiumit. Seadistamise lingi loomiseks lisa API võti ja vali rakenduse variant",
"ocr": "OCR",
"ocr_body": "Immich loeb nüüd teksti sinu fotodel, nii et saad selle järgi neid otsida.",
"ocr_title": "Otsi teksti oma fotodel",
"official_immich_resources": "Ametlikud Immich'i ressursid",
"offline": "Ühendus puudub",
"offset": "Nihe",
@@ -1531,6 +1546,8 @@
"open": "Ava",
"open_calendar": "Ava kalender",
"open_in_browser": "Ava brauseris",
"open_in_immich_body": "Sea Immich oma Androidi galeriirakenduseks, et fotosid otse teistest rakendustest avada.",
"open_in_immich_title": "Ava fotod Immich'is",
"open_in_map_view": "Ava kaardi vaates",
"open_in_openstreetmap": "Ava OpenStreetMap",
"open_the_search_filters": "Ava otsingufiltrid",
@@ -1688,7 +1705,9 @@
"recent": "Hiljutine",
"recent_searches": "Hiljutised otsingud",
"recently_added": "Hiljuti lisatud",
"recently_added_description": "Sirvi oma üksuseid sorteerituna Immich'isse üleslaadimise järgi",
"recently_added_page_title": "Hiljuti lisatud",
"recently_added_title": "Hiljuti lisatud",
"recently_taken": "Hiljuti tehtud",
"refresh": "Värskenda",
"refresh_encoded_videos": "Värskenda kodeeritud videod",
@@ -1895,6 +1914,8 @@
"share_link": "Jaga linki",
"share_original": "Kasuta algset (suur)",
"share_preview": "Kasuta pisipilti (väike)",
"share_quality_body": "Vajuta ja hoia jagamise nuppu, et valida enne jagamist pildifaili kvaliteet.",
"share_quality_title": "Vali jagamise kvaliteet",
"shared": "Jagatud",
"shared_album_activities_input_disable": "Kommentaarid on keelatud",
"shared_album_activity_remove_content": "Kas soovid selle tegevuse kustutada?",
@@ -1976,6 +1997,7 @@
"sign_out": "Logi välja",
"sign_up": "Registreeru",
"size": "Suurus",
"skip": "Jäta vahele",
"skip_to_content": "Sisu juurde",
"skip_to_folders": "Kaustade juurde",
"skip_to_tags": "Siltide juurde",
@@ -1986,6 +2008,7 @@
"slideshow_repeat": "Korda slaidiesitlust",
"slideshow_repeat_description": "Mine slaidiesitluse lõppedes tagasi algusesse",
"slideshow_settings": "Slaidiesitluse seaded",
"slideshow_title": "Slaidiesitlus",
"smart_album": "Nutikas album",
"some_assets_already_have_a_location_warning": "Osadel valitud üksustest on juba asukoht määratud",
"sort_albums_by": "Järjesta albumid...",
@@ -2148,6 +2171,8 @@
"upload_status_errors": "Vead",
"upload_status_uploaded": "Üleslaaditud",
"upload_success": "Üleslaadimine õnnestus, uute üksuste nägemiseks värskenda lehte.",
"upload_to_album_body": "Kui sa ei kasuta käsitsi üleslaadimise funktsionaalsust, saad nüüd fotode üleslaadimisel need otse albumisse lisada, pole vaja kõigepealt üles laadida ja seejärel albumisse lisada.",
"upload_to_album_title": "Laadi üles otse albumisse",
"upload_to_immich": "Laadi Immich'isse ({count})",
"uploading": "Üleslaadimine",
"uploading_media": "Üksuste üleslaadimine",
@@ -2215,6 +2240,9 @@
"week": "Nädal",
"welcome": "Tere tulemast",
"welcome_to_immich": "Tere tulemast Immich'isse",
"whats_new": "Mis on uut",
"whats_new_settings_subtitle": "Vaata, mis on Immich'is uut",
"whats_new_version": "Versioon {version}",
"width": "Laius",
"wifi_name": "WiFi-võrgu nimi",
"workflow": "Töövoog",
+33 -1
View File
@@ -108,6 +108,21 @@
"image_thumbnail_quality_description": "Qualité des miniatures : de 1 à 100. Une valeur élevée produit de meilleurs résultats, mais elle produit des fichiers plus volumineux et peut réduire la réactivité de l'application.",
"image_thumbnail_title": "Paramètres des miniatures",
"import_config_from_json_description": "Importer la configuration système en envoyant un fichier de configuration JSON",
"integrity_checks_checksum_files": "Vérifier l'intégrité des fichiers",
"integrity_checks_checksum_files_description": "Configurer le test d'intégrité",
"integrity_checks_checksum_files_enable_description": "Active le test d'intégrité des fichiers (checksum)",
"integrity_checks_checksum_files_percentage_limit": "Limite de pourcentage",
"integrity_checks_checksum_files_percentage_limit_description": "Configurez le pourcentage maximal du test d'intégrité qui doit être effectué à chaque interval, entre 0.01 et 1.",
"integrity_checks_checksum_files_time_limit": "Limite de temps",
"integrity_checks_checksum_files_time_limit_description": "Configurez la durée maximale en millisecondes entre deux vérifications.",
"integrity_checks_missing_files": "Fichiers manquants",
"integrity_checks_missing_files_description": "Configurez la fréquence et activez/désactivez la vérification des fichiers manquants",
"integrity_checks_missing_files_enable_description": "Activez la vérification des fichiers manquants",
"integrity_checks_settings": "Tests d'intégrité",
"integrity_checks_settings_description": "Gérez l'interval des tests d'intégrité",
"integrity_checks_untracked_files": "Fichiers non suivis",
"integrity_checks_untracked_files_description": "Configurez la fréquence et activez/désactivez la vérification des fichiers non suivis",
"integrity_checks_untracked_files_enable_description": "Activez la vérification des fichiers non suivis",
"job_concurrency": "{job}: nombre de tâches simultanées",
"job_created": "Tâche créée",
"job_not_concurrency_safe": "Cette tâche ne peut pas être exécutée en multitâche de façon sûre.",
@@ -171,7 +186,7 @@
"machine_learning_ocr_max_resolution": "Résolution maximale",
"machine_learning_ocr_max_resolution_description": "Les prévisualisations au-dessus de cette résolution seront retaillées en conservant leur ratio. Des valeurs plus grandes sont plus précises, mais sont plus lentes et utilisent plus de mémoire.",
"machine_learning_ocr_min_detection_score": "Score minimum de détection",
"machine_learning_ocr_min_detection_score_description": "Score de confiance minimum pour la détection du textew entre 0 et 1. Des valeurs faibles permettront de reconnaître davantage de texte mais peuvent entraîner des faux positifs.",
"machine_learning_ocr_min_detection_score_description": "Score de confiance minimum pour la détection du texte, entre 0 et 1. Des valeurs faibles permettront de reconnaître davantage de texte mais peuvent entraîner des faux positifs.",
"machine_learning_ocr_min_recognition_score": "Score de reconnaissance minimum",
"machine_learning_ocr_min_score_recognition_description": "Score de confiance minimum pour la reconnaissance du texte, entre 0 et 1. Des valeurs faible permettront de reconnaître davantage de texte, mais peuvent entraîner des faux positifs.",
"machine_learning_ocr_model": "Modèle de Reconnaissance Optique de Caractères OCR",
@@ -1461,6 +1476,7 @@
"never": "Jamais",
"new_album": "Nouvel Album",
"new_api_key": "Nouvelle clé API",
"new_feature": "Nouvelle fonctionnalité",
"new_password": "Nouveau mot de passe",
"new_person": "Nouvelle personne",
"new_pin_code": "Nouveau code PIN",
@@ -1521,6 +1537,8 @@
"obtainium_configurator": "Configuration pour Obtainium",
"obtainium_configurator_instructions": "Utilisez Obtainium pour installer et mettre à jour l'application Android directement depuis la version d'Immich sur Github. Créez une clé d'API et sélectionnez une variante pour créer votre lien de configuration pour Obtainium",
"ocr": "Reconnaissance Optique de Caractères OCR",
"ocr_body": "Immich peut désormais lire le texte dans vos photos, pour que vous puissiez les chercher dans votre galerie par ce qu'elles disent.",
"ocr_title": "Cherchez le texte dans vos photos",
"official_immich_resources": "Ressources Immich officielles",
"offline": "Hors ligne",
"offset": "Décalage",
@@ -1539,6 +1557,8 @@
"open": "Ouvrir",
"open_calendar": "Ouvrir le calendrier",
"open_in_browser": "Ouvrir dans le navigateur",
"open_in_immich_body": "Définissez Immich comme vote application de gallerie par défaut sur Android pour ouvrir vos photos directement depuis les autres applications.",
"open_in_immich_title": "Ouvrir les photos dans Immich",
"open_in_map_view": "Montrer sur la carte",
"open_in_openstreetmap": "Ouvrir dans OpenStreetMap",
"open_the_search_filters": "Ouvrir les filtres de recherche",
@@ -1697,7 +1717,9 @@
"recent": "Récent",
"recent_searches": "Recherches récentes",
"recently_added": "Récemment ajouté",
"recently_added_body": "Allez directement à ce que vous avez ajouté récemment dans une page dédiée.",
"recently_added_page_title": "Récemment ajouté",
"recently_added_title": "Ajouté récemment",
"recently_taken": "Récemment photographié",
"refresh": "Actualiser",
"refresh_encoded_videos": "Actualiser les vidéos encodées",
@@ -1904,6 +1926,8 @@
"share_link": "Partager le lien",
"share_original": "Utiliser l'originale (grand)",
"share_preview": "Utiliser la miniature (petite)",
"share_quality_body": "Appuyez longtemps sur le bouton partager pour choisir la qualité d'image avant de partager.",
"share_quality_title": "Choisissez votre qualité d'envoi",
"shared": "Partagé",
"shared_album_activities_input_disable": "Les commentaires sont désactivés",
"shared_album_activity_remove_content": "Souhaitez-vous supprimer cette activité ?",
@@ -1985,16 +2009,19 @@
"sign_out": "Déconnexion",
"sign_up": "S'enregistrer",
"size": "Taille",
"skip": "Passer",
"skip_to_content": "Passer",
"skip_to_folders": "Passer vers les dossiers",
"skip_to_tags": "Passer vers les étiquettes",
"slideshow": "Diaporama",
"slideshow_body": "Relaxez-vous et regardez vos photos dans un diaporama en plein écran.",
"slideshow_metadata_overlay_mode": "Contenu en superposition",
"slideshow_metadata_overlay_mode_description_only": "Seulement la description",
"slideshow_metadata_overlay_mode_full": "Tout",
"slideshow_repeat": "Répéter le diaporama",
"slideshow_repeat_description": "Reboucler au début lorsque le diaporama se termine",
"slideshow_settings": "Paramètres du diaporama",
"slideshow_title": "Diaporama",
"smart_album": "Album intelligent",
"some_assets_already_have_a_location_warning": "Certains médias sélectionnés ont déjà une localisation",
"sort_albums_by": "Trier les albums par...",
@@ -2157,6 +2184,8 @@
"upload_status_errors": "Erreurs",
"upload_status_uploaded": "Envoyé",
"upload_success": "Envoi réussi. Rafraîchissez la page pour voir les nouveaux médias envoyés.",
"upload_to_album_body": "Pour les utilisateurs qui n'utilise pas la fonctionnalité de mise en ligne manuelle, vous pouvez maintenant ajouter vos photos locales directement dans un album, plus besoin de mettre en ligne puis de déplacer dans un album après.",
"upload_to_album_title": "Mettre en ligne directement dans un album",
"upload_to_immich": "Envoyer vers Immich ({count})",
"uploading": "Envoi",
"uploading_media": "Envoi du média",
@@ -2224,6 +2253,9 @@
"week": "Semaine",
"welcome": "Bienvenue",
"welcome_to_immich": "Bienvenue sur Immich",
"whats_new": "Quoi de neuf",
"whats_new_settings_subtitle": "Découvrez les nouveautés dans Immich",
"whats_new_version": "Version {version}",
"when": "Quand",
"width": "Largeur",
"wifi_name": "Nom du réseau wifi",
+7 -3
View File
@@ -1303,9 +1303,9 @@
"login_form_failed_login": "Earráid ag logáil isteach, seiceáil URL an fhreastalaí, an ríomhphost agus an focal faire",
"login_form_handshake_exception": "Bhí Eisceacht Lámh-Chroith leis an bhfreastalaí. Cumasaigh tacaíocht do theastas féinshínithe sna socruithe má tá teastas féinshínithe in úsáid agat.",
"login_form_password_hint": "pasfhocal",
"login_form_server_empty": "Cuir isteach URL freastalaí.",
"login_form_server_error": "Níorbh fhéidir ceangal leis an bhfreastalaí.",
"login_has_been_disabled": "Tá logáil isteach díchumasaithe.",
"login_form_server_empty": "Cuir isteach URL freastalaí",
"login_form_server_error": "Níorbh fhéidir ceangal leis an bhfreastalaí",
"login_has_been_disabled": "Tá logáil isteach díchumasaithe",
"login_password_changed_error": "Tharla earráid agus do phasfhocal á nuashonrú",
"login_password_changed_success": "Nuashonraíodh an focal faire go rathúil",
"logout_all_device_confirmation": "An bhfuil tú cinnte gur mian leat logáil amach as gach gléas?",
@@ -1356,6 +1356,7 @@
"map_location_picker_page_use_location": "Úsáid an suíomh seo",
"map_location_service_disabled_content": "Ní mór seirbhís suímh a chumasú chun sócmhainní ó do shuíomh reatha a thaispeáint. Ar mhaith leat é a chumasú anois?",
"map_location_service_disabled_title": "Seirbhís Suímh díchumasaithe",
"map_marker_for_image": "Marcóir léarscáile don íomhá a tógadh i {city}, {country}",
"map_marker_with_image": "Marcóir léarscáile le híomhá",
"map_no_location_permission_content": "Tá cead suímh ag teastáil chun sócmhainní a thaispeáint ó do shuíomh reatha. Ar mhaith leat é a cheadú anois?",
"map_no_location_permission_title": "Cead Suímh diúltaithe",
@@ -1506,6 +1507,9 @@
"notes": "Nótaí",
"nothing_here_yet": "Níl aon rud anseo fós",
"notification_backup_reliability": "Cumasaigh fógraí chun iontaofacht cúltaca cúlra a fheabhsú",
"notification_enabled_list_tile_content": "Úsáideann Immich fógraí le haghaidh cúltaca cúlra. Bainistigh iad i socruithe do ghléis.",
"notification_enabled_list_tile_open_button": "Oscail socruithe",
"notification_enabled_list_tile_title": "Fógraí cumasaithe",
"notification_permission_dialog_content": "Chun fógraí a chumasú, téigh go Socruithe agus roghnaigh ceadaigh.",
"notification_permission_list_tile_content": "Tabhair cead fógraí a chumasú.",
"notification_permission_list_tile_enable_button": "Cumasaigh Fógraí",
+40 -3
View File
@@ -108,6 +108,21 @@
"image_thumbnail_quality_description": "Calidade da miniatura de 1 a 100. Canto máis alto, mellor, pero produce ficheiros máis grandes e pode reducir a capacidade de resposta da aplicación.",
"image_thumbnail_title": "Configuración da miniatura",
"import_config_from_json_description": "Importar a configuración do sistema subindo un arquivo de configuración JSON",
"integrity_checks_checksum_files": "Ficheiros de suma de verificación",
"integrity_checks_checksum_files_description": "Configurar a comprobación da suma de comprobación",
"integrity_checks_checksum_files_enable_description": "Activar a comprobación da suma de verificación",
"integrity_checks_checksum_files_percentage_limit": "Límite porcentual",
"integrity_checks_checksum_files_percentage_limit_description": "Configura a porcentaxe máxima entre 0,01 e 1 para a duración da comprobación da suma de verificación en cada intervalo.",
"integrity_checks_checksum_files_time_limit": "Límite de tempo",
"integrity_checks_checksum_files_time_limit_description": "Configura a duración máxima durante a que se debe executar a comprobación da suma de comprobación en cada intervalo. (ms)",
"integrity_checks_missing_files": "Arquivos que faltan",
"integrity_checks_missing_files_description": "Configura a frecuencia e activa ou desactiva a comprobación de arquivos que faltan",
"integrity_checks_missing_files_enable_description": "Activar a comprobación de arquivos que faltan",
"integrity_checks_settings": "Comprobacións de integridade",
"integrity_checks_settings_description": "Xestionar intervalos de comprobacións de integridade",
"integrity_checks_untracked_files": "Arquivos sen rastrexar",
"integrity_checks_untracked_files_description": "Configura a frecuencia e activa ou desactiva a comprobación de arquivos sen rastrexar",
"integrity_checks_untracked_files_enable_description": "Activar a comprobación de arquivos sen rastrexar",
"job_concurrency": "concorrencia de {job}",
"job_created": "Traballo creado",
"job_not_concurrency_safe": "Este traballo non é seguro para execución concorrente.",
@@ -1303,9 +1318,9 @@
"login_form_failed_login": "Erro ao iniciar sesión, comprobe a URL do servidor, correo electrónico e contrasinal",
"login_form_handshake_exception": "Houbo unha Excepción de Handshake co servidor. Active o soporte para certificados autofirmados nas configuracións se está a usar un certificado autofirmado.",
"login_form_password_hint": "contrasinal",
"login_form_server_empty": "Introduza unha URL do servidor.",
"login_form_server_error": "Non se puido conectar co servidor.",
"login_has_been_disabled": "O inicio de sesión foi desactivado.",
"login_form_server_empty": "Introduza unha URL do servidor",
"login_form_server_error": "Non se puido conectar co servidor",
"login_has_been_disabled": "O inicio de sesión foi desactivado",
"login_password_changed_error": "Houbo un erro ao actualizar o seu contrasinal",
"login_password_changed_success": "Contrasinal actualizado correctamente",
"logout_all_device_confirmation": "Está seguro de que quere pechar sesión en todos os dispositivos?",
@@ -1356,6 +1371,7 @@
"map_location_picker_page_use_location": "Usar esta localización",
"map_location_service_disabled_content": "O servizo de localización debe estar activado para mostrar activos da súa localización actual. Quere activalo agora?",
"map_location_service_disabled_title": "Servizo de localización deshabilitado",
"map_marker_for_image": "Marcador do mapa para a imaxe tirada en {city}, {country}",
"map_marker_with_image": "Marcador de mapa con imaxe",
"map_no_location_permission_content": "Necesítase permiso de localización para mostrar activos da súa localización actual. Quere permitilo agora?",
"map_no_location_permission_title": "Permiso de localización denegado",
@@ -1460,6 +1476,7 @@
"never": "Nunca",
"new_album": "Novo Álbum",
"new_api_key": "Nova Chave API",
"new_feature": "Nova función",
"new_password": "Novo contrasinal",
"new_person": "Nova persoa",
"new_pin_code": "Novo código PIN",
@@ -1506,6 +1523,9 @@
"notes": "Notas",
"nothing_here_yet": "Aínda nada por aquí",
"notification_backup_reliability": "Activa as notificacións para mellorar a fiabilidade da copia de seguridade en segundo plano",
"notification_enabled_list_tile_content": "Immich emprega notificacións para copias de seguridade en segundo plano. Xestióneas nos axustes do seu dispositivo.",
"notification_enabled_list_tile_open_button": "Abrir axustes",
"notification_enabled_list_tile_title": "Notificacións activadas",
"notification_permission_dialog_content": "Para activar as notificacións, vaia a Axustes e seleccione permitir.",
"notification_permission_list_tile_content": "Conceda permiso para activar as notificacións.",
"notification_permission_list_tile_enable_button": "Activar Notificacións",
@@ -1517,6 +1537,8 @@
"obtainium_configurator": "Configurador de Obtainium",
"obtainium_configurator_instructions": "Emprega Obtainium para instalar e actualizar a aplicación de Android directamente desde o lanzamento do GitHub de Immich. Crea unha chave API e selecciona unha variante para xerar o teu enlace de configuración de Obtainium",
"ocr": "OCR",
"ocr_body": "Agora Immich le o texto que hai dentro das túas fotos, para que poidas buscalas polo que din.",
"ocr_title": "Buscar texto nas túas fotos",
"official_immich_resources": "Recursos Oficiais de Immich",
"offline": "Fóra de liña",
"offset": "Desprazamento",
@@ -1535,6 +1557,8 @@
"open": "Abrir",
"open_calendar": "Abrir calendario",
"open_in_browser": "Abrir no navegador",
"open_in_immich_body": "Configura Immich como a túa galería en Android para abrir fotos directamente doutras aplicacións.",
"open_in_immich_title": "Abrir fotos en Immich",
"open_in_map_view": "Abrir na vista de mapa",
"open_in_openstreetmap": "Abrir en OpenStreetMap",
"open_the_search_filters": "Abrir os filtros de busca",
@@ -1693,7 +1717,10 @@
"recent": "Recente",
"recent_searches": "Buscas recentes",
"recently_added": "Engadido recentemente",
"recently_added_body": "Vaia directamente a todo o que engadiu ultimamente nunha páxina específica.",
"recently_added_description": "Explora os teus recursos ordenados por cando foron cargados en Immich",
"recently_added_page_title": "Engadido Recentemente",
"recently_added_title": "Engadida recentemente",
"recently_taken": "Tomado recentemente",
"refresh": "Actualizar",
"refresh_encoded_videos": "Actualizar vídeos codificados",
@@ -1900,6 +1927,8 @@
"share_link": "Ligazón para Compartir",
"share_original": "Utilizar orixinal (grande)",
"share_preview": "Utilizar miniatura (pequena)",
"share_quality_body": "Mantén premido o botón de compartir para escoller a calidade da imaxe antes de compartila.",
"share_quality_title": "Escolle a calidade da compartición",
"shared": "Compartido",
"shared_album_activities_input_disable": "O comentario está desactivado",
"shared_album_activity_remove_content": "Quere eliminar esta actividade?",
@@ -1981,16 +2010,19 @@
"sign_out": "Pechar Sesión",
"sign_up": "Rexistrarse",
"size": "Tamaño",
"skip": "Saltar",
"skip_to_content": "Saltar ao contido",
"skip_to_folders": "Saltar a cartafoles",
"skip_to_tags": "Saltar a etiquetas",
"slideshow": "Presentación",
"slideshow_body": "Reláxate e mira as túas fotos reproducíndose nunha presentación de diapositivas a pantalla completa.",
"slideshow_metadata_overlay_mode": "Contido superposto",
"slideshow_metadata_overlay_mode_description_only": "Só descrición",
"slideshow_metadata_overlay_mode_full": "Cheo",
"slideshow_repeat": "Repetir presentación de diapositivas",
"slideshow_repeat_description": "Volver ao principio ao rematar a presentación de diapositivas",
"slideshow_settings": "Configuración da presentación",
"slideshow_title": "Presentación de diapositivas",
"smart_album": "Álbume intelixente",
"some_assets_already_have_a_location_warning": "Algúns dos recursos seleccionados xa teñen unha localización",
"sort_albums_by": "Ordenar álbums por...",
@@ -2153,6 +2185,8 @@
"upload_status_errors": "Erros",
"upload_status_uploaded": "Subido",
"upload_success": "Subida exitosa. Actualice a páxina para ver os novos activos subidos.",
"upload_to_album_body": "Para os usuarios que non empreguen a función de carga manual, agora poden optar por engadir fotos locais directamente a un álbum mentres as cargan, sen necesidade de cargalas e engadlas a un álbum máis tarde.",
"upload_to_album_title": "Subir directamente a un álbum",
"upload_to_immich": "Subir a Immich ({count})",
"uploading": "Subindo",
"uploading_media": "Cargando multimedia",
@@ -2220,6 +2254,9 @@
"week": "Semana",
"welcome": "Benvido/a",
"welcome_to_immich": "Benvido/a a Immich",
"whats_new": "O que hai de novo",
"whats_new_settings_subtitle": "Vexa as novidades de Immich",
"whats_new_version": "Versión {version}",
"when": "Cando",
"width": "Ancho",
"wifi_name": "Nome da wifi",
+10 -1
View File
@@ -23,6 +23,7 @@
"add_location": "સ્થાન ઉમેરો",
"add_partner": "સાથી ઉમેરો",
"add_photos": "ફોટો ઉમેરો",
"add_step": "પગલું ઉમેરો",
"add_tag": "ટેગ ઉમેરો",
"add_to": "માં ઉમેરો",
"add_to_album": "આલ્બમમાં ઉમેરો",
@@ -72,6 +73,7 @@
"cron_expression_description": "ક્રોન ફોર્મેટનો ઉપયોગ કરીને સ્કેનિંગ ઇન્ટરવલ સેટ કરો. વધુ માહિતી માટે કૃપા કરીને <link>Crontab Guru</link> જુઓ.",
"cron_expression_presets": "ક્રોન એક્સપ્રેશન પ્રીસેટ્સ",
"disable_login": "લોગિન ડિસેબલ કરો",
"download_csv": "CSV ડાઉનલોડ કરો",
"duplicate_detection_job_description": "સરખી ઈમેજો શોધવા માટે તમારા સંસાધનો પર મશીન લર્નિંગનો ઉપયોગ કરો. આ સુવિધા સ્માર્ટ સર્ચ પર આધારિત છે",
"exclusion_pattern_description": "તમારા સંગ્રહને સ્કેન કરતી વખતે એક્સક્લુઝન પેટર્ન તમને ફાઇલો અને ફોલ્ડર્સને અવગણવામાં મદદ કરે છે. જો તમારી પાસે એવી ફાઇલો ધરાવતા ફોલ્ડર્સ હોય જેને તમે ઈમ્પોર્ટ કરવા નથી માંગતા, જેમ કે RAW ફાઇલો, તો આ સુવિધા ઉપયોગી છે.",
"export_config_as_json_description": "વર્તમાન સિસ્ટમ કોન્ફિગને JSON ફાઇલ તરીકે ડાઉનલોડ કરો",
@@ -80,6 +82,13 @@
"face_detection_description": "મશીન લર્નિંગનો ઉપયોગ કરીને સંસાધનોમાં ચહેરાની પરખ કરો. વીડિયો માટે, ફક્ત થંબનેલ જ ધ્યાનમાં લેવામાં આવે છે. \"રિફ્રેશ\" બધા સંસાધનો પર ફરીથી પ્રક્રિયા કરે છે. \"રીસેટ\" વધારામાં ચહેરાના તમામ વર્તમાન ડેટાને સાફ કરે છે. \"ખૂટતા\" તેવા સંસાધનોને કતારમાં મૂકે છે જેના પર હજુ સુધી પ્રક્રિયા કરવામાં આવી નથી. ચહેરાની પરખ પૂર્ણ થયા પછી, શોધાયેલા ચહેરાઓને 'ચહેરાની ઓળખ' માટે કતારમાં મૂકવામાં આવશે, જે તેમને હાલની અથવા નવી વ્યક્તિઓના જૂથમાં વિભાજિત કરશે.",
"facial_recognition_job_description": "શોધાયેલા ચહેરાઓને વ્યક્તિઓના જૂથમાં વિભાજિત કરો. આ પગલું 'ચહેરાની પરખ' પૂર્ણ થયા પછી શરૂ થાય છે. \"રીસેટ\" બધા ચહેરાઓનું ફરીથી જૂથીકરણ કરે છે. \"ખૂટતા\" તેવા ચહેરાઓને કતારમાં મૂકે છે જેમને હજુ સુધી કોઈ વ્યક્તિ ફાળવવામાં આવી નથી.",
"failed_job_command": "આ કાર્ય માટે આદેશ {command} નિષ્ફળ રહ્યો: {job}",
"force_delete_user_warning": "ચેતવણી: આ પ્રક્રિયા તરત જ વપરાશકર્તા અને તમામ સંસાધનોને દૂર કરી દેશે. આ નિર્ણય બદલી શકાશે નહીં અને ફાઇલોને ફરીથી મેળવી શકાશે નહીં."
"force_delete_user_warning": "ચેતવણી: આ પ્રક્રિયા તરત જ વપરાશકર્તા અને તમામ સંસાધનોને દૂર કરી દેશે. આ નિર્ણય બદલી શકાશે નહીં અને ફાઇલોને ફરીથી મેળવી શકાશે નહીં.",
"image_format": "ગોઠવણ",
"image_format_description": "WebP JPEG કરતા નાની ફાઇલો બનાવે છે, પરંતુ એન્કોડ કરવામાં ધીમી છે.",
"image_prefer_embedded_preview": "એમ્બેડેડ પૂર્વાવલોકન પસંદ કરો",
"image_prefer_wide_gamut": "પહોળા અંતરને પ્રાધાન્ય આપો",
"image_preview_quality_description": "પ્રીવ્યૂ ગુણવત્તા ૧-૧૦૦ સુધી. ઉચ્ચ વધુ સારું છે, પરંતુ તે મોટી ફાઇલો ઉત્પન્ન કરે છે અને એપ્લિકેશન પ્રતિભાવ ઘટાડી શકે છે. ઓછું મૂલ્ય સેટ કરવાથી મશીન લર્નિંગ ગુણવત્તા પર અસર પડી શકે છે.",
"image_preview_title": "પૂર્વાવલોકન સેટિંગ્સ",
"image_progressive": "પ્રગતિશીલ"
}
}
+13 -5
View File
@@ -1306,7 +1306,8 @@
"map_location_picker_page_use_location": "השתמש במיקום הזה",
"map_location_service_disabled_content": "שירות המיקום צריך להיות מופעל כדי להציג תמונות מהמיקום הנוכחי שלך. האם ברצונך להפעיל אותו עכשיו?",
"map_location_service_disabled_title": "שירות מיקום מבוטל",
"map_marker_with_image": "סמן מפה עם תמונה",
"map_marker_for_image": "מרקר מפה לתמונה ממיקום {city}, {country}",
"map_marker_with_image": "סמן מפה מתמונה",
"map_no_location_permission_content": "יש צורך בהרשאה למיקום כדי להציג תמונות מהמיקום הנוכחי שלך. האם ברצונך לאפשר זאת עכשיו?",
"map_no_location_permission_title": "הרשאה למיקום נדחתה",
"map_settings": "הגדרות מפה",
@@ -1315,9 +1316,9 @@
"map_settings_date_range_option_days": "ב-{days} ימים אחרונים",
"map_settings_date_range_option_year": "שנה אחרונה",
"map_settings_date_range_option_years": "ב-{years} שנים אחרונות",
"map_settings_include_show_archived": "כלול ארכיון",
"map_settings_include_show_partners": "כלול שותפים",
"map_settings_only_show_favorites": "הצג מועדפים בלבד",
"map_settings_include_show_archived": "הכללת ארכיון",
"map_settings_include_show_partners": "הכללת שותפים",
"map_settings_only_show_favorites": "הצגת מועדפים בלבד",
"map_settings_theme_settings": "ערכת נושא למפה",
"mark_all_as_read": "סמן הכל כנקרא",
"marked_all_as_read": "כל ההתראות סומנו כנקראו",
@@ -1327,22 +1328,29 @@
"auto": "אוטומטי",
"captions": "כתוביות",
"captions_off": "כבוי",
"closed_captions": "כתוביות מובנות",
"decode_error": "שגיאת קידוד",
"disable_captions": "ביטול כתוביות",
"enable_captions": "אפשר כתוביות",
"enter_fullscreen_mode": "הפעלת מצב מסך מלא",
"exit_fullscreen_mode": "יציאה ממסך מלא",
"loop": "לולאה",
"media_error_description": "שגיאה במדיה עצדה את הניגון. המדיה אינה תקינה או שהדפדפן אינו תומך בפורמט.",
"media_loading": "טעינת מדיה",
"mute": "השתקה",
"network_error": "שגיאת רשת",
"network_error_description": "ההורדה נכשלה בגלל שגיאת רשת.",
"not_supported_error": "מקור לא נתמך",
"playback_rate": "דירוג ניגון",
"playback_rate_current": "דירוג נוכחי",
"playback_rate_value": "דירוג {playbackRate}",
"playback_time": "זמן ניגון",
"quality": "איכות",
"second": "שניה",
"seconds": "שניות",
"time_value_of_total_time": "{currentTime} מתוך {totalTime}"
"time_value_of_total_time": "{currentTime} מתוך {totalTime}",
"time_value_remaining": "נשאר {time}",
"unmute": "ביטול השתקה"
},
"media_type": "סוג מדיה",
"memories": "זכרונות",
+2
View File
@@ -72,6 +72,7 @@
"cron_expression_description": "क्रॉन प्रारूप का उपयोग करके स्कैनिंग अंतराल सेट करें। अधिक जानकारी के लिए कृपया <link>क्रोनटैब गुरु</link> देखें",
"cron_expression_presets": "क्रॉन अभिव्यक्ति प्रीसेट",
"disable_login": "लॉगिन अक्षम करें",
"download_csv": "सीएसवी डाउनलोड करें",
"duplicate_detection_job_description": "समान छवियों का पता लगाने के लिए संपत्तियों पर मशीन लर्निंग चलाएं। यह कार्यक्षमता स्मार्ट खोज पर निर्भर करती है",
"exclusion_pattern_description": "Exclusion पैटर्न आपको अपनी लाइब्रेरी को स्कैन करते समय फ़ाइलों और फ़ोल्डरों को अनदेखा करने देता है। यह उपयोगी है यदि आपके पास ऐसे फ़ोल्डर हैं जिनमें ऐसी फ़ाइलें हैं जिन्हें आप आयात नहीं करना चाहते हैं, जैसे RAW फ़ाइलें।",
"export_config_as_json_description": "वर्तमान सिस्टम कॉन्फ़िगरेशन को JSON फ़ाइल के रूप में डाउनलोड करें",
@@ -106,6 +107,7 @@
"image_thumbnail_quality_description": "थंबनेल की गुणवत्ता 1-100 तक। उच्चतर बेहतर है, लेकिन बड़ी फ़ाइलें बनाता है और ऐप की प्रतिक्रियाशीलता को कम कर सकता है।",
"image_thumbnail_title": "थंबनेल सेटिंग्स",
"import_config_from_json_description": "JSON कॉन्फ़िगरेशन फ़ाइल अपलोड करके सिस्टम कॉन्फ़िगरेशन इंपोर्ट करें",
"integrity_checks_checksum_files_time_limit": "समय सीमा",
"job_concurrency": "{job} समरूपता",
"job_created": "नौकरी बनाई गई",
"job_not_concurrency_safe": "यह कार्य (जॉब) समवर्ती-सुरक्षित नहीं है।",
+33
View File
@@ -108,6 +108,21 @@
"image_thumbnail_quality_description": "Bélyegkép minősége 1-100 között. A magasabb szám jobb minőséget, de nagyobb fájlméretet eredményez és belassíthatja az alkalmazást.",
"image_thumbnail_title": "Bélyegkép beállítások",
"import_config_from_json_description": "Rendszer konfiguráció importálása JSON fájlból",
"integrity_checks_checksum_files": "Ellenőrzőösszeg fájlok",
"integrity_checks_checksum_files_description": "Az ellenőrzőösszeg-ellenőrzés beállítása",
"integrity_checks_checksum_files_enable_description": "Ellenőrzőösszeg-ellenőrzés bekapcsolása",
"integrity_checks_checksum_files_percentage_limit": "Százalékos limit",
"integrity_checks_checksum_files_percentage_limit_description": "Állítsd be 0,01 és 1 közötti értékre azt a maximális százalékot, amely meghatározza, hogy az ellenőrzőösszeg-ellenőrzés az egyes időközökben milyen gyakorisággal fusson.",
"integrity_checks_checksum_files_time_limit": "Idő limit",
"integrity_checks_checksum_files_time_limit_description": "Állítsd be, hogy az egyes időközökben az ellenőrzőösszeg-ellenőrzés legfeljebb mennyi ideig futhasson. (ms)",
"integrity_checks_missing_files": "Hiányzó fájlok",
"integrity_checks_missing_files_description": "Gyakoriság beállítása, valamint a hiányzó fájlok ellenőrzésének engedélyezése vagy letiltása",
"integrity_checks_missing_files_enable_description": "A hiányzó fájlok ellenőrzésének engedélyezése",
"integrity_checks_settings": "Integritás ellenőrzések",
"integrity_checks_settings_description": "Integritás-ellenőrzések gyakoriságának kezelése",
"integrity_checks_untracked_files": "Nem követett fájlok",
"integrity_checks_untracked_files_description": "Gyakoriság beállítása, valamint nem követett fájlok ellenőrzésének engedélyezése vagy letiltása",
"integrity_checks_untracked_files_enable_description": "Nem követett fájlok ellenőrzésének engedélyezése",
"job_concurrency": "{job} párhuzamosan",
"job_created": "Feladat létrehozva",
"job_not_concurrency_safe": "Ez a feladat nem párhuzamosság-biztos.",
@@ -1461,6 +1476,7 @@
"never": "Soha",
"new_album": "Új album",
"new_api_key": "Új API kulcs",
"new_feature": "Új funkció",
"new_password": "Új jelszó",
"new_person": "Új személy",
"new_pin_code": "Új PIN kód",
@@ -1521,6 +1537,8 @@
"obtainium_configurator": "Obtainium konfigurátor",
"obtainium_configurator_instructions": "Az Obtainium segítségével közvetlenül az Immich GitHub-os kiadásából telepítheted és frissítheted az Android-alkalmazást. Hozz létre egy API-kulcsot és válassz egy változatot az Obtainium konfigurációs hivatkozás elkészítéséhez",
"ocr": "OCR",
"ocr_body": "Az Immich mostantól elolvassa a fotókon szereplő szöveget, így a szöveg alapján is kereshetsz közöttük.",
"ocr_title": "Szövegkeresés a fényképeidben",
"official_immich_resources": "Hivatalos Immich források",
"offline": "Nem elérhető (offline)",
"offset": "Eltolás",
@@ -1539,6 +1557,8 @@
"open": "Nyitva",
"open_calendar": "Naptár megnyitása",
"open_in_browser": "Megnyitás böngészőben",
"open_in_immich_body": "Állítsd be az Immich-et galériádként az Androidon, hogy a fényképeket közvetlenül más alkalmazásokból nyithasd meg.",
"open_in_immich_title": "Fotók megnyitása az Immich-ben",
"open_in_map_view": "Megnyitás térkép nézetben",
"open_in_openstreetmap": "Megnyitás OpenStreetMap-ben",
"open_the_search_filters": "Keresési szűrők megnyitása",
@@ -1697,7 +1717,10 @@
"recent": "Friss",
"recent_searches": "Legutóbbi keresések",
"recently_added": "Nemrég hozzáadott",
"recently_added_body": "Egy külön oldalon azonnal megtekintheted az összes legutóbb hozzáadott elemet.",
"recently_added_description": "Az Immich-re való feltöltés időpontja szerint rendezve böngészheted a fájljaid",
"recently_added_page_title": "Nemrég hozzáadott",
"recently_added_title": "Legutóbb hozzáadott",
"recently_taken": "Nemrég készített",
"refresh": "Frissítés",
"refresh_encoded_videos": "Átkódolt videók frissítése",
@@ -1904,6 +1927,8 @@
"share_link": "Link megosztása",
"share_original": "Használd az eredetit (nagy)",
"share_preview": "Használd a miniatűrt (kicsi)",
"share_quality_body": "A megosztás előtt tartsa lenyomva a megosztás gombot, hogy kiválassza a képminőséget.",
"share_quality_title": "Válaszd ki a megosztási minőséget",
"shared": "Megosztva",
"shared_album_activities_input_disable": "Hozzászólások kikapcsolva",
"shared_album_activity_remove_content": "Törölni szeretnéd ezt a tevékenységet?",
@@ -1985,16 +2010,19 @@
"sign_out": "Kijelentkezés",
"sign_up": "Regisztráció",
"size": "Méret",
"skip": "Kihagyás",
"skip_to_content": "Ugrás a tartalomhoz",
"skip_to_folders": "Ugrás a mappákhoz",
"skip_to_tags": "Ugrás a címkékhez",
"slideshow": "Diavetítés",
"slideshow_body": "Dőlj hátra, és nézd meg fotóidat teljes képernyős diavetítésben.",
"slideshow_metadata_overlay_mode": "Képadatok tartalma",
"slideshow_metadata_overlay_mode_description_only": "Csak leírás",
"slideshow_metadata_overlay_mode_full": "Teljes",
"slideshow_repeat": "Diavetítés ismétlése",
"slideshow_repeat_description": "Ha a diavetítés véget ér, újraindul az elejétől",
"slideshow_settings": "Diavetítés beállításai",
"slideshow_title": "Diavetítés",
"smart_album": "Okos album",
"some_assets_already_have_a_location_warning": "A kiválasztott elemek közül néhánynak már van helyszíne",
"sort_albums_by": "Albumok rendezése...",
@@ -2157,6 +2185,8 @@
"upload_status_errors": "Hibák",
"upload_status_uploaded": "Feltöltve",
"upload_success": "Feltöltés sikeres, frissítsd az oldalt az újonnan feltöltött elemek megtekintéséhez.",
"upload_to_album_body": "Azok a felhasználók, akik nem használják a kézi feltöltési funkciót, mostantól a helyi fényképeket feltöltés közben közvetlenül hozzáadhatják egy albumhoz; így már nem kell először feltölteniük a képeket, majd később hozzáadniuk őket az albumhoz.",
"upload_to_album_title": "Feltöltés közvetlenül egy albumba",
"upload_to_immich": "Feltöltés Immich-be ({count})",
"uploading": "Feltöltés folyamatban",
"uploading_media": "Média feltöltés folyamatban",
@@ -2224,6 +2254,9 @@
"week": "Hét",
"welcome": "Üdvözlünk",
"welcome_to_immich": "Üdvözöl az Immich",
"whats_new": "Újdonságok",
"whats_new_settings_subtitle": "Nézd meg az Immich újdonságait",
"whats_new_version": "{version} verzió",
"when": "Mikor",
"width": "Szélesség",
"wifi_name": "Wi-Fi neve",
+3 -3
View File
@@ -1303,9 +1303,9 @@
"login_form_failed_login": "Errore nel login, controlla l'URL del server e le credenziali (email e password)",
"login_form_handshake_exception": "Si è verificata un'eccezione di handshake con il server. Abilita il supporto del certificato self-signed nelle impostazioni se si utilizza questo tipo di certificato.",
"login_form_password_hint": "password",
"login_form_server_empty": "Inserisci URL del server.",
"login_form_server_error": "Non è possibile connettersi al server.",
"login_has_been_disabled": "Il login è stato disabilitato.",
"login_form_server_empty": "Inserisci URL del server",
"login_form_server_error": "Impossibile connettersi al server",
"login_has_been_disabled": "Il login è stato disabilitato",
"login_password_changed_error": "C'è stato un errore durante l'aggiornamento della password",
"login_password_changed_success": "Password aggiornata con successo",
"logout_all_device_confirmation": "Sei sicuro di volerti disconnettere da tutti i dispositivi?",
+10
View File
@@ -193,6 +193,15 @@
"maintenance_integrity_checksum_mismatch_description": "ディスク上のチェックサムが、Immichのデータベースに保存されているチェックサムと一致しないファイル。",
"maintenance_integrity_checksum_mismatch_job": "チェックサム不一致を検出する",
"maintenance_integrity_checksum_mismatch_refresh_job": "チェックサム不一致レポートを再生成",
"maintenance_integrity_missing_file": "欠落ファイル",
"maintenance_integrity_missing_file_description": "Immichがデータベースで追跡しているが、ファイルシステム上に存在しないファイル。",
"maintenance_integrity_missing_file_job": "欠落ファイルのチェック",
"maintenance_integrity_missing_file_refresh_job": "欠落ファイルレポートの更新",
"maintenance_integrity_report": "整合性レポート",
"maintenance_integrity_untracked_file": "未追跡ファイル",
"maintenance_integrity_untracked_file_description": "Immichのディレクトリ内に存在するが、Immichに記録がないファイル。",
"maintenance_integrity_untracked_file_job": "未追跡ファイルのチェック",
"maintenance_integrity_untracked_file_refresh_job": "追跡されていないファイルレポートを更新",
"maintenance_restore_backup": "バックアップを復元",
"maintenance_restore_backup_description": "現在のImmichは削除され、選択したバックアップから復元されます。続行前にバックアップが作成されます。",
"maintenance_restore_backup_different_version": "このバックアップは異なるバージョンのImmichにより作成されたものです!",
@@ -546,6 +555,7 @@
"asset_added_to_album": "アルバムに追加",
"asset_adding_to_album": "アルバムに追加しています…",
"asset_created": "項目が作成されました",
"asset_day_count": "{date}: {count, plural, one {# アセット} other {# アセット}}",
"asset_description_updated": "項目の説明文が更新されました",
"asset_hashing": "ハッシュ計算中…",
"asset_list_group_by_sub_title": "グループ分け",
+126 -15
View File
@@ -4,29 +4,29 @@
"account_settings": "खाते व्यवस्था",
"acknowledge": "मान्यता",
"action": "कृती",
"action_common_update": "अद्ययावत",
"action_common_update": "अद्यत",
"action_description": "फिल्टर केलेल्या माध्यमांवर करायच्या क्रियांचा संच",
"actions": "कृत्ये",
"active": "सक्रिय",
"active_count": "कृती: {count}",
"active_count": "कृती: {संख्या}",
"activity": "गतिविधि",
"add": "जोडा",
"add_a_description": "वर्णन करा",
"add_a_location": "एक स्थळ टाका",
"add_a_name": "नाव टाका",
"add_a_title": "शीर्षक टाका",
"add_a_location": "एक स्थळ जोडा",
"add_a_name": "नाव जोडा",
"add_a_title": "शीर्षक जोडा",
"add_action": "कृती जोडा",
"add_assets": "माध्यमे जोडा",
"add_birthday": "जन्मदिवस नोंदवा",
"add_endpoint": "एंडपॉइंट जोडा",
"add_exclusion_pattern": "अपवाद नमुना जोडा",
"add_location": "स्थळ टाका",
"add_location": "स्थळ जोडा",
"add_partner": "भागीदार जोडा",
"add_photos": "छायाचित्रे जोडा",
"add_step": "पायरी जोडा",
"add_step": "टप्पा जोडा",
"add_tag": "टॅग जोडा",
"add_to": "त्या मध्ये जोडा…",
"add_to_album": "संग्रहात टाका",
"add_to_album": "संग्रहात जोडा",
"add_to_album_bottom_sheet_added": "{album} मध्ये जोडले गेले",
"add_to_album_bottom_sheet_already_exists": "आधीच {album} मध्ये आहे",
"add_to_album_bottom_sheet_some_local_assets": "काही स्थानिक माध्यमे अल्बममध्ये जोडणे शक्य झाले नाही",
@@ -37,7 +37,7 @@
"add_url": "URL प्रविष्ट करा",
"added_to_archive": "संग्रहित केले",
"added_to_favorites": "आवडत्या संग्रहात जोडले",
"added_to_favorites_count": "आवडत्यात {count, number} टाकले",
"added_to_favorites_count": "आवडत्यात {count, number} जोडले",
"admin": {
"add_exclusion_pattern_description": "अपवाद अनुकूलन जोडा. ** आणि ? या उपयोगात ग्लोबिंग समर्थित आहे. कोणत्याही \"Raw\" नावाच्या निर्देशिकेमधील सर्व खतावण्या दुर्लक्षीत करण्यासाठी \"/Raw/\" वापरा. \".tif\" या सामान्य पथावर समाप्त असलेल्या सर्व खतावण्या दुर्लक्षीत करण्यासाठी \"**/.tif\" वापरा. विशिष्ट पथ दुर्लक्ष करण्यासाठी \"/path/to/ignore/**\" वापरा.",
"admin_user": "प्रशासन वापरकर्ता",
@@ -67,13 +67,17 @@
"confirm_reprocess_all_faces": "तुम्हाला खात्री आहे का की तुम्हाला सर्व चेहऱ्यांवर पुन्हा प्रक्रिया करायची आहे? यामुळे नाव दिलेले लोकही साफ होतील.",
"confirm_user_password_reset": "तुम्हाला नक्की {user} चा परवलीचा शब्द बदलायचा आहे का?",
"confirm_user_pin_code_reset": "तुम्हाला नक्की {user} चा पिन कोड रीसेट करायचा आहे का?",
"copy_config_to_clipboard_description": "चालू सिस्टम कॉन्फिगरेशन JSON ऑब्जेक्ट म्हणून क्लिपबोर्डवर कॉपी करा",
"create_job": "कार्य बनवा",
"cron_expression": "वेळापत्रक सूत्र",
"cron_expression_description": "चाळन्याचे वेळापत्रक क्रॉन पद्धती ने करा. अधिक माहिती साठी पहा: <link> क्रॉन गुरु</link>",
"cron_expression_presets": "पूर्वनिर्धारित वेळापत्रक सूत्रे",
"disable_login": "प्रवेशाधिकर वर्ज्य करा",
"download_csv": "CSV डाउनलोड करा",
"duplicate_detection_job_description": "सारख्या छायाचित्रांचा शोध घेण्यासाठी यांत्रिकी प्रशिक्षण द्या. ही कार्यक्षमता चतुर शोधप्रणालीवर अवलंबून आहे",
"exclusion_pattern_description": "आपले संग्रहालय चाळताना अपवाद नमुने आपल्याला खतावण्या आणि र्निर्देशिकेला दुर्लक्षीत करू देतात. आपल्याकडे कच्च्या खतावण्या सारख्या आयात करू इच्छित नसलेल्या असंपादित (RAW) खतावण्या असलेल्या निर्देशिका असल्यास हे उपयुक्त आहे.",
"export_config_as_json_description": "चालू सिस्टम कॉन्फिगरेशन JSON फाईल म्हणून डाउनलोड करा",
"external_libraries_page_description": "बाह्य ग्रंथालयाच्या पृष्ठाचे व्यवस्थापन",
"face_detection": "मुख संशोधन",
"face_detection_description": "मशीन लर्निंग वापरून मालमत्तांमधील चेहरे शोधा. व्हिडिओंसाठी, फक्त थंबनेलचा विचार केला जातो. \"रिफ्रेश\" (पुन्हा) सर्व मालमत्तांवर प्रक्रिया करते. \"रीसेट\" याव्यतिरिक्त सर्व वर्तमान चेहरा डेटा साफ करते. \"गहाळ\" मालमत्तांवर अद्याप प्रक्रिया न केलेल्या रांगेत ठेवते. शोधलेले चेहरे फेस डिटेक्शन पूर्ण झाल्यानंतर फेशियल रेकग्निशनसाठी रांगेत ठेवले जातील, त्यांना विद्यमान किंवा नवीन लोकांमध्ये गटबद्ध केले जाईल.",
"facial_recognition_job_description": "शोधलेले चेहरे लोकांमध्ये गटबद्ध करा. हे चरण चेहरा शोधणे पूर्ण झाल्यानंतर चालते. \"रीसेट करा\" (पुन्हा) सर्व चेहरे क्लस्टर कर. \"गहाळ\" चेहरे रांगेत समाविष्ट करते ज्यांना नियुक्त केलेली व्यक्ती नाही.",
@@ -93,6 +97,8 @@
"image_preview_description": "एकच मालमत्ता पाहताना आणि मशीन लर्निंगसाठी वापरली जाणारी, मेटाडेटा काढून दिलेली मध्यम आकाराची प्रतिमा",
"image_preview_quality_description": "पूर्वावलोकन गुणवत्ता 1–100: जितकी उच्च, तितकी चांगली; फाइल आकार वाढतो आणि ॲपची प्रतिसादक्षमता कमी होऊ शकते. कमी मूल्य सेट केल्यास मशीन लर्निंग गुणवत्ता प्रभावित होऊ शकते.",
"image_preview_title": "पूर्वावलोकन विन्यास",
"image_progressive": "प्रगतीशील",
"image_progressive_description": "JPEG प्रतिमा हळूहळू लोड होऊन प्रदर्शित होण्यासाठी क्रमाक्रमानुसार एन्कोड करा. याचा WebP प्रतिमांवर कोणताही परिणाम होत नाही.",
"image_quality": "गुणवत्ता",
"image_resolution": "प्रतिमेची स्पष्टता",
"image_resolution_description": "उच्च रिझोल्यूशन अधिक तपशील जतन करतात, परंतु त्यांचे एन्कोडिंग जास्त वेळ घेतं, फाइल साईज मोठी होते आणि अ‍ॅपची प्रतिसादक्षमता कमी होऊ शकते.",
@@ -101,6 +107,22 @@
"image_thumbnail_description": "फोटो समूह पाहताना मेटाडेटा काढून दाखवलेले लहान थंबनेल",
"image_thumbnail_quality_description": "थंबनेल गुणवत्ता (1–100): जितकी जास्त, तितकी चांगली; परंतु फाइल आकार वाढतो आणि ॲपची प्रतिसादक्षमता कमी होऊ शकते.",
"image_thumbnail_title": "लघुरूप विन्यास",
"import_config_from_json_description": "JSON कॉन्फिग फाइल अपलोड करून सिस्टम कॉन्फिग आयात करा",
"integrity_checks_checksum_files": "चेकसम फाइल्स",
"integrity_checks_checksum_files_description": "चेकसम तपासणी कॉन्फिगर करा",
"integrity_checks_checksum_files_enable_description": "चेकसम तपासणी सक्षम करा",
"integrity_checks_checksum_files_percentage_limit": "टक्केवारी मर्यादा",
"integrity_checks_checksum_files_percentage_limit_description": "प्रत्येक अंतरालमध्ये चेकसम तपासणी किती टक्केपर्यंत चालवायची ते 0.01 ते 1 या श्रेणीत सेट करा.",
"integrity_checks_checksum_files_time_limit": "वेळेची मर्यादा",
"integrity_checks_checksum_files_time_limit_description": "प्रत्येक अंतरालानंतर चेकसम तपासणी किती काळ चालवायची ते कमाल कालावधी कॉन्फिगर करा. (मिलीसेकंद)",
"integrity_checks_missing_files": "गहाळ फाइल्स",
"integrity_checks_missing_files_description": "वारंवारता कॉन्फिगर करा आणि गहाळ फाइल्स तपासणी सक्षम किंवा अक्षम करा",
"integrity_checks_missing_files_enable_description": "गहाळ फाइल्स तपासणी सक्षम करा",
"integrity_checks_settings": "अखंडता तपासण्या",
"integrity_checks_settings_description": "अखंडता तपासणी अंतरांचे व्यवस्थापन करा",
"integrity_checks_untracked_files": "अनट्रॅक्ड फाइल्स",
"integrity_checks_untracked_files_description": "वारंवारता कॉन्फिगर करा आणि ट्रॅक न केलेल्या फाइल्सची तपासणी सक्षम किंवा अक्षम करा",
"integrity_checks_untracked_files_enable_description": "अनट्रॅक्ड फाइल्सची तपासणी सक्षम करा",
"job_concurrency": "{job} एकरूपता",
"job_created": "कार्य तयार झाले",
"job_not_concurrency_safe": "हे कार्य समांतरपणे चालवण्यासाठी सुरक्षित नाही.",
@@ -175,10 +197,15 @@
"machine_learning_smart_search_enabled": "स्मार्ट सर्च सक्षम करा",
"machine_learning_smart_search_enabled_description": "अक्षम केल्यास, प्रतिमा स्मार्ट सर्चसाठी एन्कोड केल्या जाणार नाहीत.",
"machine_learning_url_description": "मशीन लर्निंग सर्व्हरची URL. एकाहून अधिक URL दिल्यास, प्रथम ते दिलेल्या क्रमाने प्रत्येक सर्व्हरवर एक-एक करून प्रयत्न केले जातील, जोपर्यंत कोणीतरी यशस्वी प्रतिसाद देत नाही तोपर्यंत. जे सर्व्हर प्रतिसाद देत नाहीत, त्यांना ते परत ऑनलाइन येईपर्यंत तात्पुरते दुर्लक्षित केले जाईल.",
"maintenance_restore_backup_unknown_version": "बॅकअप आवृत्ती निश्चित करता आली नाही.",
"maintenance_restore_database_backup": "डेटाबेस बॅकअप पुनर्संचयित करा",
"maintenance_restore_database_backup_description": "बॅकअप फाइल वापरून डेटाबेसच्या पूर्वीच्या स्थितीत परत करा",
"maintenance_settings": "देखभाल",
"maintenance_settings_description": "Immich ला देखभाल मोडमध्ये ठेवा.",
"maintenance_start": "देखभाल मोड सुरू करा",
"maintenance_start_error": "देखभाल मोड सुरू करण्यात अयशस्वी.",
"maintenance_upload_backup": "डेटाबेस बॅकअप फाइल अपलोड करा",
"maintenance_upload_backup_error": "बॅकअप अपलोड करता आला नाही, हे .sql/.sql.gz फाइल आहे का?",
"manage_concurrency": "समांतरता व्यवस्थापित करा",
"manage_log_settings": "लॉग सेटिंग्ज नियंत्रण करा",
"map_dark_style": "गडद शैली",
@@ -245,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 प्रदात्याद्वारे समर्थित नसल्यास आवश्यक.",
"oauth_enable_description": "OAuth सह लॉगिन करा",
"oauth_mobile_redirect_uri": "मोबाइल रीडायरेक्ट URI",
"oauth_mobile_redirect_uri_override": "मोबाईल रीडायरेक्ट URI अधिलेखन",
@@ -273,6 +300,9 @@
"refreshing_all_libraries": "सर्व लायब्ररी रीफ्रेश करीत आहे",
"registration": "प्रशासक नोंदणी",
"registration_description": "आपण प्रणालीवरील पहिले वापरकर्ता आहात, म्हणून आपल्याला प्रशासक म्हणून नियुक्त केले जाईल आणि प्रशासकीय कार्ये आपल्याद्वारे हाताळली जातील; तसेच इतर वापरकर्ते आपण तयार कराल.",
"release_channel_release_candidate": "रिलीज कॅंडिडेट",
"release_channel_stable": "स्थिर",
"remove_failed_jobs": "अपयशी झालेल्या कामांना काढून टाका",
"require_password_change_on_login": "पहिल्या लॉगिनवर वापरकर्त्यास पासवर्ड बदलण्याची आवश्यकता असेल",
"reset_settings_to_default": "सेटिंग्ज डीफॉल्टवर रीसेट करा",
"reset_settings_to_recent_saved": "सेटिंग्ज अलीकडील जतन केलेल्या सेटिंग्जवर रीसेट करा",
@@ -285,8 +315,10 @@
"server_public_users_description": "सार्वजनिक अल्बममध्ये वापरकर्ता जोडताना सर्व वापरकर्त्यांची (नाव व ईमेल) यादी दर्शवली जाते. अक्षम केल्यास, ही यादी फक्त प्रशासकांसाठीच उपलब्ध असेल.",
"server_settings": "सर्व्हर सेटिंग्ज",
"server_settings_description": "सर्व्हर सेटिंग्ज व्यवस्थापित करा",
"server_stats_page_description": "प्रशासकीय सर्व्हर सांख्यिकी पृष्ठ",
"server_welcome_message": "स्वागत संदेश",
"server_welcome_message_description": "लॉगिन पृष्ठावर दर्शविण्यात येणारा संदेश।",
"settings_page_description": "प्रशासकीय सेटिंग्ज पृष्ठ",
"sidecar_job": "साइडकार मेटाडेटा",
"sidecar_job_description": "फाईल सिस्टममधून साइडकार मेटाडेटा शोधा किंवा समक्रमित करा",
"slideshow_duration_description": "प्रत्येक प्रतिमा किती सेकंद प्रदर्शित करायची",
@@ -364,6 +396,10 @@
"transcoding_preferred_hardware_device_description": "केवळ VAAPI आणि QSV साठी लागू. हार्डवेअर ट्रान्सकोडिंग साठी वापरला जाणारा DRI नोड सेट करा.",
"transcoding_preset_preset": "प्रीसेट (preset)",
"transcoding_preset_preset_description": "संकुचन गती. किंचित मंद प्रीसेट्स लहान फाइल तयार करतात आणि ठराविक बिटरेटसाठी गुणवत्ता वाढवतात. VP9 ‘faster’ पेक्षा जास्त गती लक्षात घेत नाही.",
"transcoding_realtime": "रिअल-टाइम ट्रान्सकोडिंग [प्रयोगात्मक]",
"transcoding_realtime_description": "व्हिडिओ प्रक्षेपित होत असताना रिअल-टाइममध्ये ट्रान्सकोडिंग करण्याची परवानगी देते. गुणवत्ता स्विचिंग सक्षम करते, परंतु सर्व्हरच्या क्षमतेवर अवलंबून प्लेबॅकमध्ये अधिक विलंब आणि अडथळे येऊ शकतात.",
"transcoding_realtime_enabled": "रिअल-टाइम ट्रान्सकोडिंग सक्षम करा",
"transcoding_realtime_enabled_description": "अक्षम केल्यास, सर्व्हर नवीन रिअल-टाइम ट्रान्सकोडिंग सत्रे सुरू करण्यास नकार देईल.",
"transcoding_reference_frames": "संदर्भ फ्रेम्स",
"transcoding_reference_frames_description": "दिलेल्या फ्रेमचे संकुचन करताना किती फ्रेम्स संदर्भित कराव्यात हे ठरवते. जास्त मूल्ये संकुचन कार्यक्षमतेत सुधारणा करतात, परंतु एन्कोडिंग मंद करतात. 0 ठेवल्यास हे स्वयंचलितपणे सेट होते.",
"transcoding_required_description": "फक्त मान्य प्रारूपात नसलेले व्हिडिओ",
@@ -405,6 +441,10 @@
"user_restore_scheduled_removal": "वापरकर्ता पुनर्संचयित करा – नियोजित हटविण्याची तारीख {date, date, long}",
"user_settings": "वापरकर्ता सेटिंग्ज",
"user_settings_description": "वापरकर्ता सेटिंग्ज व्यवस्थापित करा",
"user_successfully_removed": "वापरकर्ता {email} यशस्वीरित्या काढून टाकण्यात आला आहे.",
"users_page_description": "प्रशासक वापरकर्त्यांचे पृष्ठ",
"version_check_channel": "विमोचन मार्ग",
"version_check_channel_description": "आपल्याला आवृत्ती घोषणा मिळवायच्या असलेल्या प्रकाशन चॅनेलची निवड करा",
"version_check_enabled_description": "आवृत्ती तपासणी सक्षम करा",
"version_check_implications": "आवृत्ती तपासणी वैशिष्ट्य {server} सोबत आवर्ती संवादावर अवलंबून आहे",
"version_check_settings": "आवृत्ती तपासणी",
@@ -416,6 +456,9 @@
"admin_password": "प्रशासक पासवर्ड",
"administration": "प्रशासन",
"advanced": "प्रगत",
"advanced_settings_clear_image_cache": "प्रतिमा कॅश हटवा",
"advanced_settings_clear_image_cache_error": "प्रतिमा कॅश साफ करण्यात अयशस्वी",
"advanced_settings_clear_image_cache_success": "यशस्वीरित्या {size} पार केले",
"advanced_settings_log_level_title": "लॉग पातळी: {level}",
"advanced_settings_prefer_remote_subtitle": "काही उपकरणे स्थानिक अॅसेटमधून थंबनेल लोड करण्यात खूप मंद आहेत. त्याऐवजी रिमोट प्रतिमा लोड करण्यासाठी हा सेटिंग सक्षम करा.",
"advanced_settings_prefer_remote_title": "रिमोट प्रतिमा पसंत करा",
@@ -448,6 +491,7 @@
"album_summary": "अल्बम सारांश",
"album_updated": "अल्बम अद्यतनित",
"album_updated_setting_description": "शेअर केलेल्या अल्बममध्ये नवीन फाईल्स आल्यास ईमेल सूचनार्थ प्राप्त करा",
"album_upload_assets": "आपल्या संगणकातून फाइल्स अपलोड करा आणि अल्बममध्ये जोडा",
"album_viewer_appbar_share_err_delete": "अल्बम हटवण्यात अयशस्वी",
"album_viewer_page_share_add_users": "वापरकर्ते जोडा",
"album_with_link_access": "लिंक असलेल्या कोणत्याही व्यक्तीस या अल्बममधील फोटो आणि लोक पाहता येतील.",
@@ -460,12 +504,16 @@
"all": "सर्व",
"all_albums": "सर्व अल्बम्स",
"all_people": "सर्व लोक",
"all_photos": "सर्व फोटो",
"all_videos": "सर्व व्हिडिओ",
"allow_dark_mode": "डार्क मोडला परवानगी द्या",
"allow_public_user_to_download": "सार्वजनिक वापरकर्त्यांना डाउनलोड करण्याची परवानगी द्या",
"allow_public_user_to_upload": "सार्वजनिक वापरकर्त्यांना अपलोड करण्याची परवानगी द्या",
"allowed": "परवानगी आहे",
"alt_text_qr_code": "QR कोड प्रतिमा",
"always_keep": "नेहमी ठेवा",
"always_keep_photos_hint": "Free Up Space या डिव्हाइसवरील सर्व फोटो जपून ठेवेल.",
"always_keep_videos_hint": "Free Up Space या डिव्हाइसवरील सर्व व्हिडिओ जपून ठेवेल.",
"api_key": "एपीआई की",
"api_key_description": "हा मूल्य एकदाच दाखविला जाईल. कृपया विंडो बंद करण्यापूर्वी ते कॉपी करायला विसरू नका.",
"api_key_empty": "आपले API की नाव रिक्त असू नये",
@@ -490,6 +538,8 @@
"are_you_sure_to_do_this": "तुम्हाला हे खरंच करायचे आहे का?",
"asset_added_to_album": "अल्बममध्ये जोडले गेले",
"asset_adding_to_album": "अल्बममध्ये जोडत आहे…",
"asset_created": "अ‍ॅसेट तयार करण्यात आला",
"asset_day_count": "{date}: {count, plural, एक {# asset} इतर {# assets}}",
"asset_description_updated": "साधनाचे वर्णन अद्यावत केले गेले आहे",
"asset_hashing": "हॅशिंग…",
"asset_list_group_by_sub_title": "गटानुसार गटबद्ध करा",
@@ -498,6 +548,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_skipped": "वगळले",
@@ -562,12 +615,14 @@
"backup_options": "बॅकअप पर्याय",
"backup_settings_subtitle": "अपलोड सेटिंग्ज व्यवस्थापित करा",
"backward": "मागासलेले",
"battery_optimization_backup_reliability": "बॅटरी ऑप्टिमायझेशन अक्षम केल्याने पार्श्वभूमी बॅकअपची विश्वसनीयता सुधारू शकते",
"biometric_auth_enabled": "बायोमेट्रिक प्रमाणीकरण चालू आहे",
"biometric_locked_out": "आपण बायोमेट्रिक प्रमाणीकरणापासून लॉक आहात",
"biometric_no_options": "कोणतेही बायोमेट्रिक पर्याय उपलब्ध नाहीत",
"biometric_not_available": "या डिव्हाइसवर बायोमेट्रिक प्रमाणीकरण उपलब्ध नाही",
"birthdate_set_description": "फोटोच्या वेळी या व्यक्तीचे वय मोजण्यासाठी जन्मतारीख वापरली जाते.",
"blurred_background": "पार्श्वभूमी धुसळलेली",
"browse_templates": "टॅम्पलेट्स ब्राउझ करा",
"bugs_and_feature_requests": "बग्ज & फिचर विनंत्या",
"build": "तयार करा",
"build_image": "इमेज तयार करा",
@@ -587,6 +642,7 @@
"cannot_update_the_description": "वर्णन अद्यतनित करता येणार नाही",
"cast": "कास्ट",
"cast_description": "उपलब्ध कास्ट गंतव्ये कॉन्फिगर करा",
"change": "बदल",
"change_date": "तारीख बदला",
"change_description": "वर्णन बदला",
"change_display_order": "प्रदर्शन क्रम बदला",
@@ -608,17 +664,34 @@
"charging": "चार्जिंग",
"charging_requirement_mobile_backup": "बॅकग्राउंड बॅकअपसाठी उपकरण चार्ज होत असणे आवश्यक आहे",
"check_logs": "लॉग्ज तपासा",
"checksum": "चेकसम",
"choose": "निवडा",
"choose_matching_people_to_merge": "विलीन करण्यासाठी जुळणारे लोक निवडा",
"city": "शहर",
"cleanup_confirm_description": "Immich ने {count} अ‍ॅसेट({date} पूर्वी तयार केलेल्या) सर्व्हरवर सुरक्षितपणे बॅकअप केल्या आढळल्या. या डिव्हाइसमधील स्थानिक प्रती काढून टाकाव्यात का?",
"cleanup_confirm_prompt_title": "या उपकरणातून काढून टाकायचे?",
"cleanup_deleted_assets": "{count} मालमत्ता डिव्हाइसच्या कचऱ्यात हलवल्या",
"cleanup_deleting": "कचऱ्यात हलवत आहे...",
"cleanup_found_assets": "{count} बॅकअप केलेल्या मालमत्ता आढळल्या",
"cleanup_found_assets_with_size": "{count} बॅकअप केलेल्या मालमत्ता आढळल्या ({size})",
"cleanup_icloud_shared_albums_excluded": "iCloud सामायिक अल्बम्स स्कॅनमधून वगळले जातात",
"cleanup_no_assets_found": "वरील निकषांनुसार कोणतीही मालमत्ता सापडली नाही. फ्री अप स्पेस फक्त ती मालमत्ता हटवू शकते ज्याचे सर्व्हरवर बॅकअप घेतले गेले आहेत",
"cleanup_preview_title": "काढून टाकण्यासाठीची अ‍ॅसेट({count})",
"cleanup_step3_description": "तुमच्या तारखेनुसार बॅकअप झालेल्या अ‍ॅसेट स्कॅन करा आणि सेटिंग्ज जतन करा.",
"cleanup_step4_summary": "{count} अ‍ॅसेट({date} पूर्वी तयार केलेली) आपल्या स्थानिक उपकरणातून काढण्यासाठी. फोटो Immich अॅपमधून उपलब्ध राहतील.",
"cleanup_trash_hint": "साठवणुकीची जागा पूर्णपणे परत मिळवण्यासाठी, सिस्टम गॅलरी अॅप उघडा आणि कचरा रिकामी करा",
"clear": "साफ करा",
"clear_all": "सर्व साफ करा",
"clear_all_recent_searches": "सर्व शोध इतिहास मिटवा",
"clear_failed_count": "हटवणे अयशस्वी झाले ({count})",
"clear_file_cache": "फाईल कॅश मिटवा",
"clear_message": "संदेश मिटवा",
"clear_value": "मूल्य मिटवा",
"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 क्लायंट प्रमाणपत्र [प्रायोगिक]",
@@ -626,12 +699,19 @@
"collapse": "संकुचित करा",
"collapse_all": "सर्व संकुचित करा",
"color": "रंग",
"command": "आदेश",
"command_palette_prompt": "पृष्ठे, क्रिया किंवा आदेश पटकन शोधा",
"command_palette_to_close": "बंद करण्यासाठी",
"command_palette_to_navigate": "प्रवेश करण्यासाठी",
"command_palette_to_select": "निवडण्यासाठी",
"command_palette_to_show_all": "सर्व दाखवण्यासाठी",
"comment_deleted": "टिप्पणी हटवली",
"comment_options": "टिप्पणी पर्याय",
"comments_and_likes": "टिप्पण्या & लाईक्स",
"comments_are_disabled": "टिप्पण्या अक्षम आहेत",
"common_create_new_album": "नवीन अल्बम तयार करा",
"completed": "पूर्ण झाले",
"configuration": "संरचना",
"confirm": "पुष्टी करा",
"confirm_admin_password": "ऍडमिन संकेतशब्द पुष्टी करा",
"confirm_delete_face": "तुम्हाला {name} चे चेहरा या फाईलमधून हटवायचे आहे का?",
@@ -646,6 +726,7 @@
"contain": "समाविष्ट करा",
"context": "संदर्भ",
"continue": "पुढे",
"control_bottom_app_bar_add_tags": "टॅग जोडा",
"control_bottom_app_bar_delete_from_local": "उपकरणातून हटवा",
"control_bottom_app_bar_edit_location": "स्थान संपादित करा",
"control_bottom_app_bar_edit_time": "तारीख व वेळ संपादित करा",
@@ -654,6 +735,7 @@
"copied_to_clipboard": "क्लिपबोर्डवर कॉपी झाले!",
"copy_error": "कॉपी करताना त्रुटी",
"copy_image": "प्रतिमा कॉपी करा",
"copy_json": "JSON कॉपी करा",
"copy_link": "लिंक कॉपी करा",
"copy_link_to_clipboard": "लिंक क्लिपबोर्डवर कॉपी करा",
"copy_password": "संकेतशब्द कॉपी करा",
@@ -665,6 +747,7 @@
"create_album": "अल्बम तयार करा",
"create_album_page_untitled": "शीर्षकेतर",
"create_api_key": "API की तयार करा",
"create_first_workflow": "पहिला वर्कफ्लो तयार करा",
"create_library": "लायब्ररी तयार करा",
"create_link": "लिंक तयार करा",
"create_link_to_share": "शेअर करण्यासाठी लिंक तयार करा",
@@ -673,33 +756,47 @@
"create_new_person": "नवीन व्यक्ती तयार करा",
"create_new_person_hint": "निवडलेल्या फाईल्स नवीन व्यक्तीशी जोडा",
"create_new_user": "नवीन वापरकर्ता तयार करा",
"create_person": "व्यक्ती तयार करा",
"create_person_subtitle": "नवीन व्यक्ती तयार करण्यासाठी आणि टॅग करण्यासाठी निवडलेल्या चेहऱ्यावर नाव जोडा",
"create_shared_album_page_share_add_assets": "फाईल्स जोडा",
"create_shared_album_page_share_select_photos": "फोटो निवडा",
"create_shared_link": "शेअर लिंक तयार करा",
"create_tag": "टॅग तयार करा",
"create_tag_description": "नवीन टॅग तयार करा. सबटॅगसाठी पूर्ण पाथसहित नाव टाका।",
"create_user": "वापरकर्ता तयार करा",
"create_workflow": "कार्यप्रवाह तयार करा",
"created": "तयार केले",
"created_at": "निर्मिती तारीख",
"creating_linked_albums": "लिंक केलेले अल्बम तयार करत आहे...",
"crop": "छाटणी करा",
"crop_aspect_ratio_free": "रिक्त",
"crop_aspect_ratio_original": "मूळ",
"crop_aspect_ratio_square": "चौरस",
"current_device": "वर्तमान उपकरण",
"current_pin_code": "चालू PIN कोड",
"current_server_address": "सर्व्हर पत्ता",
"custom_date": "सानुकूल तारीख",
"custom_locale": "भाषा व क्षेत्र",
"custom_locale_description": "दिनांक व संख्या भाषेनुसार व क्षेत्रानुसार format करा",
"custom_url": "सानुकूल URL",
"cutoff_date_description": "शेवटच्या… पासूनचे फोटो ठेवा…",
"cutoff_day": "{count, plural, one {दिवस} other {दिवस}}",
"cutoff_year": "{count, plural, one {वर्ष} other {वर्ष}}",
"dark": "डार्क",
"dark_theme": "डार्क थीम बदल",
"date": "तारीख",
"date_after": "नंतरची तारीख",
"date_and_time": "दिनांक व वेळ",
"date_before": "पूर्वची तारीख",
"date_of_birth": "जन्म तारीख",
"date_of_birth_saved": "जन्मतारीख जतन झाली",
"date_range": "तारीख श्रेणी",
"date_time_original": "मूळ तारीख/वेळ",
"day": "दिवस",
"days": "अनेक दिवस",
"deduplicate_all": "सर्व डुप्लिकेट काढा",
"default_quality_subtitle": "शेअर टॅप करताना वापरली जाणारी गुणवत्ता. प्रत्येक वेळी निवडण्यासाठी शेअर बटण दीर्घकाळ दाबा.",
"default_share_quality": "डीफॉल्ट शेअर गुणवत्ता",
"delete": "हटवा",
"delete_action_confirmation_message": "तुम्हाला ही फाईल हटवायची आहे का? ही क्रिया सर्व्हरच्या ट्रॅशमध्ये हलवेल आणि स्थानिकपणे हटवायचे का ते विचारेल",
"delete_action_prompt": "{count} हटवले",
@@ -730,6 +827,7 @@
"deselect_all": "सर्व निवड रद्द करा",
"details": "तपशील",
"direction": "दिशा",
"disable": "अक्षम करा",
"disabled": "अक्षम",
"discord": "डिस्कॉर्ड",
"discover": "शोधा",
@@ -751,16 +849,21 @@
"download_include_embedded_motion_videos": "एम्बेड केलेली व्हिडिओ",
"download_include_embedded_motion_videos_description": "मोशन फोटोमधील एम्बेड केलेली व्हिडिओ स्वतंत्र फाईल म्हणून समाविष्ट करा",
"download_notfound": "डाउनलोड आढळला नाही",
"download_original": "मूळ डाउनलोड करा",
"download_paused": "डाउनलोड थांबवला",
"download_settings": "डाउनलोड सेटिंग्ज",
"download_settings_description": "फाईल डाउनलोड संबंधित सेटिंग्ज व्यवस्थापित करा",
"download_waiting_to_retry": "पुन्हा प्रयत्न करण्याची प्रतीक्षा",
"downloading": "डाउनलोड करत आहे",
"downloading_asset_filename": "{filename} डाउनलोड करत आहे",
"downloading_from_icloud": "iCloud वरून डाउनलोड करत आहे",
"downloading_media": "मीडिया डाउनलोड करत आहे",
"drag_to_reorder": "पुनर्क्रम करण्यासाठी ओढा",
"drop_files_to_upload": "अपलोडसाठी फाईल्स इथे ड्रॉप करा",
"duplicate": "प्रतिलिपी",
"duplicate_workflow": "प्रतिलिपी कार्यप्रवाह",
"duplicates": "डुप्लिकेट्स",
"duplicates_description": "प्रत्येक गटातले डुप्लिकेट फाईल्स निवडा",
"duplicates_description": "प्रत्येक गटातले डुप्लिकेट फाईल्स निवडा.",
"duration": "कालावधी",
"edit": "संपादित करा",
"edit_album": "अल्बम संपादित करा",
@@ -783,7 +886,15 @@
"edit_tag": "टॅग संपादित करा",
"edit_title": "शीर्षक संपादित करा",
"edit_user": "वापरकर्ता संपादित करा",
"edit_workflow": "कार्यप्रवाह संपादित करा",
"editor": "एडिटर",
"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": "उभे पलटवा",
"email": "ईमेल",
"email_notifications": "ईमेल सूचना",
"empty_folder": "हा फोल्डर रिकामा आहे",
@@ -1128,9 +1239,9 @@
"login_form_failed_login": "लॉगिन करताना त्रुटी, सर्व्हर URL, ईमेल आणि पासवर्ड तपासा",
"login_form_handshake_exception": "सर्व्हरसह handshake exception आली. तुम्ही self-signed प्रमाणपत्र वापरत असाल तर सेटिंग्जमध्ये self-signed प्रमाणपत्र समर्थन सक्षम करा.",
"login_form_password_hint": "पासवर्ड",
"login_form_server_empty": "सर्व्हर URL भरा.",
"login_form_server_error": "सर्व्हरशी जोडता आले नाही.",
"login_has_been_disabled": "लॉगिन बंद केले आहे.",
"login_form_server_empty": "सर्व्हर URL भरा",
"login_form_server_error": "सर्व्हरशी जोडता आले नाही",
"login_has_been_disabled": "लॉगिन बंद केले आहे",
"login_password_changed_error": "पासवर्ड अपडेट करताना त्रुटी आली",
"login_password_changed_success": "पासवर्ड यशस्वीपणे अपडेट झाला",
"logout_all_device_confirmation": "तुम्हाला नक्की सर्व डिव्हाइसवरून लॉग आऊट करायचे आहे का?",
@@ -1495,7 +1606,7 @@
"reset_pin_code_description": "तुमचा PIN विसरला असल्यास, तो रीसेट करण्यासाठी सर्व्हर प्रशासकाशी संपर्क साधा",
"reset_pin_code_with_password": "पासवर्डने तुम्ही नेहमी PIN कोड रीसेट करू शकता",
"reset_sqlite": "SQLite डेटाबेस रीसेट करा",
"reset_sqlite_confirmation": "तुम्हाला नक्की SQLite डेटाबेस रीसेट करायचा आहे का? डेटा पुन्हा समक्रमित करण्यासाठी तुम्हाला लॉगआउट करून पुन्हा लॉगइन करावे लागेल",
"reset_sqlite_confirmation": "तुम्हाला नक्की SQLite डेटाबेस रीसेट करायचा आहे का? डेटा पुन्हा समक्रमित करण्यासाठी तुम्हाला लॉगआउट करून पुन्हा लॉगइन करावे लागेल.",
"reset_sqlite_success": "SQLite डेटाबेस यशस्वीरीत्या रीसेट केला",
"reset_to_default": "डीफॉल्टवर रीसेट करा",
"resolution": "रेझोल्यूशन",
+32
View File
@@ -108,6 +108,21 @@
"image_thumbnail_quality_description": "Thumbnail kwaliteit van 1-100. Hoger is beter, maar produceert grotere bestanden en kan de app vertragen.",
"image_thumbnail_title": "Thumbnailinstellingen",
"import_config_from_json_description": "Importeer de systeem­configuratie door een JSON-configuratie­bestand te uploaden",
"integrity_checks_checksum_files": "Checksum bestanden",
"integrity_checks_checksum_files_description": "Configureer de checksum controle",
"integrity_checks_checksum_files_enable_description": "Schakel de checksum controle in",
"integrity_checks_checksum_files_percentage_limit": "Percentage limiet",
"integrity_checks_checksum_files_percentage_limit_description": "Stel het maximale percentage in tussen 0.01 en 1 voor hoe vaak de checksum controle per interval moet worden uitgevoerd.",
"integrity_checks_checksum_files_time_limit": "Tijdslimiet",
"integrity_checks_checksum_files_time_limit_description": "Stel de maximale duur in waarin de checksum controle per interval moet worden uitgevoerd. (ms)",
"integrity_checks_missing_files": "Ontbrekende bestanden",
"integrity_checks_missing_files_description": "Configureer de frequentie en schakel de controle op ontbrekende bestanden in of uit",
"integrity_checks_missing_files_enable_description": "Schakel de controle op ontbrekende bestanden in",
"integrity_checks_settings": "Integriteitscontroles",
"integrity_checks_settings_description": "Beheer de intervallen voor integriteitscontroles",
"integrity_checks_untracked_files": "Niet getraceerde bestanden",
"integrity_checks_untracked_files_description": "Configureer de frequentie en schakel de controle op niet getraceerde bestanden in of uit",
"integrity_checks_untracked_files_enable_description": "Schakel de controle op niet getraceerde bestanden in",
"job_concurrency": "{job} gelijktijdigheid",
"job_created": "Taak aangemaakt",
"job_not_concurrency_safe": "Deze taak kan niet parallel worden uitgevoerd.",
@@ -1461,6 +1476,7 @@
"never": "Nooit",
"new_album": "Nieuw album",
"new_api_key": "Nieuwe API-sleutel",
"new_feature": "Nieuwe functie",
"new_password": "Nieuw wachtwoord",
"new_person": "Nieuw persoon",
"new_pin_code": "Nieuwe pincode",
@@ -1521,6 +1537,8 @@
"obtainium_configurator": "Obtainium instellen",
"obtainium_configurator_instructions": "Gebruik Obtainium om de Android-app recht­streeks vanuit Immich's GitHub-releases te installeren en bij te werken. Maak een API-sleutel aan en selecteer een variant om je Obtainium-configuratielink te maken",
"ocr": "OCR",
"ocr_body": "Immich leest nu de tekst in je foto's, zodat je ze kunt zoeken op basis van de inhoud.",
"ocr_title": "Zoek tekst in je foto's",
"official_immich_resources": "Officiële Immich bronnen",
"offline": "Offline",
"offset": "Verrekening",
@@ -1539,6 +1557,8 @@
"open": "Openen",
"open_calendar": "Open kalender",
"open_in_browser": "Openen in browser",
"open_in_immich_body": "Stel Immich in als je galerij op Android om foto's rechtstreeks vanuit andere apps te openen.",
"open_in_immich_title": "Open foto's in Immich",
"open_in_map_view": "Openen in kaartweergave",
"open_in_openstreetmap": "Openen in OpenStreetMap",
"open_the_search_filters": "Open de zoekfilters",
@@ -1697,7 +1717,9 @@
"recent": "Recent",
"recent_searches": "Recente zoekopdrachten",
"recently_added": "Onlangs toegevoegd",
"recently_added_body": "Ga direct naar een aparte pagina met al je recent toegevoegde items.",
"recently_added_page_title": "Recent toegevoegd",
"recently_added_title": "Recent toegevoegd",
"recently_taken": "Recent genomen",
"refresh": "Vernieuwen",
"refresh_encoded_videos": "Vernieuw gecodeerde video's",
@@ -1904,6 +1926,8 @@
"share_link": "Link delen",
"share_original": "Origineel gebruiken (groot)",
"share_preview": "Voorbeeldafbeelding gebruiken (klein)",
"share_quality_body": "Houd de deelknop ingedrukt om de beeldkwaliteit te kiezen voordat je deelt.",
"share_quality_title": "Kies de gewenste deel kwaliteit",
"shared": "Gedeeld",
"shared_album_activities_input_disable": "Reactie is uitgeschakeld",
"shared_album_activity_remove_content": "Wil je deze activiteit verwijderen?",
@@ -1985,16 +2009,19 @@
"sign_out": "Uitloggen",
"sign_up": "Registreren",
"size": "Grootte",
"skip": "Overslaan",
"skip_to_content": "Doorgaan naar inhoud",
"skip_to_folders": "Doorgaan naar mappen",
"skip_to_tags": "Doorgaan naar tags",
"slideshow": "Diavoorstelling",
"slideshow_body": "Leun achterover en bekijk hoe je foto's in een diavoorstelling op volledig scherm worden afgespeeld.",
"slideshow_metadata_overlay_mode": "Metadata bijschrift",
"slideshow_metadata_overlay_mode_description_only": "Enkel beschrijving",
"slideshow_metadata_overlay_mode_full": "Volledig",
"slideshow_repeat": "Herhaal diavoorstelling",
"slideshow_repeat_description": "Keer terug naar het begin wanneer de diavoorstelling eindigt",
"slideshow_settings": "Diavoorstelling instellingen",
"slideshow_title": "Diavoorstelling",
"smart_album": "Slim album",
"some_assets_already_have_a_location_warning": "Sommige geselecteerde items hebben al een locatie",
"sort_albums_by": "Sorteer albums op...",
@@ -2157,6 +2184,8 @@
"upload_status_errors": "Fouten",
"upload_status_uploaded": "Geüpload",
"upload_success": "Uploaden gelukt, vernieuw de pagina om de nieuwe items te zien.",
"upload_to_album_body": "Voor gebruikers die de handmatige uploadfunctie niet gebruiken is het nu mogelijk om lokale foto's direct aan een album toe te voegen tijdens het uploaden. Het is dus niet meer nodig om foto's eerst te uploaden en ze later aan een album toe te voegen.",
"upload_to_album_title": "Upload direct naar een album",
"upload_to_immich": "Uploaden naar Immich ({count})",
"uploading": "Aan het uploaden",
"uploading_media": "Media wordt geüpload",
@@ -2224,6 +2253,9 @@
"week": "Week",
"welcome": "Welkom",
"welcome_to_immich": "Welkom bij Immich",
"whats_new": "Wat is er nieuw",
"whats_new_settings_subtitle": "Ontdek wat er nieuw is in Immich",
"whats_new_version": "Versie {version}",
"when": "Wanneer",
"width": "Breedte",
"wifi_name": "Wifinetwerk",
+7 -3
View File
@@ -1303,9 +1303,9 @@
"login_form_failed_login": "Ocorreu um erro ao iniciar sessão, verifique o URL do servidor, o e-mail e a palavra-passe",
"login_form_handshake_exception": "Erro ao conectar com o servidor. Ative o suporte para certificados auto-assinados nas configurações se estiver utilizando um certificado auto-assinado.",
"login_form_password_hint": "Palavra-passe",
"login_form_server_empty": "Insira a URL do servidor.",
"login_form_server_error": "Não foi possível ligar ao servidor.",
"login_has_been_disabled": "O início de sessão foi desativado.",
"login_form_server_empty": "Insira a URL do servidor",
"login_form_server_error": "Não foi possível ligar ao servidor",
"login_has_been_disabled": "O início de sessão foi desativado",
"login_password_changed_error": "Ocorreu um erro ao atualizar a sua palavra-passe",
"login_password_changed_success": "Palavra-passe atualizada com sucesso",
"logout_all_device_confirmation": "Tem a certeza de que deseja terminar a sessão em todos os dispositivos?",
@@ -1356,6 +1356,7 @@
"map_location_picker_page_use_location": "Utilizar esta localização",
"map_location_service_disabled_content": "Serviço de localização precisa de estar ativado para mostrar recursos da localização atual. Deseja ativar agora?",
"map_location_service_disabled_title": "Serviço de localização desativado",
"map_marker_for_image": "Marcador do mapa para a foto tirada em {city}, {country}",
"map_marker_with_image": "Marcador de mapa com imagem",
"map_no_location_permission_content": "A permissão da localização é necessária para mostrar recursos da localização atual. Deseja conceder a permissão agora?",
"map_no_location_permission_title": "Permissão de localização foi negada",
@@ -1506,6 +1507,9 @@
"notes": "Notas",
"nothing_here_yet": "Ainda não existe nada aqui",
"notification_backup_reliability": "Ativar notificações para melhorar a fiabilidade da cópia de segurança em segundo plano",
"notification_enabled_list_tile_content": "O Immich utiliza notificações para cópias de segurança em segundo plano. Faça a gestão destas nas definições do seu dispositivo.",
"notification_enabled_list_tile_open_button": "Abrir Definições",
"notification_enabled_list_tile_title": "Notificações ativadas",
"notification_permission_dialog_content": "Para ativar as notificações, vá em Configurações e selecione permitir.",
"notification_permission_list_tile_content": "Conceder permissões para ativar notificações.",
"notification_permission_list_tile_enable_button": "Ativar notificações",
+38 -3
View File
@@ -108,6 +108,21 @@
"image_thumbnail_quality_description": "Qualidade da miniatura, de 1 a 100. Maior é melhor, mas produz arquivos maiores e pode reduzir a velocidade do aplicativo.",
"image_thumbnail_title": "Configurações de Miniaturas",
"import_config_from_json_description": "Importar configuração do sistema enviando um arquivo JSON de configuração",
"integrity_checks_checksum_files": "Arquivos de checksum",
"integrity_checks_checksum_files_description": "Configure a verificação de checksum",
"integrity_checks_checksum_files_enable_description": "Ative a verificação de checksum",
"integrity_checks_checksum_files_percentage_limit": "Limite percentual",
"integrity_checks_checksum_files_percentage_limit_description": "Configure a porcentagem máxima entre 0,01 e 1 para a proporção da verificação de checksum a ser executada em cada intervalo.",
"integrity_checks_checksum_files_time_limit": "Tempo limite",
"integrity_checks_checksum_files_time_limit_description": "Configure a duração máxima pela qual a verificação de checksum deve ser executada em cada intervalo. (ms)",
"integrity_checks_missing_files": "Arquivos ausentes",
"integrity_checks_missing_files_description": "Configure a frequência e habilite ou desabilite a verificação de arquivos ausentes",
"integrity_checks_missing_files_enable_description": "Ativar a verificação de arquivos ausentes",
"integrity_checks_settings": "Verificações de integridade",
"integrity_checks_settings_description": "Gerenciar intervalos de verificações de integridade",
"integrity_checks_untracked_files": "Arquivos não rastreados",
"integrity_checks_untracked_files_description": "Configure a frequência e habilite ou desabilite a verificação de arquivos não rastreados",
"integrity_checks_untracked_files_enable_description": "Habilitar a verificação de arquivos não rastreados",
"job_concurrency": "{job} simultâneo",
"job_created": "Tarefa criada",
"job_not_concurrency_safe": "Esta tarefa não é compatível com simultaneidade.",
@@ -1303,9 +1318,9 @@
"login_form_failed_login": "Erro ao fazer login, verifique a URL do servidor, e-mail e senha",
"login_form_handshake_exception": "Houve um erro de autorização com o servidor. Se estiver utilizando um certificado auto assinado, ative o suporte a isso nas configurações.",
"login_form_password_hint": "senha",
"login_form_server_empty": "Digite a URL do servidor.",
"login_form_server_error": "Não foi possível conectar ao servidor.",
"login_has_been_disabled": "Login foi desativado.",
"login_form_server_empty": "Digite a URL do servido",
"login_form_server_error": "Não foi possível conectar ao servidor",
"login_has_been_disabled": "Login foi desativado",
"login_password_changed_error": "Erro ao atualizar a sua senha",
"login_password_changed_success": "Senha atualizada com sucesso",
"logout_all_device_confirmation": "Tem certeza de que deseja sair de todos os dispositivos?",
@@ -1461,6 +1476,7 @@
"never": "Nunca",
"new_album": "Novo Álbum",
"new_api_key": "Nova Chave de API",
"new_feature": "Recurso novo",
"new_password": "Nova senha",
"new_person": "Nova Pessoa",
"new_pin_code": "Novo código PIN",
@@ -1507,6 +1523,9 @@
"notes": "Notas",
"nothing_here_yet": "Ainda não existe nada aqui",
"notification_backup_reliability": "Ative as notificações para evitar problemas durante o backup em segundo plano",
"notification_enabled_list_tile_content": "O Immich utiliza notificações para o backup em segundo plano. Gerencie-as nas configurações do seu dispositivo.",
"notification_enabled_list_tile_open_button": "Abrir configurações",
"notification_enabled_list_tile_title": "Notificações ativadas",
"notification_permission_dialog_content": "Para ativar as notificações, vá em Configurações e selecione permitir.",
"notification_permission_list_tile_content": "Conceda permissão para ativar notificações.",
"notification_permission_list_tile_enable_button": "Ativar notificações",
@@ -1518,6 +1537,8 @@
"obtainium_configurator": "Configurador Obtainium",
"obtainium_configurator_instructions": "Use o Obtainium para instalar e atualizar o aplicativo Android diretamente do lançamento do Immich no GitHub. Crie uma chave API e selecione a variante para criar o seu link de configuração Obtainium",
"ocr": "OCR",
"ocr_body": "O Immich agora lê o texto dentro das suas fotos, para que você possa pesquisá-las pelo que está escrito nelas.",
"ocr_title": "Pesquise texto em suas fotos",
"official_immich_resources": "Recursos oficiais do Immich",
"offline": "Desconectado",
"offset": "Deslocamento",
@@ -1536,6 +1557,8 @@
"open": "Abrir",
"open_calendar": "Abrir calendário",
"open_in_browser": "Abrir no navegador",
"open_in_immich_body": "Defina o Immich como sua galeria no Android para abrir fotos diretamente de outros aplicativos.",
"open_in_immich_title": "Abrir fotos no Immich",
"open_in_map_view": "Mostrar no mapa",
"open_in_openstreetmap": "Abrir no OpenStreetMap",
"open_the_search_filters": "Abre os filtros de pesquisa",
@@ -1694,7 +1717,9 @@
"recent": "Recente",
"recent_searches": "Pesquisas recentes",
"recently_added": "Adicionado recentemente",
"recently_added_body": "Acesse diretamente tudo o que você adicionou recentemente em uma página dedicada.",
"recently_added_page_title": "Adicionados recentemente",
"recently_added_title": "Adicionado recentemente",
"recently_taken": "Tirada recentemente",
"refresh": "Atualizar",
"refresh_encoded_videos": "Atualizar vídeos codificados",
@@ -1901,6 +1926,8 @@
"share_link": "Criar Link",
"share_original": "Original (grande)",
"share_preview": "Miniatura (pequeno)",
"share_quality_body": "Pressione e segure o botão de compartilhamento para escolher a qualidade da imagem antes de compartilhar.",
"share_quality_title": "Escolha a qualidade do compartilhamento",
"shared": "Compartilhado",
"shared_album_activities_input_disable": "Comentários desativados",
"shared_album_activity_remove_content": "Deseja excluir esta atividade?",
@@ -1982,16 +2009,19 @@
"sign_out": "Sair",
"sign_up": "Registrar",
"size": "Tamanho",
"skip": "Pular",
"skip_to_content": "Ir para o conteúdo",
"skip_to_folders": "Ir para pastas",
"skip_to_tags": "Ir para os marcadores",
"slideshow": "Apresentação",
"slideshow_body": "Relaxe e assista às suas fotos em uma apresentação de slides em tela cheia.",
"slideshow_metadata_overlay_mode": "Conteúdo exibido",
"slideshow_metadata_overlay_mode_description_only": "Apenas a descrição",
"slideshow_metadata_overlay_mode_full": "Tudo",
"slideshow_repeat": "Repetir apresentação de slides",
"slideshow_repeat_description": "Voltar para o início quando a apresentação terminar",
"slideshow_settings": "Opções de apresentação",
"slideshow_title": "Apresentação de slides",
"smart_album": "Álbum inteligente",
"some_assets_already_have_a_location_warning": "Alguns dos arquivos selecionados já tem uma localização",
"sort_albums_by": "Ordenar álbuns por...",
@@ -2154,6 +2184,8 @@
"upload_status_errors": "Erros",
"upload_status_uploaded": "Enviado",
"upload_success": "Enviado com sucesso, atualize a página para ver os novos arquivos.",
"upload_to_album_body": "Para os usuários que não utilizam o recurso de upload manual, agora é possível adicionar fotos locais diretamente a um álbum durante o upload; não é mais necessário fazer o upload para depois adicioná-las a um álbum.",
"upload_to_album_title": "Faça o upload diretamente para um álbum",
"upload_to_immich": "Enviar para o Immich ({count})",
"uploading": "Enviando",
"uploading_media": "Enviando mídia",
@@ -2221,6 +2253,9 @@
"week": "Semana",
"welcome": "Bem-vindo(a)",
"welcome_to_immich": "Bem-vindo(a) ao Immich",
"whats_new": "O que há de novo",
"whats_new_settings_subtitle": "Veja as novidades do Immich",
"whats_new_version": "Versão {version}",
"when": "Quando",
"width": "Largura",
"wifi_name": "Nome do Wi-Fi",
+4
View File
@@ -73,6 +73,7 @@
"cron_expression_description": "Setați intervalul de scanare folosind formatul cron. Pentru mai multe informații, consultați de ex. <link>Crontab Guru</link>",
"cron_expression_presets": "Presetări de expresie cron",
"disable_login": "Dezactivați autentificarea",
"download_csv": "Descarcă CSV",
"duplicate_detection_job_description": "Rulați învățarea automată pe materiale pentru a detecta imagini similare. Se bazează pe Căutare Inteligentă",
"exclusion_pattern_description": "Modelele de excludere vă permit să ignorați fișierele și folderele atunci când vă scanați biblioteca. Acest lucru este util dacă aveți foldere care conțin fișiere pe care nu doriți să le importați, cum ar fi fișierele RAW.",
"export_config_as_json_description": "Descărcați configurația actuală a sistemului ca fișier JSON",
@@ -182,9 +183,12 @@
"machine_learning_smart_search_enabled": "Activează căutarea inteligentă",
"machine_learning_smart_search_enabled_description": "Dacă este dezactivată, imaginile nu vor fi codificate pentru căutarea inteligentă.",
"machine_learning_url_description": "URL-ul serverului de învățare automată. Dacă sunt furnizate mai multe URL-uri, fiecare server va fi încercat pe rând, până când unul răspunde cu succes, în ordine de la primul până la ultimul. Serverele care nu răspund vor fi ignorate temporar până revin online.",
"maintenance_backup_management": "Gestionare backup",
"maintenance_delete_backup": "Sterge Backup",
"maintenance_delete_backup_description": "Acest fisier va fi sters permanent.",
"maintenance_delete_error": "Stergerea backup-ului nu a reusit.",
"maintenance_integrity_check": "Verifică",
"maintenance_integrity_check_all": "Verifică tot",
"maintenance_restore_backup": "Restaureaza Backup",
"maintenance_restore_backup_description": "Immich va fi șters si restaurat din backup-ul ales. Va fi creat un nou backup înainte de a continua.",
"maintenance_restore_backup_different_version": "Acest backup a fost creat folosind o versiune diferita de Immich!",
+32
View File
@@ -108,6 +108,21 @@
"image_thumbnail_quality_description": "Качество миниатюр от 1 до 100. Чем выше качество, тем лучше, но при этом создаются файлы большего размера и может снизиться скорость отклика приложения.",
"image_thumbnail_title": "Настройки миниатюр",
"import_config_from_json_description": "Импортировать конфигурацию системы, загрузив JSON файл настроек",
"integrity_checks_checksum_files": "Контрольные суммы файлов",
"integrity_checks_checksum_files_description": "Настройка проверки контрольных сумм",
"integrity_checks_checksum_files_enable_description": "Включить проверку контрольных сумм",
"integrity_checks_checksum_files_percentage_limit": "Процентный лимит",
"integrity_checks_checksum_files_percentage_limit_description": "Максимальное количество объектов в процентах от 0,01 до 1, обрабатываемых за раз при проверке контрольных сумм.",
"integrity_checks_checksum_files_time_limit": "Временной лимит",
"integrity_checks_checksum_files_time_limit_description": "Максимальная продолжительность выполнения итерации по проверке контрольных сумм (мс)",
"integrity_checks_missing_files": "Отсутствующие файлы",
"integrity_checks_missing_files_description": "Настройка частоты и включение/отключение функции поиска отсутствующих файлов",
"integrity_checks_missing_files_enable_description": "Включить функцию поиска отсутствующих файлов",
"integrity_checks_settings": "Проверка целостности",
"integrity_checks_settings_description": "Управление интервалами функции проверки целостности",
"integrity_checks_untracked_files": "Неотслеживаемые файлы",
"integrity_checks_untracked_files_description": "Настройка частоты и включение/отключение функции поиска неотслеживаемых файлов",
"integrity_checks_untracked_files_enable_description": "Включение функции поиска неотслеживаемых файлов",
"job_concurrency": "Число параллельных потоков задачи {job}",
"job_created": "Задача создана",
"job_not_concurrency_safe": "Эта задача не обеспечивает безопасность параллельности выполнения.",
@@ -1461,6 +1476,7 @@
"never": "никогда",
"new_album": "Новый альбом",
"new_api_key": "Новый API ключ",
"new_feature": "Новая функциональность",
"new_password": "Новый пароль",
"new_person": "Новый человек",
"new_pin_code": "Новый PIN-код",
@@ -1521,6 +1537,8 @@
"obtainium_configurator": "Настройка Obtainium",
"obtainium_configurator_instructions": "Для установки и обновления Android приложения Immich напрямую из источников на GitHub (минуя магазины приложений) можно использовать Obtainium. Создайте новый API ключ и укажите архитектуру приложения для формирования ссылки для Obtainium.",
"ocr": "Текст (OCR)",
"ocr_body": "Immich научился распознавать текст на изображениях, теперь поиск возможен и по этому содержимому.",
"ocr_title": "Поиск текста на фотографиях",
"official_immich_resources": "Официальные ресурсы Immich",
"offline": "Недоступен",
"offset": "Смещение",
@@ -1539,6 +1557,8 @@
"open": "Открыть",
"open_calendar": "Открыть календарь",
"open_in_browser": "Открыть в браузере",
"open_in_immich_body": "Установите Immich в качестве галереи по умолчанию на Android, чтобы открывать фотографии в Immich из других приложений.",
"open_in_immich_title": "Открывать фотографии в Immich",
"open_in_map_view": "Открыть в режиме просмотра карты",
"open_in_openstreetmap": "Открыть в OpenStreetMap",
"open_the_search_filters": "Открыть фильтры поиска",
@@ -1697,7 +1717,9 @@
"recent": "Недавние",
"recent_searches": "Недавние поисковые запросы",
"recently_added": "Недавно добавленные",
"recently_added_body": "Переход к отдельной странице с недавно добавленными объектами.",
"recently_added_page_title": "Недавно добавленные",
"recently_added_title": "Недавно добавленные",
"recently_taken": "Недавно снято",
"refresh": "Обновить",
"refresh_encoded_videos": "Обновить закодированные видео",
@@ -1904,6 +1926,8 @@
"share_link": "Создать ссылку",
"share_original": "Оригинал (большой размер)",
"share_preview": "Миниатюра (маленький размер)",
"share_quality_body": "Нажмите и удерживайте кнопку «Поделиться», чтобы выбрать качество изображения перед отправкой.",
"share_quality_title": "Выберите качество для функции \"Поделиться\"",
"shared": "Общиe",
"shared_album_activities_input_disable": "Комментарии отключены",
"shared_album_activity_remove_content": "Удалить сообщение?",
@@ -1985,16 +2009,19 @@
"sign_out": "Выход",
"sign_up": "Зарегистрироваться",
"size": "Размер",
"skip": "Пропустить",
"skip_to_content": "Перейти к содержанию",
"skip_to_folders": "Перейти к папкам",
"skip_to_tags": "Перейти к тегам",
"slideshow": "Слайд-шоу",
"slideshow_body": "Устройтесь поудобнее и наслаждайтесь полноэкранным просмотром фотографий в режиме слайд-шоу.",
"slideshow_metadata_overlay_mode": "Вид информации",
"slideshow_metadata_overlay_mode_description_only": "Только описание",
"slideshow_metadata_overlay_mode_full": "Полный",
"slideshow_repeat": "Зациклить слайд-шоу",
"slideshow_repeat_description": "Повторять слайд-шоу после его окончания",
"slideshow_settings": "Настройки слайд-шоу",
"slideshow_title": "Слайдшоу",
"smart_album": "Смарт-альбом",
"some_assets_already_have_a_location_warning": "Некоторые из выбранных объектов уже содержат данные о местоположении",
"sort_albums_by": "Сортировать альбомы по...",
@@ -2157,6 +2184,8 @@
"upload_status_errors": "Ошибки",
"upload_status_uploaded": "Загружено",
"upload_success": "Загрузка прошла успешно. Обновите страницу, чтобы увидеть новые объекты.",
"upload_to_album_body": "Для пользователей, которые не используют функцию ручной загрузки, теперь можно добавлять фотографии с устройства сразу в нужный альбом по мере их загрузки. Больше не нужно сначала загружать их, а затем добавлять в альбом.",
"upload_to_album_title": "Загружать сразу в альбом",
"upload_to_immich": "Загрузка в Immich ({count})",
"uploading": "Загружается",
"uploading_media": "Выполняется загрузка",
@@ -2224,6 +2253,9 @@
"week": "Неделя",
"welcome": "Добро пожаловать",
"welcome_to_immich": "Добро пожаловать в Immich",
"whats_new": "Что нового",
"whats_new_settings_subtitle": "Узнайте об нововведениях в Immich",
"whats_new_version": "Версия {version}",
"when": "Когда",
"width": "Ширина",
"wifi_name": "Имя сети",
+246 -2
View File
@@ -6,7 +6,7 @@
"action": "Aksion",
"action_common_update": "Përditëso",
"action_description": "Një grup veprimesh për t'u kryer në asetet e filtruara",
"actions": "Aksione",
"actions": "Veprime",
"active": "Aktiv",
"active_count": "Aktive: {count}",
"activity": "Aktivitet",
@@ -183,18 +183,23 @@
"machine_learning_smart_search_enabled": "Aktivizo kërkimin inteligjent",
"machine_learning_smart_search_enabled_description": "Nëse çaktivizohet, imazhet nuk do të kodohen për kërkim inteligjent.",
"machine_learning_url_description": "URL-ja e serverit të të mësuarit automatik. Nëse jepet më shumë se një URL, secili server do të provohet një nga një derisa njëri të përgjigjet me sukses, në rend nga i pari tek i fundit. Serverët që nuk përgjigjen do të injorohen përkohësisht derisa të kthehen në linjë.",
"maintenance_backup_management": "Menaxhimi i kopjeve rezervë",
"maintenance_delete_backup": "Fshi kopjen rezervë",
"maintenance_delete_backup_description": "Ky skedar do të fshihet në mënyrë të pakthyeshme.",
"maintenance_delete_error": "Fshirja e kopjes rezervë dështoi.",
"maintenance_integrity_check": "Kontrollo",
"maintenance_integrity_check_all": "Kontrollo të gjitha",
"maintenance_integrity_checksum_mismatch": "Mospërputhje e shumës së kontrollit",
"maintenance_integrity_checksum_mismatch_description": "Skedarët për të cilët checksumi në disk nuk përputhet me checksumin që Immich ka ruajtur në bazën e të dhënave të tij.",
"maintenance_integrity_checksum_mismatch_job": "Kontrolloni për mospërputhje të shumës së kontrollit",
"maintenance_integrity_checksum_mismatch_refresh_job": "Rifresko raportet e mospërputhjes së shumës së kontrollit",
"maintenance_integrity_missing_file": "Skedarët që mungojnë",
"maintenance_integrity_missing_file_description": "Skedarët që Immich i ka gjurmuar në bazën e të dhënave të tij, por që nuk ekzistojnë në sistemin e skedarëve.",
"maintenance_integrity_missing_file_job": "Kontrolloni për skedarë që mungojnë",
"maintenance_integrity_missing_file_refresh_job": "Rifresko raportet e skedarëve që mungojnë",
"maintenance_integrity_report": "Raporti i Integritetit",
"maintenance_integrity_untracked_file": "Skedarët e Pagjurmuar",
"maintenance_integrity_untracked_file_description": "Skedarët në direktoritë e Immich-it për të cilët Immich nuk ka asnjë regjistër.",
"maintenance_integrity_untracked_file_job": "Kontrolloni për skedarë të pagjurmuar",
"maintenance_integrity_untracked_file_refresh_job": "Rifresko raportet e skedarëve të pagjurmuar",
"maintenance_restore_backup": "Rivendos rezervën",
@@ -318,8 +323,224 @@
"reset_settings_to_default": "Rivendos cilësimet në ato të parazgjedhura",
"reset_settings_to_recent_saved": "Rivendos cilësimet në cilësimet e ruajtura së fundmi",
"scanning_library": "Duke skanuar bibliotekën",
"search_jobs": "Kërko punë…"
"search_jobs": "Kërko punë…",
"send_welcome_email": "Dërgo email mirëseardhjeje",
"server_external_domain_settings": "Domeni i jashtëm",
"server_external_domain_settings_description": "Domeni i përdorur për lidhjet e jashtme",
"server_public_users": "Përdoruesit publikë",
"server_public_users_description": "Të gjithë përdoruesit (emri dhe emaili) shfaqen kur shtohet një përdorues në albumet e përbashkëta. Kur është çaktivizuar, lista e përdoruesve do të jetë e disponueshme vetëm për përdoruesit admin.",
"server_settings": "Caktimet e serverit",
"server_settings_description": "Menaxho cilësimet e serverit",
"server_stats_page_description": "Faqja e statistikave të serverit administrativ",
"server_welcome_message": "Mesazh mirëseardhjeje",
"server_welcome_message_description": "Një mesazh që shfaqet në faqen e hyrjes.",
"settings_page_description": "Faqja e cilësimeve të administrimit",
"sidecar_job": "Metadata sidecar",
"sidecar_job_description": "Zbulo ose sinkronizo metadatat sidecar nga sistemi i skedarëve",
"slideshow_duration_description": "Numri i sekondave për me shfaqë çdo imazh",
"smart_search_job_description": "Ekzekuto mësim makine mbi asetet për me mbështetë kërkimin inteligjent",
"storage_template_date_time_description": "Markatimpi i krijimit të asetit përdoret për informacionin e datës dhe orës",
"storage_template_date_time_sample": "Koha e mostrës {date}",
"storage_template_enable_description": "Aktivizo motorin e shabllonit të ruajtjes",
"storage_template_hash_verification_enabled": "Verifikimi i hash-it asht aktivizuar",
"storage_template_hash_verification_enabled_description": "Aktivizon verifikimin e hash-it, mos e çaktivizo këtë nëse nuk je i sigurt për implikacionet",
"storage_template_migration": "Migrimi i shabllonit të ruajtjes",
"storage_template_migration_description": "Apliko <link>{template}</link> aktual te asetet e ngarkuara ma parë",
"storage_template_migration_info": "Shablloni i ruajtjes do t'i konvertojë të gjitha shtesat në shkronja të vogla. Ndryshimet e shabllonit do të aplikohen vetëm për asetet e reja. Për t'i aplikuar retroaktivisht te asetet e ngarkuara ma parë, ekzekuto <link>{job}</link>.",
"storage_template_migration_job": "Puna e migrimit të shabllonit të ruajtjes",
"storage_template_more_details": "Për ma shumë detaje rreth kësaj veçorie, referoju <template-link>Shabllonit të Ruajtjes</template-link> dhe <implications-link>implikacioneve</implications-link> të tij",
"storage_template_onboarding_description_v2": "Kur aktivizohet, kjo veçori do t'i organizojë automatikisht skedarët bazuar në një shabllon të definuar nga përdoruesi. Për ma shumë informacion, shih <link>dokumentacionin</link>.",
"storage_template_path_length": "Kufiri afërsisht i gjatësisë së shtegut: <b>{length, number}</b>/{limit, number}",
"storage_template_settings": "Shablloni i ruajtjes",
"storage_template_settings_description": "Menaxho strukturën e dosjeve dhe emrin e skedarit të asetit të ngarkuar",
"storage_template_user_label": "<code>{label}</code> asht Etiketa e Ruajtjes e përdoruesit",
"system_settings": "Cilësimet e sistemit",
"tag_cleanup_job": "Pastrimi i etiketave",
"template_email_available_tags": "Mund të përdorësh variablat e mëposhtme në shabllonin tënd: {tags}",
"template_email_if_empty": "Nëse shablloni asht bosh, do të përdoret emaili i parazgjedhur.",
"template_email_invite_album": "Shablloni i ftesës në album",
"template_email_preview": "Pamja paraprake",
"template_email_settings": "Shabllonet e emailit",
"template_email_update_album": "Shablloni i përditësimit të albumit",
"template_email_welcome": "Shablloni i emailit të mirëseardhjes",
"template_settings": "Shablonet e njoftimeve",
"template_settings_description": "Menaxho shablonet e personalizuara për njoftimet",
"theme_custom_css_settings": "CSS i personalizuar",
"theme_custom_css_settings_description": "Cascading Style Sheets lejojnë personalizimin e dizajnit të Immich.",
"theme_settings": "Cilësimet e temës",
"theme_settings_description": "Menaxho personalizimin e ndërfaqes web të Immich",
"thumbnail_generation_job": "Gjenero miniatura",
"thumbnail_generation_job_description": "Gjenero miniatura të mëdha, të vogla dhe të turbullta për çdo aset, si dhe miniatura për çdo person",
"transcoding_acceleration_api": "API-ja e përshpejtimit",
"transcoding_acceleration_api_description": "API-ja që do të ndërveprojë me pajisjen tënde për me përshpejtuar transkodimin. Ky cilësim asht 'best effort': do të kthehet te transkodimi softuerik nëse dështon. VP9 mund të funksionojë ose jo varësisht nga hardueri yt.",
"transcoding_acceleration_nvenc": "NVENC (kërkon GPU NVIDIA)",
"transcoding_acceleration_qsv": "Quick Sync (kërkon CPU Intel brez 7 ose ma të ri)",
"transcoding_acceleration_rkmpp": "RKMPP (vetëm në SOC-et Rockchip)",
"transcoding_acceleration_vaapi": "VAAPI",
"transcoding_accepted_audio_codecs": "Kodeckët e audios të pranuara",
"transcoding_accepted_audio_codecs_description": "Zgjidh cilët kodeckë audio nuk kanë nevojë të transkodihen. Përdoret vetëm për politika të caktuara transkodimi.",
"transcoding_accepted_containers": "Kontejnerët e pranuar",
"transcoding_accepted_containers_description": "Zgjidh cilët formate kontejneri nuk kanë nevojë të ripakohen në MP4. Përdoret vetëm për politika të caktuara transkodimi.",
"transcoding_accepted_video_codecs": "Kodeckët e videos të pranuara",
"transcoding_accepted_video_codecs_description": "Zgjidh cilët kodeckë video nuk kanë nevojë të transkodihen. Përdoret vetëm për politika të caktuara transkodimi.",
"transcoding_advanced_options_description": "Opsione që shumica e përdoruesve nuk kanë nevojë t'i ndryshojnë",
"transcoding_audio_codec": "Kodeku i audios",
"transcoding_audio_codec_description": "Opus asht opsioni me cilësi ma të lartë, por ka përputhshmëri ma të ulët me pajisje ose softuer të vjetër.",
"transcoding_bitrate_description": "Videot ma të larta se bitrate maksimal ose jo në format të pranuar",
"transcoding_codecs_learn_more": "Për me mësu ma shumë rreth terminologjisë këtu, referoju dokumentacionit FFmpeg për <h264-link>kodekun H.264</h264-link>, <hevc-link>kodekun HEVC</hevc-link> dhe <vp9-link>kodekun VP9</vp9-link>.",
"transcoding_constant_quality_mode": "Mënyra e cilësisë konstante",
"transcoding_constant_quality_mode_description": "ICQ asht ma e mirë se CQP, por disa pajisje të përshpejtimit harduerik nuk e mbështesin këtë mënyrë. Ky opsion do të preferojë mënyrën e specifikuar kur përdor kodim bazuar në cilësi. Injorohet nga NVENC pasi nuk mbështet ICQ.",
"transcoding_constant_rate_factor": "Faktori i normës konstante (-crf)",
"transcoding_constant_rate_factor_description": "Niveli i cilësisë së videos. Vlerat tipike janë 23 për H.264, 28 për HEVC, 31 për VP9 dhe 35 për AV1. Vlera ma e ulët asht ma e mirë, por prodhon skedarë ma të mëdhenj.",
"transcoding_disabled_description": "Mos transkodo asnjë video, mund të prish luajtjen në disa klientë",
"transcoding_encoding_options": "Opsionet e kodimit",
"transcoding_encoding_options_description": "Vendos kodeckë, rezolucion, cilësi dhe opsione të tjera për videot e koduara",
"transcoding_hardware_acceleration": "Përshpejtimi harduerik",
"transcoding_hardware_acceleration_description": "Eksperimental: transkodim ma i shpejtë por mund të ulë cilësinë me të njëjtin bitrate",
"transcoding_hardware_decoding": "Dekodimi harduerik",
"transcoding_hardware_decoding_setting_description": "Aktivizon përshpejtimin nga fillimi në fund në vend se të përshpejtojë vetëm kodimin. Mund të mos funksionojë me të gjitha videot.",
"transcoding_max_b_frames": "B-frames maksimale",
"transcoding_max_b_frames_description": "Vlerat ma të larta përmirësojnë efikasitetin e kompresimit, por ngadalësojnë kodimin. Mund të mos jetë i përputhshëm me përshpejtimin harduerik në pajisje të vjetra. 0 çaktivizonte B-frames, ndërsa -1 e vendos këtë vlerë automatikisht.",
"transcoding_max_bitrate": "Bitrate maksimal",
"transcoding_max_bitrate_description": "Vendosja e bitrate-it maksimal mund t'i bëjë madhësitë e skedarëve ma të parashikueshme me kosto të vogël në cilësi. Në 720p, vlerat tipike janë 2600 kbit/s për VP9 ose HEVC, ose 4500 kbit/s për H.264. Çaktivizohet nëse vendoset në 0. Kur nuk specifikohet njësia, supozohet k (kbit/s).",
"transcoding_max_keyframe_interval": "Intervali maksimal i keyframe",
"transcoding_max_keyframe_interval_description": "Vendos distancën maksimale të kornizave ndërmjet keyframe-ve. Vlerat ma të ulëta përkeqësojnë efikasitetin e kompresimit, por përmirësojnë kohët e kërkimit dhe mund të përmirësojnë cilësinë në skenat me lëvizje të shpejtë. 0 e vendos këtë vlerë automatikisht.",
"transcoding_optimal_description": "Videot ma të larta se rezolucioni i synuar ose jo në format të pranuar",
"transcoding_policy": "Politika e transkodimit",
"transcoding_policy_description": "Vendos kur do të transkodihet një video",
"transcoding_preferred_hardware_device": "Pajisja harduerike e preferuar",
"transcoding_preferred_hardware_device_description": "Aplikohet vetëm për VAAPI dhe QSV. Vendos nyjen dri të përdorur për transkodim harduerik.",
"transcoding_preset_preset": "Paravendosja (-preset)",
"transcoding_preset_preset_description": "Shpejtësia e kompresimit. Paravendosjet ma të ngadalta prodhojnë skedarë ma të vegjël dhe rrisin cilësinë kur synon një bitrate të caktuar. VP9 injoron shpejtësi mbi 'faster'.",
"transcoding_realtime": "Transkodimi në kohë reale [EKSPERIMENTAL]",
"transcoding_realtime_description": "Lejon transkodimin të kryhet në kohë reale ndërsa videoja transmetohet. Aktivizon ndërrimin e cilësisë, por mund të shkaktojë vonesë ma të lartë luajtjeje dhe ngecje varësisht nga kapacitetet e serverit.",
"transcoding_realtime_enabled": "Aktivizo transkodimin në kohë reale",
"transcoding_realtime_enabled_description": "Nëse çaktivizohet, serveri do të refuzojë me fillu sesione të reja transkodimi në kohë reale.",
"transcoding_reference_frames": "Kornizat referencë",
"transcoding_reference_frames_description": "Numri i kornizave për t'u referuar kur kompresohet një kornizë e dhënë. Vlerat ma të larta përmirësojnë efikasitetin e kompresimit, por ngadalësojnë kodimin. 0 e vendos këtë vlerë automatikisht.",
"transcoding_required_description": "Vetëm videot jo në format të pranuar",
"transcoding_settings": "Cilësimet e transkodimit të videos",
"transcoding_settings_description": "Menaxho cilat video të transkodihen dhe si t'i procesosh ato",
"transcoding_target_resolution": "Rezolucioni i synuar",
"transcoding_target_resolution_description": "Rezolucionet ma të larta mund të ruajnë ma shumë detaje por marrin ma shumë kohë për t'u koduar, kanë madhësi skedarësh ma të mëdha dhe mund të reduktojnë reagimin e aplikacionit.",
"transcoding_temporal_aq": "Temporal AQ",
"transcoding_temporal_aq_description": "Aplikohet vetëm për NVENC. Kuantizimi adaptiv temporal rrit cilësinë e skenave me shumë detaje dhe lëvizje të ulët. Mund të mos jetë i përputhshëm me pajisje të vjetra.",
"transcoding_threads": "Threads",
"transcoding_threads_description": "Vlerat ma të larta çojnë në kodim ma të shpejtë, por lënë ma pak hapësirë për serverin me procesuar detyra të tjera ndërsa asht aktiv. Kjo vlerë nuk duhet të jetë ma e madhe se numri i bërthamave CPU. Maksimizon përdorimin nëse vendoset në 0.",
"transcoding_tone_mapping": "Tone-mapping",
"transcoding_tone_mapping_description": "Përpiqet të ruajë pamjen e videove HDR kur konvertohen në SDR. Çdo algoritëm bën kompromise të ndryshme për ngjyrën, detajet dhe ndriçimin. Hable ruan detajet, Mobius ruan ngjyrën dhe Reinhard ruan ndriçimin.",
"transcoding_transcode_policy": "Politika e transkodimit",
"transcoding_transcode_policy_description": "Politika për kur duhet të transkodihet një video. Videot HDR dhe videot me format pikselësh tjetër se YUV 4:2:0 do të transkodihen gjithmonë (përveç nëse transkodimi asht çaktivizuar).",
"transcoding_two_pass_encoding": "Kodim me dy kalime",
"transcoding_two_pass_encoding_setting_description": "Transkodo në dy kalime për me prodhuar video ma mirë të koduara. Kur bitrate maksimal asht aktivizuar (i nevojshëm për të punuar me H.264 dhe HEVC), kjo mënyrë përdor një gamë bitrate bazuar në bitrate maksimal dhe injoron CRF. Për VP9, CRF mund të përdoret nëse bitrate maksimal asht çaktivizuar.",
"transcoding_video_codec": "Kodeku i videos",
"transcoding_video_codec_description": "VP9 ka efikasitet të lartë dhe përputhshmëri web, por merr ma shumë kohë për t'u transkoduar. HEVC funksionon ngjashëm, por ka përputhshmëri ma të ulët web. H.264 asht i përputhshëm gjerësisht dhe i shpejtë për t'u transkoduar, por prodhon skedarë shumë ma të mëdhenj. AV1 asht kodeku ma efikas por mungon mbështetja në pajisje të vjetra.",
"trash_enabled_description": "Aktivizo veçoritë e koshit",
"trash_number_of_days": "Numri i ditëve",
"trash_number_of_days_description": "Numri i ditëve për të mbajtur asetet në kosh para fshirjes përgjithmonë",
"trash_settings": "Cilësimet e koshit",
"trash_settings_description": "Menaxho cilësimet e koshit",
"unlink_all_oauth_accounts": "Shkyç të gjitha llogaritë OAuth",
"unlink_all_oauth_accounts_description": "Mos harro me shkyçë të gjitha llogaritë OAuth para migrimit te një ofrues i ri.",
"unlink_all_oauth_accounts_prompt": "A je i sigurt që dëshiron të shkyçësh të gjitha llogaritë OAuth? Kjo do të rivendosë OAuth ID-në për çdo përdorues dhe nuk mund të zhbëhet.",
"user_cleanup_job": "Pastrimi i përdoruesve",
"user_delete_delay": "Llogaria dhe asetet e <b>{user}</b> do të planifikohen për fshirje të përhershme pas {delay, plural, one {# ditë} other {# ditësh}}.",
"user_delete_delay_settings": "Vonesa e fshirjes",
"user_delete_delay_settings_description": "Numri i ditëve pas heqjes për me fshirë përgjithmonë llogarinë dhe asetet e një përdoruesi. Puna e fshirjes së përdoruesit ekzekutohet në mesnatë për të kontrolluar përdoruesit gati për fshirje. Ndryshimet e këtij cilësimi do të vlerësohen në ekzekutimin tjetër.",
"user_delete_immediately": "Llogaria dhe asetet e <b>{user}</b> do të radhiten për fshirje të përhershme <b>menjëherë</b>.",
"user_delete_immediately_checkbox": "Radho përdoruesin dhe asetet për fshirje të menjëhershme",
"user_details": "Detajet e përdoruesit",
"user_management": "Menaxhimi i përdoruesve",
"user_password_has_been_reset": "Fjalëkalimi i përdoruesit asht rivendosur:",
"user_password_reset_description": "Ju lutem jepja përdoruesit fjalëkalimin e përkohshëm dhe informoje se do të duhet ta ndryshojë fjalëkalimin në hyrjen e tij të ardhshme.",
"user_restore_description": "Llogaria e <b>{user}</b> do të restaurohet.",
"user_restore_scheduled_removal": "Restauro përdoruesin - heqja e planifikuar më {date, date, long}",
"user_settings": "Cilësimet e përdoruesit",
"user_settings_description": "Menaxho cilësimet e përdoruesit",
"user_successfully_removed": "Përdoruesi {email} u hoq me sukses.",
"users_page_description": "Faqja e administratorit për përdoruesit",
"version_check_channel": "Kanali i lëshimit",
"version_check_channel_description": "Zgjidh kanalin e lëshimit për të cilin dëshiron të marrësh njoftime versioni",
"version_check_enabled_description": "Aktivizo kontrollin e versionit",
"version_check_implications": "Veçoria e kontrollit të versionit mbështetet në komunikim periodik me {server}",
"version_check_settings": "Kontrolli i versionit",
"version_check_settings_description": "Aktivizo/çaktivizo njoftimin e versionit të ri",
"video_conversion_job": "Transkodo videot",
"video_conversion_job_description": "Transkodo videot për përputhshmëri ma të gjerë me shfletuesit dhe pajisjet"
},
"admin_email": "Email i administratorit",
"admin_password": "Fjalëkalimi i administratorit",
"administration": "Administrimi",
"advanced": "Të avancuara",
"advanced_settings_clear_image_cache": "Pastro cache-in e imazheve",
"advanced_settings_clear_image_cache_error": "Dështoi pastrimi i cache-it të imazheve",
"advanced_settings_clear_image_cache_success": "U pastrua me sukses {size}",
"advanced_settings_log_level_title": "Niveli i regjistrit: {level}",
"advanced_settings_prefer_remote_subtitle": "Disa pajisje janë shumë të ngadalta për të ngarkuar miniatura nga asetet lokale. Aktivizo këtë cilësim për të ngarkuar imazhe nga distanca.",
"advanced_settings_prefer_remote_title": "Prefero imazhe nga distanca",
"advanced_settings_proxy_headers_subtitle": "Defino header-at proxy që Immich duhet t'i dërgojë me çdo kërkesë rrjeti",
"advanced_settings_proxy_headers_title": "Header-at e personalizuara proxy [EKSPERIMENTAL]",
"advanced_settings_readonly_mode_subtitle": "Aktivizon mënyrën vetëm-lexim ku fotot mund të shikohen vetëm, gjëra si zgjedhja e imazheve të shumta, ndarja, transmetimi, fshirja janë të çaktivizuara. Aktivizo/çaktivizo vetëm-lexim nëpërmjet avatarit të përdoruesit nga ekrani kryesor",
"advanced_settings_readonly_mode_title": "Mënyra vetëm-lexim",
"advanced_settings_sync_remote_deletions_subtitle": "Fshi ose restauro automatikisht një aset në këtë pajisje kur kjo veprim kryhet në web",
"advanced_settings_sync_remote_deletions_title": "Sinkronizo fshirjet nga distanca [EKSPERIMENTAL]",
"advanced_settings_tile_subtitle": "Cilësimet e avancuara të përdoruesit",
"advanced_settings_troubleshooting_subtitle": "Aktivizo veçori shtesë për zgjidhjen e problemeve",
"advanced_settings_troubleshooting_title": "Zgjidhja e problemeve",
"age_months": "Mosha {months, plural, one {# muaj} other {# muaj}}",
"age_year_months": "Mosha 1 vit, {months, plural, one {# muaj} other {# muaj}}",
"age_years": "{years, plural, other {Mosha #}}",
"album": "Album",
"album_added": "Albumi u shtua",
"album_added_notification_setting_description": "Merr njoftim me email kur shtohet në një album të ndarë",
"album_cover_updated": "Kopertina e albumit u përditësua",
"album_delete_confirmation": "A je i sigurt që dëshiron të fshish albumin {album}?",
"album_delete_confirmation_description": "Nëse ky album asht i ndarë, përdoruesit e tjerë nuk do të mund ta aksesojnë ma.",
"album_deleted": "Albumi u fshi",
"album_info_updated": "Informacioni i albumit u përditësua",
"album_name": "Emri i albumit",
"album_options": "Opsionet e albumit",
"album_remove_user": "Hiq përdoruesin?",
"album_remove_user_confirmation": "A je i sigurt që dëshiron të heqësh {user}?",
"album_search_not_found": "Nuk u gjet asnjë album që përputhet me kërkimin tënd",
"album_share_no_users": "Duket se e ke ndarë këtë album me të gjithë përdoruesit ose nuk ke asnjë përdorues me të cilin të ndash.",
"album_summary": "Përmbledhja e albumit",
"album_updated": "Albumi u përditësua",
"album_updated_setting_description": "Merr njoftim me email kur një album i ndarë ka asete të reja",
"album_upload_assets": "Ngarko asete nga kompjuteri yt dhe shto në album",
"album_viewer_appbar_share_err_delete": "Dështoi fshirja e albumit",
"album_viewer_page_share_add_users": "Shto përdorues",
"album_with_link_access": "Lejo këdo me lidhjen të shohë fotot dhe njerëzit në këtë album.",
"albums": "Albumet",
"albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Albume}}",
"albums_default_sort_order": "Rendi i parazgjedhur i albumeve",
"albums_default_sort_order_description": "Rendi fillestar i aseteve kur krijohen albume të reja.",
"albums_feature_description": "Koleksione asetesh që mund të ndahen me përdoruesit e tjerë.",
"albums_on_device_count": "Albumet në pajisje ({count})",
"all": "Të gjithë",
"all_albums": "Të gjitha albumet",
"all_people": "Të gjithë njerëzit",
"all_photos": "Të gjitha fotot",
"all_videos": "Të gjitha videot",
"allow_dark_mode": "Lejo mënyrën e errët",
"allow_public_user_to_download": "Lejo përdoruesin publik të shkarkojë",
"allow_public_user_to_upload": "Lejo përdoruesin publik të ngarkojë",
"allowed": "I lejuar",
"alt_text_qr_code": "Imazh i kodit QR",
"always_keep": "Mbaj gjithmonë",
"always_keep_photos_hint": "Lirimi i hapësirës do t'i mbajë të gjitha fotot në këtë pajisje.",
"always_keep_videos_hint": "Lirimi i hapësirës do t'i mbajë të gjitha videot në këtë pajisje.",
"api_key": "Çelësi API",
"api_key_description": "Kjo vlerë do të tregohet vetëm një herë. Sigurohu me e kopjuar para se të mbyllësh dritaren.",
"api_key_empty": "Emri i çelësit API nuk duhet të jetë bosh",
"api_keys": "Çelësat API",
"app_architecture_variant": "Varianta (Arkitektura)",
"app_bar_signout_dialog_content": "A je i sigurt që dëshiron të dalësh?",
"back": "Mbrapa",
"close": "Mbyll",
"copy_image": "Kopjo imazhin",
"dark": "E errët",
"disabled": "I çaktivizuar",
"download_original": "Shkarko origjinalin",
"download_paused": "Shkarkimi u pezullua",
"download_settings": "Shkarko",
@@ -328,6 +549,29 @@
"downloading_asset_filename": "Duke shkarkuar asetin {filename}",
"downloading_from_icloud": "Duke shkarkuar nga iCloud",
"downloading_media": "Duke shkarkuar median",
"enable": "Aktivizo",
"error": "Gabim",
"expired": "Skaduar",
"image": "Imazhi",
"info": "Info",
"model": "Modeli",
"name": "Emri",
"none": "Asnjë",
"offline": "Jashtë linje",
"ok": "Në rregull",
"online": "Online",
"path": "Shtegu",
"refresh": "Rifresko",
"rename": "Riemërto",
"search": "Kërko",
"settings": "Cilësimet",
"size": "Madhësia",
"status": "Statusi",
"type": "Lloji",
"unknown": "E panjohur",
"upload_finished": "Ngarkimi përfundoi",
"username": "Emri i përdoruesit",
"version": "Versioni",
"you_dont_have_any_shared_links": "Nuk keni asnjë link të shpërndarë",
"your_wifi_name": "Emri i Wi-Fi tuaj",
"zoom_image": "Zmadho imazhin"
+6 -3
View File
@@ -1303,9 +1303,9 @@
"login_form_failed_login": "Kunde inte logga in. Kontrollera serverns webbadress, email och lösenord",
"login_form_handshake_exception": "Ett Undantag vid Handskakning med servern har skett. Aktivera stöd för självsignerade certifikat i inställningar om du använder ett självsignerat certifikat.",
"login_form_password_hint": "lösenord",
"login_form_server_empty": "Ange en server-URL.",
"login_form_server_error": "Kunde inte ansluta till servern.",
"login_has_been_disabled": "Inloggning har blivit inaktiverat.",
"login_form_server_empty": "Ange en server-URL",
"login_form_server_error": "Kunde inte ansluta till servern",
"login_has_been_disabled": "Inloggning har blivit inaktiverat",
"login_password_changed_error": "Ett fel uppstod vid uppdatering av ditt lösenord",
"login_password_changed_success": "Uppdatering av lösenord lyckades",
"logout_all_device_confirmation": "Är du säker på att du vill logga ut från alla enheter?",
@@ -1507,6 +1507,9 @@
"notes": "Notera",
"nothing_here_yet": "Inget här ännu",
"notification_backup_reliability": "Aktivera aviseringar för att förbättra säkerhetskopieringen i bakgrunden",
"notification_enabled_list_tile_content": "Immich använde notiser för bakgrundssäkerhetskopiering. Hantera dem på din enhets inställningar.",
"notification_enabled_list_tile_open_button": "Öppna inställningar",
"notification_enabled_list_tile_title": "Notiser aktiverade",
"notification_permission_dialog_content": "För att aktivera notiser, gå till Inställningar och välj tillåt.",
"notification_permission_list_tile_content": "Tillåt rättighet för att slå på notiser.",
"notification_permission_list_tile_enable_button": "Aktivera Notiser",
+84 -52
View File
@@ -108,6 +108,21 @@
"image_thumbnail_quality_description": "Chất lượng ảnh thu nhỏ từ 1-100. Càng cao càng tốt, nhưng sẽ tạo ra các tệp lớn hơn có thể làm giảm khả năng phản hồi của app.",
"image_thumbnail_title": "Cài đặt Ảnh thu nhỏ",
"import_config_from_json_description": "Nhập cấu hình hệ thống bằng cách tải lên tệp cấu hình JSON",
"integrity_checks_checksum_files": "Các tệp checksum",
"integrity_checks_checksum_files_description": "Thiết lập kiểm tra checksum",
"integrity_checks_checksum_files_enable_description": "Bật kiểm tra checksum",
"integrity_checks_checksum_files_percentage_limit": "Giới hạn tỷ lệ phần trăm",
"integrity_checks_checksum_files_percentage_limit_description": "Thiết lập tỷ lệ phần trăm tối đa (trong khoảng từ 0.01 đến 1) cho tần suất thực hiện kiểm tra checksum trong mỗi khoảng thời gian.",
"integrity_checks_checksum_files_time_limit": "Giới hạn thời gian",
"integrity_checks_checksum_files_time_limit_description": "Thiết lập thời lượng tối đa để thực hiện kiểm tra checksum trong mỗi khoảng thời gian. (ms)",
"integrity_checks_missing_files": "Những tệp bị thiếu",
"integrity_checks_missing_files_description": "Thiết lập tần suất và bật/tắt tính năng kiểm tra tệp bị thiếu",
"integrity_checks_missing_files_enable_description": "Bật tính năng kiểm tra tệp bị thiếu",
"integrity_checks_settings": "Kiểm tra tính toàn vẹn",
"integrity_checks_settings_description": "Quản lý khoảng thời gian kiểm tra tính toàn vẹn",
"integrity_checks_untracked_files": "Những tệp chưa được theo dõi",
"integrity_checks_untracked_files_description": "Thiết lập tần suất và bật/tắt việc kiểm tra các tệp chưa được theo dõi",
"integrity_checks_untracked_files_enable_description": "Bật tính năng kiểm tra các tệp chưa được theo dõi",
"job_concurrency": "{job} chạy đồng thời",
"job_created": "Tác vụ đã được tạo",
"job_not_concurrency_safe": "Tác vụ này không an toàn để chạy đồng thời.",
@@ -123,7 +138,7 @@
"library_remove_exclusion_pattern_prompt": "Bạn có chắc muốn xóa mẫu loại trừ này?",
"library_remove_folder_prompt": "Bạn có chắc muốn gỡ thư mục nhập này?",
"library_scanning": "Quét định kỳ",
"library_scanning_description": "Cấu hình quét thư viện định kỳ",
"library_scanning_description": "Thiết lập quét thư viện định kỳ",
"library_scanning_enable_description": "Bật quét thư viện định kỳ",
"library_settings": "Thư viện ngoài",
"library_settings_description": "Quản lý cài đặt thư viện ngoài",
@@ -476,16 +491,16 @@
"advanced_settings_clear_image_cache_error": "Lỗi khi xóa bộ nhớ đệm",
"advanced_settings_clear_image_cache_success": "Đã giải phóng thành công {size}",
"advanced_settings_log_level_title": "Phân loại log: {level}",
"advanced_settings_prefer_remote_subtitle": "Việc tải ảnh thu nhỏ từ tài nguyên trên một số thiết bị có thể diễn ra chậm. Kích hoạt cài đặt này để tải ảnh từ máy chủ.",
"advanced_settings_prefer_remote_subtitle": "Một số thiết bị nạp ảnh thu nhỏ từ tài nguyên thiết bị rất chậm. Bật để nạp ảnh từ máy chủ.",
"advanced_settings_prefer_remote_title": "Ưu tiên ảnh từ máy chủ",
"advanced_settings_proxy_headers_subtitle": "Xác định các tiêu đề proxy Immich sẽ gửi kèm mỗi yêu cầu mạng",
"advanced_settings_proxy_headers_title": "Tùy chỉnh tiêu đề proxy [THỬ NGHIỆM]",
"advanced_settings_readonly_mode_subtitle": "Chế độ chỉ-xem chỉ cho phép xem ảnh, các tính năng như chọn nhiều ảnh, chia sẻ, phát, xóa đều bị vô hiệu hóa. Bật/Tắt chế độ chỉ-xem thông qua ảnh đại diện người dùng từ màn hình chính",
"advanced_settings_readonly_mode_subtitle": "Chỉ dùng ứng dụng để xem ảnh - các tính năng khác như chọn nhiều mục, chia sẻ, phát, xóa đều bị vô hiệu hóa. Nhấn giữ ảnh đại diện người dùng từ màn hình chính để bật/tắt chế độ.",
"advanced_settings_readonly_mode_title": "Chế độ chỉ-xem",
"advanced_settings_sync_remote_deletions_subtitle": "Tự động xóa hoặc khôi phục tài nguyên trên thiết bị này khi bạn thao tác trên web",
"advanced_settings_sync_remote_deletions_title": "Đồng bộ việc xóa từ thiết bị khác [THỬ NGHIỆM]",
"advanced_settings_tile_subtitle": "Dành cho người dùng nâng cao",
"advanced_settings_troubleshooting_subtitle": "Bật các tính năng bổ sung để xử lý sự cố",
"advanced_settings_troubleshooting_subtitle": "Bật tính năng bổ sung để xử lý sự cố",
"advanced_settings_troubleshooting_title": "Xử lý sự cố",
"age_months": "{months, plural, one {# tháng} other {# tháng}} tuổi",
"age_year_months": "1 tuổi, {months, plural, one {# tháng} other {# tháng}}",
@@ -544,16 +559,16 @@
"appears_in": "Xuất hiện trong",
"apply_count": "Áp dụng ({count, number})",
"archive": "Lưu trữ",
"archive_action_prompt": "{count} đã được thêm vào Lưu trữ",
"archive_action_prompt": "Đã thêm ({count}) vào Lưu trữ",
"archive_or_unarchive_photo": "Lưu trữ hoặc bỏ lưu trữ ảnh",
"archive_size": "Dung lượng tệp nén",
"archive_size_description": "Cấu hình dung lượng tệp nén để tải xuống (đơn vị GiB)",
"archive_size_description": "Thiết lập dung lượng tệp nén tải xuống (đơn vị GiB)",
"archived": "Lưu trữ",
"archived_count": "{count, plural, other {Đã lưu trữ # mục}}",
"are_these_the_same_person": "Đây có phải cùng một người không?",
"are_you_sure_to_do_this": "Bạn có chắc muốn thực hiện điều này?",
"asset_added_to_album": "Đã thêm vào album",
"asset_adding_to_album": "Đang thêm vào album…",
"asset_adding_to_album": "Thêm vào album…",
"asset_created": "Đã tạo tài nguyên",
"asset_day_count": "{date}: {count, plural, one {# tài nguyên} other {# tài nguyên}}",
"asset_description_updated": "Mô tả tài nguyên đã được cập nhật",
@@ -630,7 +645,7 @@
"backup_info_card_assets": "tài nguyên",
"backup_options": "Tùy chọn sao lưu",
"backup_settings_subtitle": "Cài đặt việc tải lên",
"backward": "Lùi lại",
"backward": "Lùi",
"battery_optimization_backup_reliability": "Tắt tính năng tiết kiệm pin để đảm bảo quá trình sao lưu nền",
"biometric_auth_enabled": "Đã bật xác thực sinh trắc học",
"biometric_locked_out": "Bạn đã bị khóa xác thực bằng sinh trắc học",
@@ -657,7 +672,7 @@
"cannot_undo_this_action": "Bạn không thể hoàn tác hành động này!",
"cannot_update_the_description": "Không thể cập nhật mô tả",
"cast": "Chiếu",
"cast_description": "Cấu hình các thiết bị chiếu khả dụng",
"cast_description": "Thiết lập thiết bị chiếu khả dụng",
"change": "Thay đổi",
"change_date": "Thay đổi ngày",
"change_description": "Thay đổi mô tả",
@@ -684,11 +699,11 @@
"choose": "Chọn",
"choose_matching_people_to_merge": "Chọn những người trùng khớp để hợp nhất",
"city": "Thành phố",
"cleanup_confirm_description": "Immich phát hiện {count} tài nguyên (được tạo ra trước {date}) được sao lưu trên máy chủ. Bạn có muốn xóa bản sao được lưu trên thiết bị này không?",
"cleanup_confirm_description": "Immich tìm thấy {count} tài nguyên (được tạo ra trước {date}) được sao lưu trên máy chủ. Bạn có muốn xóa phiên bản của chúng lưu trên thiết bị này không?",
"cleanup_confirm_prompt_title": "Xóa khỏi thiết bị này?",
"cleanup_deleted_assets": "Đã chuyển {count} tài nguyên vào thùng rác",
"cleanup_deleting": "Đang chuyển vào thùng rác...",
"cleanup_found_assets": "Phát hiện {count} tài nguyên được sao lưu",
"cleanup_found_assets": "Tìm thấy {count} tài nguyên đã sao lưu",
"cleanup_found_assets_with_size": "Phát hiện {count} tài nguyên đã sao lưu ({size})",
"cleanup_icloud_shared_albums_excluded": "Những album được chia sẻ trên iCloud không nằm trong phạm vi quét",
"cleanup_no_assets_found": "Không tìm thấy tài nguyên nào phù hợp với điều kiện trên. Tính năng Giải phóng dung lượng chỉ có thể xóa các tài nguyên đã được sao lưu lên máy chủ",
@@ -744,9 +759,9 @@
"continue": "Tiếp tục",
"control_bottom_app_bar_add_tags": "Thêm thẻ",
"control_bottom_app_bar_delete_from_local": "Xóa khỏi thiết bị",
"control_bottom_app_bar_edit_location": "Chỉnh sửa vị trí",
"control_bottom_app_bar_edit_time": "Chỉnh sửa Ngày và Giờ",
"control_bottom_app_bar_trash_from_immich": "Di chuyển vào Thùng rác",
"control_bottom_app_bar_edit_location": "Sửa địa điểm",
"control_bottom_app_bar_edit_time": "Sửa ngày",
"control_bottom_app_bar_trash_from_immich": "Xóa",
"copied_image_to_clipboard": "Đã sao chép ảnh vào clipboard.",
"copied_to_clipboard": "Đã sao chép vào clipboard!",
"copy_error": "Sao chép lỗi",
@@ -791,11 +806,11 @@
"current_device": "Thiết bị hiện tại",
"current_pin_code": "Mã PIN hiện tại",
"current_server_address": "Địa chủ máy chủ hiện tại",
"custom_date": "Thiết lập ngày tùy chỉnh",
"custom_date": "Tùy chỉnh mốc",
"custom_locale": "Khu vực tùy chỉnh",
"custom_locale_description": "Định dạng ngày, thời gian và số dựa trên ngôn ngữ và khu vực đã chọn",
"custom_url": "URL tùy chỉnh",
"cutoff_date_description": "Giữ lại ảnh trong vòng…",
"cutoff_date_description": "Giữ lại các ảnh t…",
"cutoff_day": "{count, plural, one {ngày} other {ngày}}",
"cutoff_year": "{count, plural, one {năm} other {năm}}",
"dark": "Tối",
@@ -815,7 +830,7 @@
"default_share_quality": "Chất lượng chia sẻ mặc định",
"delete": "Xóa",
"delete_action_confirmation_message": "Bạn có chắc muốn xóa tài nguyên này? Tài nguyên sẽ được chuyển vào thùng rác của máy chủ và sẽ hỏi bạn có muốn xóa nó trên đó không",
"delete_action_prompt": "{count} đã xóa",
"delete_action_prompt": "Đã xóa ({count})",
"delete_album": "Xóa album",
"delete_api_key_prompt": "Bạn có chắc muốn xóa khóa API này?",
"delete_dialog_alert": "Những mục này sẽ bị xóa vĩnh viễn khỏi Immich và thiết bị của bạn",
@@ -827,12 +842,12 @@
"delete_key": "Xóa khóa",
"delete_library": "Xóa Thư viện",
"delete_link": "Xóa link",
"delete_local_action_prompt": "{count} đã xóa trên thiết bị",
"delete_local_action_prompt": "Đã xóa ({count}) trên thiết bị",
"delete_local_dialog_ok_backed_up_only": "Xóa ảnh đã sao lưu",
"delete_local_dialog_ok_force": "Vẫn xóa",
"delete_others": "Xóa ảnh còn lại",
"delete_permanently": "Xóa vĩnh viễn",
"delete_permanently_action_prompt": "{count} đã xóa vĩnh viễn",
"delete_permanently_action_prompt": "Đã xóa vĩnh viễn ({count})",
"delete_shared_link": "Xóa link đã chia sẻ",
"delete_shared_link_dialog_title": "Xóa link đã chia sẻ",
"delete_tag": "Xóa thẻ",
@@ -842,7 +857,7 @@
"description": "Mô tả",
"deselect_all": "Bỏ chọn tất cả",
"details": "Chi tiết",
"direction": "Hướng",
"direction": "Điều hướng",
"disable": "Vô hiệu hóa",
"disabled": "Đã tắt",
"discord": "Discord",
@@ -887,7 +902,7 @@
"edit_birthday": "Sửa ngày sinh",
"edit_date": "Chỉnh sửa ngày",
"edit_date_and_time": "Chỉnh sửa ngày và giờ",
"edit_date_and_time_action_prompt": "{count} đã sửa ngày và giờ",
"edit_date_and_time_action_prompt": "Đã sửa ngày và giờ ({count})",
"edit_date_and_time_by_offset": "Thay đổi ngày theo độ lệch",
"edit_description": "Chỉnh sửa mô tả",
"edit_exclusion_pattern": "Chỉnh sửa quy tắc loại trừ",
@@ -895,7 +910,7 @@
"edit_key": "Chỉnh sửa khóa",
"edit_link": "Chỉnh sửa link",
"edit_location": "Chỉnh sửa vị trí",
"edit_location_action_prompt": "{count} đã sửa vị trí",
"edit_location_action_prompt": "Đã sửa địa điểm ({count})",
"edit_location_dialog_title": "Vị trí",
"edit_name": "Chỉnh sửa tên",
"edit_people": "Chỉnh sửa người",
@@ -930,7 +945,7 @@
"enqueued": "Đã xếp hàng",
"enter_wifi_name": "Nhập tên Wi-Fi",
"enter_your_pin_code": "Nhập mã PIN của bạn",
"enter_your_pin_code_subtitle": "Nhập mã PIN của bạn để truy cập thư mục Khóa",
"enter_your_pin_code_subtitle": "Nhập mã PIN của bạn để truy cập thư mục Bí mật",
"error": "Lỗi",
"error_delete_face": "Lỗi khi xóa khuôn mặt khỏi tài nguyên",
"error_loading_albums": "Xảy ra lỗi khi tải các album",
@@ -1099,7 +1114,7 @@
"failed_to_load_assets": "Không tải được tài nguyên",
"failed_to_load_folder": "Không tải được thư mục",
"favorite": "Thích",
"favorite_action_prompt": "{count} đã thêm vào Yêu thích",
"favorite_action_prompt": "Đã thêm ({count}) vào Yêu thích",
"favorite_or_unfavorite_photo": "Thích hoặc bỏ thích ảnh",
"favorites": "Yêu thích",
"feature_photo_updated": "Đã cập nhật ảnh nổi bật",
@@ -1123,10 +1138,10 @@
"folders": "Thư mục",
"folders_feature_description": "Duyệt ảnh và video theo thư mục trên hệ thống tệp",
"forgot_pin_code_question": "Quên mã PIN?",
"forward": "Tiến tới",
"forward": "Tiến",
"free_up_space": "Giải phóng dung lượng",
"free_up_space_description": "Chuyển hình ảnh và video đã được sao lưu vào thùng rác của thiết bị để giải phóng dung lượng. Bản sao lưu trên máy chủ không bị ảnh hưởng.",
"free_up_space_settings_subtitle": "Tiết kiệm dung lượng bộ nhớ thiết bị",
"free_up_space_description": "Chuyển các hình ảnh và video đã được sao lưu vào thùng rác thiết bị. Bản sao lưu trên máy chủ không bị ảnh hưởng.",
"free_up_space_settings_subtitle": "Tiết kiệm bộ nhớ thiết bị",
"full_path": "Đường dẫn đầy đủ: {path}",
"full_path_or_folder": "Đường dẫn đầy đủ hoặc thư mục",
"gcast_enabled": "Google Cast",
@@ -1215,13 +1230,13 @@
"items_count": "{count, plural, one {# mục} other {# mục}}",
"jobs": "Tác vụ",
"keep": "Giữ",
"keep_albums": "Giữ lại các tập ảnh",
"keep_albums": "Giữ lại các album",
"keep_albums_count": "Giữ lại {count} {count, plural, one {album} other {album}}",
"keep_all": "Giữ tất cả",
"keep_description": "Chọn giữ lại những gì trên thiết bị khi giải phóng dung lượng.",
"keep_favorites": "Giữ lại các mục yêu thích",
"keep_on_device": "Giữ lại trên thiết bị",
"keep_on_device_hint": "Chọn các tệp sẽ được giữ lại trên thiết bị",
"keep_on_device_hint": "Chọn các mục sẽ không xóa",
"keep_this_delete_others": "Giữ tệp này, xóa các tệp khác",
"keeping": "Giữ lại: {items}",
"kept_this_deleted_others": "Đã giữ lại tài nguyên này và xóa {count, plural, one {# tài nguyên} other {# tài nguyên}}",
@@ -1281,7 +1296,7 @@
"location_picker_longitude_error": "Nhập kinh độ hợp lệ",
"location_picker_longitude_hint": "Nhập kinh độ của bạn",
"lock": "Khóa",
"locked_folder": "Khóa",
"locked_folder": "Bí mật",
"log_detail_title": "Chi tiết log",
"log_out": "Đăng xuất",
"log_out_all_devices": "Đăng xuất tất cả thiết bị",
@@ -1312,7 +1327,7 @@
"logout_this_device_confirmation": "Bạn có chắc muốn đăng xuất thiết bị này?",
"logs": "Log",
"longitude": "Kinh độ",
"look": "Xem",
"look": "Hiển thị",
"loop_videos": "Lặp video",
"loop_videos_description": "Bật để video tự động lặp lại trong trình xem chi tiết.",
"main_branch_warning": "Bạn đang dùng phiên bản đang phát triển; chúng tôi khuyên bạn nên dùng phiên bản phát hành!",
@@ -1344,7 +1359,7 @@
"manage_media_access_rationale": "Để có thể di chuyển tài nguyên vào thùng rác và khôi phục chúng từ đó.",
"manage_media_access_settings": "Mở cài đặt",
"manage_media_access_subtitle": "Cho phép ứng dụng [Immich] quản lý và di chuyển tệp.",
"manage_media_access_title": "Quản lý phương tiện",
"manage_media_access_title": "Quản lý media",
"manage_sharing_with_partners": "Quản lý chia sẻ với người thân",
"manage_the_app_settings": "Quản lý cài đặt ứng dụng",
"manage_your_account": "Quản lý tài khoản của bạn",
@@ -1439,9 +1454,9 @@
"move_off_locked_folder": "Di chuyển ra khỏi thư mục Khóa",
"move_to": "Di chuyển đến",
"move_to_device_trash": "Chuyển vào thùng rác thiết bị",
"move_to_lock_folder_action_prompt": "{count} đã được thêm vào thư mục Khóa",
"move_to_locked_folder": "Di chuyển đến thư mục Khóa",
"move_to_locked_folder_confirmation": "Ảnh và video này sẽ bị xóa khỏi các album, chỉ có thể xem được trong thư mục Khóa",
"move_to_lock_folder_action_prompt": "Đã thêm {count} vào thư mục Bí mật",
"move_to_locked_folder": "Di chuyển đến thư mục Bí mật",
"move_to_locked_folder_confirmation": "Ảnh và video này sẽ bị xóa khỏi mọi album chỉ có thể xem được trong thư mục Bí mật",
"moved_to_trash": "Đã chuyển vào thùng rác",
"mute_memories": "Tắt tiếng Kỷ niệm",
"my_albums": "Album của tôi",
@@ -1461,6 +1476,7 @@
"never": "Không bao giờ",
"new_album": "Album mới",
"new_api_key": "Khóa API mới",
"new_feature": "Tính năng mới",
"new_password": "Mật khẩu mới",
"new_person": "Người mới",
"new_pin_code": "Mã PIN mới",
@@ -1521,6 +1537,8 @@
"obtainium_configurator": "Cấu hình Obtainium",
"obtainium_configurator_instructions": "Sử dụng Obtainium để cài đặt và cập nhật ứng dụng Android trực tiếp từ bản phát hành GitHub của Immich. Tạo khóa API và chọn một biến thể để tạo liên kết cấu hình Obtainium của bạn",
"ocr": "OCR",
"ocr_body": "Immich hiện có thể đọc văn bản trong ảnh của bạn, nhờ đó bạn có thể tìm kiếm ảnh dựa trên nội dung văn bản đó.",
"ocr_title": "Tìm kiếm văn bản trong ảnh của bạn",
"official_immich_resources": "Tài nguyên chính thức của Immich",
"offline": "Ngoại tuyến",
"offset": "Độ lệch",
@@ -1539,6 +1557,8 @@
"open": "Mở",
"open_calendar": "Mở lịch",
"open_in_browser": "Mở trong trình duyệt",
"open_in_immich_body": "Hãy đặt Immich làm ứng dụng thư viện ảnh trên Android để mở ảnh trực tiếp từ các ứng dụng khác.",
"open_in_immich_title": "Mở ảnh trong Immich",
"open_in_map_view": "Mở trong bản đồ",
"open_in_openstreetmap": "Mở trong OpenStreetMap",
"open_the_search_filters": "Mở bộ lọc tìm kiếm",
@@ -1696,8 +1716,10 @@
"reassing_hint": "Gán các tài nguyên đã chọn cho một người hiện có",
"recent": "Gần đây",
"recent_searches": "Tìm kiếm gần đây",
"recently_added": "Được thêm gần đây",
"recently_added_page_title": "Được thêm gần đây",
"recently_added": "Thêm gần đây",
"recently_added_body": "Truy cập ngay vào tất cả những gì bạn đã thêm gần đây trên một trang dành riêng.",
"recently_added_page_title": "Thêm gần đây",
"recently_added_title": "Thêm gần đây",
"recently_taken": "Chụp gần đây",
"refresh": "Làm mới",
"refresh_encoded_videos": "Làm mới video đã mã hóa",
@@ -1719,11 +1741,11 @@
"remove_custom_date_range": "Bỏ chọn khoảng ngày tùy chỉnh",
"remove_filter": "Xóa bộ lọc",
"remove_from_album": "Xóa khỏi album",
"remove_from_album_action_prompt": "{count} đã gỡ khỏi album",
"remove_from_album_action_prompt": "Đã gỡ ({count}) khỏi album",
"remove_from_favorites": "Xóa khỏi mục Yêu thích",
"remove_from_lock_folder_action_prompt": "{count} đã được xóa khỏi thư mục Khóa",
"remove_from_locked_folder": "Xóa khỏi thư mục Khóa",
"remove_from_locked_folder_confirmation": "Bạn có chắc muốn di chuyển ảnh và video này khỏi thư mục Khóa? Chúng sẽ hiện trong thư viện của bạn.",
"remove_from_lock_folder_action_prompt": "Đã xóa {count} khỏi thư mục Bí mật",
"remove_from_locked_folder": "Xóa khỏi thư mục Bí mật",
"remove_from_locked_folder_confirmation": "Bạn có chắc muốn di chuyển ảnh và video này khỏi thư mục Bí mật? Chúng sẽ hiện trong thư viện của bạn.",
"remove_from_shared_link": "Xóa khỏi link chia sẻ",
"remove_memory": "Xóa kỷ niệm",
"remove_photo_from_memory": "Xóa ảnh khỏi kỷ niệm này",
@@ -1842,7 +1864,7 @@
"select_all_duplicates": "Chọn tất cả các bản trùng lặp",
"select_avatar_color": "Chọn màu ảnh đại diện",
"select_count": "{count, plural, one {Chọn #} other {Chọn #}}",
"select_cutoff_date": "Chọn giới hạn thời gian",
"select_cutoff_date": "Chọn mốc thời gian",
"select_face": "Chọn khuôn mặt",
"select_featured_photo": "Chọn ảnh nổi bật",
"select_from_computer": "Chọn từ máy tính",
@@ -1901,9 +1923,11 @@
"setup_pin_code": "Thiết lập mã PIN",
"share": "Chia sẻ",
"share_dialog_preparing": "Đang xử lý...",
"share_link": "Link chia sẻ",
"share_link": "Chia sẻ link",
"share_original": "Chất lượng gốc (lớn)",
"share_preview": "Dùng ảnh thu nhỏ (nhỏ)",
"share_quality_body": "Nhấn giữ nút chia sẻ để chọn chất lượng hình ảnh trước khi chia sẻ.",
"share_quality_title": "Chọn chất lượng chia sẻ",
"shared": "Đã chia sẻ",
"shared_album_activities_input_disable": "Nhận xét hiện đã tắt",
"shared_album_activity_remove_content": "Bạn có muốn xóa hoạt động này?",
@@ -1915,7 +1939,7 @@
"shared_by_you": "Được chia sẻ bởi bạn",
"shared_from_partner": "Ảnh từ {partner}",
"shared_intent_upload_button_progress_text": "{current} / {total} Đã tải lên",
"shared_link_app_bar_title": "Liên kết đã chia sẻ",
"shared_link_app_bar_title": "Những link đã chia sẻ",
"shared_link_clipboard_copied_massage": "Đã sao chép vào clipboard",
"shared_link_create_error": "Tạo liên kết chia sẻ không thành công",
"shared_link_custom_url_description": "Truy cập link đã chia sẻ với một URL tùy chỉnh",
@@ -1945,7 +1969,7 @@
"shared_link_manage_links": "Quản lý các link đã chia sẻ",
"shared_link_options": "Tùy chọn chia sẻ link",
"shared_link_password_description": "Bắt buộc để truy cập link chia sẻ này",
"shared_links": "Chia sẻ link",
"shared_links": "Đã chia sẻ",
"shared_links_description": "Chia sẻ ảnh và video bằng link",
"shared_photos_and_videos_count": "{assetCount, plural, other {# ảnh & video đã chia sẻ.}}",
"shared_with_me": "Chia sẻ với tôi",
@@ -1985,16 +2009,19 @@
"sign_out": "Đăng xuất",
"sign_up": "Đăng ký",
"size": "Kích thước",
"skip": "Bỏ qua",
"skip_to_content": "Chuyển đến nội dung",
"skip_to_folders": "Chuyển đến thư mục",
"skip_to_tags": "Chuyển đến thẻ",
"slideshow": "Trình chiếu",
"slideshow_body": "Hãy thư giãn và xem các bức ảnh của bạn trình chiếu ở chế độ toàn màn hình.",
"slideshow_metadata_overlay_mode": "Lớp phủ nội dung",
"slideshow_metadata_overlay_mode_description_only": "Chỉ mô tả",
"slideshow_metadata_overlay_mode_full": "Đầy đủ",
"slideshow_repeat": "Trình chiếu lại",
"slideshow_repeat": "Lặp lại trình chiếu",
"slideshow_repeat_description": "Phát lại từ đầu khi trình chiếu kết thúc",
"slideshow_settings": "Cài đặt trình chiếu",
"slideshow_title": "Slideshow",
"smart_album": "Album thông minh",
"some_assets_already_have_a_location_warning": "Một số tài nguyên được lựa chọn đã có vị trí",
"sort_albums_by": "Sắp xếp album theo...",
@@ -2007,7 +2034,7 @@
"sort_title": "Tiêu đề",
"source": "Mã nguồn",
"stack": "Nhóm ảnh",
"stack_action_prompt": "{count} đã được nhóm",
"stack_action_prompt": "Đã nhóm ({count}) tương tự",
"stack_duplicates": "Nhóm mục trùng lặp",
"stack_selected_photos": "Nhóm các ảnh đã chọn",
"stacked_assets_count": "Đã nhóm {count, plural, one {# tài nguyên} other {# tài nguyên}}",
@@ -2092,7 +2119,7 @@
"total": "Tổng cộng",
"total_usage": "Dung lượng đã dùng",
"trash": "Thùng rác",
"trash_action_prompt": "{count} đã chuyển vào thùng rác",
"trash_action_prompt": "Đã chuyển ({count}) vào thùng rác",
"trash_all": "Xóa hết",
"trash_count": "Xóa {count, number} mục",
"trash_delete_asset": "Chuyển vào thùng rác/Xóa tài nguyên",
@@ -2113,11 +2140,11 @@
"unable_to_check_version": "Không thể kiểm tra phiên bản ứng dụng hoặc máy chủ",
"unable_to_setup_pin_code": "Thiết lập mã PIN thất bại",
"unarchive": "Bỏ lưu trữ",
"unarchive_action_prompt": "{count} đã bỏ khỏi Lưu trữ",
"unarchive_action_prompt": "Đã bỏ lưu trữ ({count})",
"unarchived_count": "{count, plural, other {Đã bỏ lưu trữ # mục}}",
"undo": "Hoàn tác",
"unfavorite": "Bỏ thích",
"unfavorite_action_prompt": "{count} đã bỏ khỏi Yêu thích",
"unfavorite_action_prompt": "Đã bỏ yêu thích ({count})",
"unhide_person": "Hiện người",
"unknown": "Không xác định",
"unknown_country": "Quốc gia chưa rõ",
@@ -2135,7 +2162,7 @@
"unselect_all": "Bỏ chọn tất cả",
"unselect_all_duplicates": "Bỏ chọn tất cả các bản trùng lặp",
"unstack": "Hủy xếp nhóm",
"unstack_action_prompt": "{count} đã bỏ nhóm",
"unstack_action_prompt": "Đã bỏ tương tự ({count})",
"unstacked_assets_count": "Đã hủy xếp nhóm {count, plural, one {# tài nguyên} other {# tài nguyên}}",
"unsupported_field_type": "Loại trường không được hỗ trợ",
"unsupported_file_type": "Tệp {file} không thể được tải lên vì loại tệp {type} không được hỗ trợ.",
@@ -2151,12 +2178,14 @@
"upload_error_with_count": "Không thể tải lên {count, plural, one {# tài nguyên} other {# tài nguyên}}",
"upload_errors": "Đã hoàn tất tải lên với {count, plural, one {# lỗi} other {# lỗi}}, làm mới trang để xem các tài nguyên mới tải lên.",
"upload_finished": "Đã hoàn tất tải lên",
"upload_progress": "Còn lại {remaining, number} - Đã xử lý {processed, number}/{total, number}",
"upload_progress": "Còn {remaining, number} - Đã xử lý {processed, number}/{total, number}",
"upload_skipped_duplicates": "Đã bỏ qua {count, plural, one {# tài nguyên trùng lặp} other {# tài nguyên trùng lặp}}",
"upload_status_duplicates": "Trùng lặp",
"upload_status_errors": "Lỗi",
"upload_status_uploaded": "Đã tải lên",
"upload_success": "Tải lên thành công, làm mới trang để xem các tài nguyên mới tải lên.",
"upload_to_album_body": "Đối với những người dùng không sử dụng tính năng tải lên thủ công, giờ đây bạn có thể chọn thêm ảnh từ thiết bị trực tiếp vào album ngay khi tải lên, thay vì phải tải lên rồi mới thêm vào album sau như trước đây.",
"upload_to_album_title": "Tải lên trực tiếp vào album",
"upload_to_immich": "Tải lên Immich ({count})",
"uploading": "Đang tải lên",
"uploading_media": "Đang tải lên phương tiện",
@@ -2224,6 +2253,9 @@
"week": "Tuần",
"welcome": "Chào mừng",
"welcome_to_immich": "Chào mừng đến với Immich",
"whats_new": "Có gì mới?",
"whats_new_settings_subtitle": "Xem Immich có gì mới",
"whats_new_version": "Phiên bản {version}",
"when": "Khi nào",
"width": "Chiều rộng",
"wifi_name": "Tên Wi-Fi",
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "immich-ml"
version = "3.0.0rc4"
version = "3.0.0"
description = ""
authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }]
requires-python = ">=3.11,<4.0"
+1 -1
View File
@@ -974,7 +974,7 @@ wheels = [
[[package]]
name = "immich-ml"
version = "3.0.0rc4"
version = "3.0.0"
source = { editable = "." }
dependencies = [
{ name = "aiocache" },
+4 -4
View File
@@ -22,8 +22,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 3052,
"android.injected.version.name" => "3.0.0-rc.4",
"android.injected.version.code" => 3053,
"android.injected.version.name" => "3.0.0",
}
)
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab', track: 'beta')
@@ -35,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 3052,
"android.injected.version.name" => "3.0.0-rc.4",
"android.injected.version.code" => 3053,
"android.injected.version.name" => "3.0.0",
}
)
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

+2
View File
@@ -9,6 +9,8 @@ enum SortOrder {
enum TextSearchType { context, filename, description, ocr }
enum AssetVisibilityEnum { timeline, hidden, archive, locked }
enum ActionSource { timeline, viewer }
enum ShareAssetType { original, preview }
@@ -52,10 +52,6 @@ class RemoteAsset extends BaseAsset {
bool get isTrashed => deletedAt != null;
bool get isStacked => stackId != null;
bool get isArchived => visibility == .archive;
@override
String toString() {
return '''Asset {
@@ -4,6 +4,7 @@ import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/config/album_config.dart';
import 'package:immich_mobile/domain/models/config/backup_config.dart';
import 'package:immich_mobile/domain/models/config/cleanup_config.dart';
import 'package:immich_mobile/domain/models/config/feature_message_config.dart';
import 'package:immich_mobile/domain/models/config/image_config.dart';
import 'package:immich_mobile/domain/models/config/map_config.dart';
import 'package:immich_mobile/domain/models/config/network_config.dart';
@@ -16,6 +17,7 @@ import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/domain/models/settings_key.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
import 'package:immich_mobile/utils/semver.dart';
const defaultConfig = AppConfig();
@@ -32,6 +34,7 @@ class AppConfig {
final BackupConfig backup;
final NetworkConfig network;
final ShareConfig share;
final FeatureMessageConfig featureMessage;
const AppConfig({
this.logLevel = .info,
@@ -46,6 +49,7 @@ class AppConfig {
this.backup = const .new(),
this.network = const .new(),
this.share = const .new(),
this.featureMessage = const .new(),
});
AppConfig copyWith({
@@ -61,6 +65,7 @@ class AppConfig {
BackupConfig? backup,
NetworkConfig? network,
ShareConfig? share,
FeatureMessageConfig? featureMessage,
}) => .new(
logLevel: logLevel ?? this.logLevel,
theme: theme ?? this.theme,
@@ -74,6 +79,7 @@ class AppConfig {
backup: backup ?? this.backup,
network: network ?? this.network,
share: share ?? this.share,
featureMessage: featureMessage ?? this.featureMessage,
);
@override
@@ -91,15 +97,29 @@ class AppConfig {
other.album == album &&
other.backup == backup &&
other.network == network &&
other.share == share);
other.share == share &&
other.featureMessage == featureMessage);
@override
int get hashCode =>
Object.hash(logLevel, theme, cleanup, map, timeline, image, viewer, slideshow, album, backup, network, share);
int get hashCode => Object.hash(
logLevel,
theme,
cleanup,
map,
timeline,
image,
viewer,
slideshow,
album,
backup,
network,
share,
featureMessage,
);
@override
String toString() =>
'AppConfig(logLevel: $logLevel, theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, slideshow: $slideshow, album: $album, backup: $backup, network: $network, share: $share)';
'AppConfig(logLevel: $logLevel, theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, slideshow: $slideshow, album: $album, backup: $backup, network: $network, share: $share, featureMessage: $featureMessage)';
T read<T>(SettingsKey<T> key) =>
(switch (key) {
@@ -146,6 +166,7 @@ class AppConfig {
.slideshowDuration => slideshow.duration,
.slideshowLook => slideshow.look,
.slideshowDirection => slideshow.direction,
.featureMessageSeenRelease => featureMessage.seenRelease,
})
as T;
@@ -199,6 +220,7 @@ class AppConfig {
.slideshowDuration => copyWith(slideshow: slideshow.copyWith(duration: value as int)),
.slideshowLook => copyWith(slideshow: slideshow.copyWith(look: value as SlideshowLook)),
.slideshowDirection => copyWith(slideshow: slideshow.copyWith(direction: value as SlideshowDirection)),
.featureMessageSeenRelease => copyWith(featureMessage: featureMessage.copyWith(seenRelease: value as SemVer)),
};
}
}
@@ -0,0 +1,20 @@
import 'package:immich_mobile/utils/semver.dart';
class FeatureMessageConfig {
final SemVer seenRelease;
const FeatureMessageConfig({this.seenRelease = const SemVer(major: 0, minor: 0, patch: 0)});
FeatureMessageConfig copyWith({SemVer? seenRelease}) =>
FeatureMessageConfig(seenRelease: seenRelease ?? this.seenRelease);
@override
bool operator ==(Object other) =>
identical(this, other) || (other is FeatureMessageConfig && other.seenRelease == seenRelease);
@override
int get hashCode => seenRelease.hashCode;
@override
String toString() => 'FeatureMessageConfig(seenRelease: $seenRelease)';
}
@@ -0,0 +1,54 @@
import 'package:flutter/foundation.dart';
import 'package:immich_mobile/utils/semver.dart';
class FeatureHighlight {
/// Asset path of the feature screenshot, or null to show a placeholder.
final String? image;
final String titleKey;
final String bodyKey;
final List<TargetPlatform> platform;
const FeatureHighlight({
this.image,
required this.titleKey,
required this.bodyKey,
this.platform = const [.iOS, .android],
});
bool get isVisibleOnCurrentPlatform => platform.contains(defaultTargetPlatform);
}
/// The release this batch of highlights was authored for. Content-defined:
/// bump it only when publishing a new batch, never from the running app version.
const featureMessageRelease = SemVer(major: 3, minor: 0, patch: 0);
/// Highlights relevant to the current platform.
List<FeatureHighlight> get visibleFeatureMessageHighlights =>
featureMessageHighlights.where((h) => h.isVisibleOnCurrentPlatform).toList();
const List<FeatureHighlight> featureMessageHighlights = [
FeatureHighlight(
image: 'assets/feature_message/share_quality.webp',
titleKey: 'share_quality_title',
bodyKey: 'share_quality_body',
),
FeatureHighlight(
image: 'assets/feature_message/slideshow.webp',
titleKey: 'slideshow_title',
bodyKey: 'slideshow_body',
),
FeatureHighlight(
image: 'assets/feature_message/recently_added.webp',
titleKey: 'recently_added_title',
bodyKey: 'recently_added_body',
),
FeatureHighlight(image: 'assets/feature_message/ocr.webp', titleKey: 'ocr_title', bodyKey: 'ocr_body'),
FeatureHighlight(
image: 'assets/feature_message/open_in_immich.webp',
titleKey: 'open_in_immich_title',
bodyKey: 'open_in_immich_body',
platform: [.android],
),
FeatureHighlight(titleKey: 'upload_to_album_title', bodyKey: 'upload_to_album_body'),
];
+15 -1
View File
@@ -6,6 +6,7 @@ import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
import 'package:immich_mobile/utils/semver.dart';
enum SettingsKey<T> {
// Theme
@@ -73,7 +74,10 @@ enum SettingsKey<T> {
slideshowRepeat<bool>(),
slideshowDuration<int>(),
slideshowLook<SlideshowLook>(codec: _EnumCodec(SlideshowLook.values)),
slideshowDirection<SlideshowDirection>(codec: _EnumCodec(SlideshowDirection.values));
slideshowDirection<SlideshowDirection>(codec: _EnumCodec(SlideshowDirection.values)),
// Feature message
featureMessageSeenRelease<SemVer>(codec: _SemVerCodec());
final _SettingsCodec<T>? _codecOverride;
@@ -139,6 +143,16 @@ final class _DateTimeCodec extends _SettingsCodec<DateTime> {
DateTime decode(String raw) => DateTime.parse(raw);
}
final class _SemVerCodec extends _SettingsCodec<SemVer> {
const _SemVerCodec();
@override
String encode(SemVer value) => value.toString();
@override
SemVer decode(String raw) => SemVer.fromString(raw);
}
final class _MapCodec<K extends Object, V extends Object> extends _SettingsCodec<Map<K, V>> {
final _SettingsCodec<K> _keyCodec;
final _SettingsCodec<V> _valueCodec;
@@ -77,40 +77,4 @@ class AssetService {
await _apiRepository.updateFavorite(remoteIds, isFavorite);
await _remoteRepository.updateFavorite(remoteIds, isFavorite);
}
Future<void> restoreTrash(List<String> remoteIds) async {
if (remoteIds.isEmpty) {
return;
}
await _apiRepository.restoreTrash(remoteIds);
await _remoteRepository.restoreTrash(remoteIds);
}
Future<void> stack(String userId, List<String> remoteIds) async {
if (remoteIds.isEmpty) {
return;
}
final stack = await _apiRepository.stack(remoteIds);
await _remoteRepository.stack(userId, stack);
}
Future<void> unstack(List<String> stackIds) async {
if (stackIds.isEmpty) {
return;
}
await _remoteRepository.unStack(stackIds);
await _apiRepository.unStack(stackIds);
}
Future<void> updateVisibility(List<String> remoteIds, AssetVisibility visibility) async {
if (remoteIds.isEmpty) {
return;
}
await _apiRepository.updateVisibility(remoteIds, visibility);
await _remoteRepository.updateVisibility(remoteIds, visibility);
}
}
@@ -0,0 +1,16 @@
import 'package:immich_mobile/domain/models/feature_message.model.dart';
import 'package:immich_mobile/domain/models/settings_key.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
class FeatureMessageService {
final SettingsRepository _settingsRepository;
const FeatureMessageService(this._settingsRepository);
bool shouldShow() {
final seen = _settingsRepository.appConfig.featureMessage.seenRelease;
return featureMessageHighlights.isNotEmpty && featureMessageRelease > seen;
}
Future<void> markSeen() => _settingsRepository.write(SettingsKey.featureMessageSeenRelease, featureMessageRelease);
}
@@ -10,7 +10,6 @@ import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/utils/option.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
class RemoteAssetRepository extends DriftDatabaseRepository {
@@ -287,20 +286,4 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
..orderBy([(row) => OrderingTerm.asc(row.sequence)]);
return query.map((row) => row.toDto()!).get();
}
Future<void> update(
List<String> remoteIds, {
Option<bool> isFavorite = const .none(),
Option<AssetVisibility> visibility = const .none(),
}) {
final companion = RemoteAssetEntityCompanion(
visibility: visibility.toDriftValue(),
isFavorite: isFavorite.toDriftValue(),
);
return _db.batch((batch) {
for (final remoteId in remoteIds) {
batch.update(_db.remoteAssetEntity, companion, where: (e) => e.id.equals(remoteId));
}
});
}
}
@@ -3,6 +3,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/settings/advanced_settings.dart';
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_settings.dart';
@@ -87,6 +88,14 @@ class _MobileLayout extends StatelessWidget {
],
)
.toList();
settings.add(
SettingsCard(
icon: Icons.auto_awesome_outlined,
title: context.t.whats_new,
subtitle: context.t.whats_new_settings_subtitle,
settingRoute: const WhatsNewRoute(),
),
);
return ListView(padding: const EdgeInsets.only(top: 10.0, bottom: 60), children: [...settings]);
}
}
@@ -116,6 +125,13 @@ class _TabletLayout extends HookWidget {
),
),
),
SliverToBoxAdapter(
child: ListTile(
title: Text('whats_new'.tr()),
leading: const Icon(Icons.auto_awesome_outlined),
onTap: () => context.pushRoute(const WhatsNewRoute()),
),
),
],
),
),
+8 -31
View File
@@ -1,10 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/utils/asset_filter.dart';
class ActionScope {
final BuildContext context;
@@ -17,7 +14,13 @@ class ActionScope {
abstract class BaseAction {
const BaseAction();
ActionView resolve(ActionScope scope);
IconData get icon;
String label(ActionScope scope);
bool isVisible(ActionScope scope) => true;
Future<void> onAction(ActionScope scope);
}
abstract class AssetAction<T extends BaseAsset> extends BaseAction {
@@ -25,31 +28,5 @@ abstract class AssetAction<T extends BaseAsset> extends BaseAction {
const AssetAction({required this.assets});
@override
AssetActionView resolve(ActionScope scope);
}
abstract class ActionView {
final ActionScope scope;
const ActionView({required this.scope});
IconData get icon;
String get label;
bool get isVisible => true;
FutureOr<void> onAction();
}
abstract class AssetActionView<T extends BaseAsset> extends ActionView {
final Iterable<BaseAsset> assets;
const AssetActionView({required this.assets, required super.scope});
AssetFilter<T> get filter => .new(assets.whereType<T>());
@override
bool get isVisible => filter.isNotEmpty;
Iterable<T> filter(ActionScope scope) => assets.whereType<T>();
}
@@ -1,5 +1,3 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -9,11 +7,10 @@ import 'package:immich_mobile/utils/error_handler.dart';
import 'package:immich_ui/immich_ui.dart';
class _ActionWidgetScope {
final IconData icon;
final String label;
final FutureOr<void> Function() onAction;
final VoidCallback onAction;
const _ActionWidgetScope({required this.icon, required this.label, required this.onAction});
const _ActionWidgetScope({required this.label, required this.onAction});
}
class _ActionWidget extends ConsumerWidget {
@@ -22,11 +19,11 @@ class _ActionWidget extends ConsumerWidget {
const _ActionWidget({required this.action, required this.builder});
Future<void> _onAction(FutureOr<void> Function() action) async {
Future<void> _onAction(ActionScope scope) async {
try {
await action();
await action.onAction(scope);
} catch (error, stackTrace) {
handleError(error, stack: stackTrace, description: 'Action failed: ${action.runtimeType}');
handleError(scope.context, error, stack: stackTrace, description: 'Action failed: ${action.runtimeType}');
}
}
@@ -38,13 +35,11 @@ class _ActionWidget extends ConsumerWidget {
}
final scope = ActionScope(context: context, ref: ref, authUser: authUser);
final view = action.resolve(scope);
if (!view.isVisible) {
if (!action.isVisible(scope)) {
return const SizedBox.shrink();
}
return builder(.new(icon: view.icon, label: view.label, onAction: () => _onAction(view.onAction)));
return builder(.new(label: action.label(scope), onAction: () => _onAction(scope)));
}
}
@@ -57,7 +52,7 @@ class ActionIconButtonWidget extends StatelessWidget {
@override
Widget build(BuildContext context) => _ActionWidget(
action: action,
builder: (ctx) => ImmichIconButton(icon: ctx.icon, onPressed: ctx.onAction, variant: variant),
builder: (ctx) => ImmichIconButton(icon: action.icon, onPressed: ctx.onAction, variant: variant),
);
}
@@ -70,7 +65,8 @@ class ActionButtonWidget extends StatelessWidget {
@override
Widget build(BuildContext context) => _ActionWidget(
action: action,
builder: (ctx) => ImmichTextButton(labelText: ctx.label, icon: ctx.icon, onPressed: ctx.onAction, variant: variant),
builder: (ctx) =>
ImmichTextButton(labelText: ctx.label, icon: action.icon, onPressed: ctx.onAction, variant: variant),
);
}
@@ -82,7 +78,7 @@ class ActionColumnButtonWidget extends StatelessWidget {
@override
Widget build(BuildContext context) => _ActionWidget(
action: action,
builder: (ctx) => ImmichColumnButton(icon: ctx.icon, label: ctx.label, onPressed: ctx.onAction),
builder: (ctx) => ImmichColumnButton(icon: action.icon, label: ctx.label, onPressed: ctx.onAction),
);
}
@@ -94,6 +90,6 @@ class ActionMenuItemWidget extends StatelessWidget {
@override
Widget build(BuildContext context) => _ActionWidget(
action: action,
builder: (ctx) => ImmichMenuItem(icon: ctx.icon, label: ctx.label, onPressed: ctx.onAction),
builder: (ctx) => ImmichMenuItem(icon: action.icon, label: ctx.label, onPressed: ctx.onAction),
);
}
@@ -1,68 +0,0 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/presentation/actions/action.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/toast.provider.dart';
import 'package:immich_mobile/providers/routes.provider.dart';
import 'package:immich_mobile/utils/asset_filter.dart';
class ArchiveAction extends AssetAction<RemoteAsset> {
const ArchiveAction({required super.assets});
@override
AssetActionView<RemoteAsset> resolve(ActionScope scope) {
final hasNonArchived = AssetFilter(assets).owned(scope.authUser.id).any((asset) => asset.visibility != .archive);
return hasNonArchived ? ArchiveView(assets: assets, scope: scope) : UnarchiveView(assets: assets, scope: scope);
}
}
@visibleForTesting
class ArchiveView extends AssetActionView<RemoteAsset> {
const ArchiveView({required super.assets, required super.scope});
@override
IconData get icon => Icons.archive_outlined;
@override
String get label => scope.context.t.archive;
@override
AssetFilter<RemoteAsset> get filter => .new(assets).owned(scope.authUser.id).archived(isArchived: false);
@override
bool get isVisible => !scope.ref.watch(inLockedViewProvider) && filter.isNotEmpty;
@override
Future<void> onAction() async {
final ActionScope(:ref, :context) = scope;
final ids = filter.map((asset) => asset.id).toList(growable: false);
await ref.read(assetServiceProvider).updateVisibility(ids, .archive);
ref.read(toastRepositoryProvider).success(context.t.archive_action_prompt(count: ids.length));
}
}
@visibleForTesting
class UnarchiveView extends AssetActionView<RemoteAsset> {
const UnarchiveView({required super.assets, required super.scope});
@override
IconData get icon => Icons.unarchive_outlined;
@override
String get label => scope.context.t.unarchive;
@override
AssetFilter<RemoteAsset> get filter => .new(assets).owned(scope.authUser.id).archived();
@override
bool get isVisible => !scope.ref.watch(inLockedViewProvider) && filter.isNotEmpty;
@override
Future<void> onAction() async {
final ActionScope(:ref, :context) = scope;
final ids = filter.map((asset) => asset.id).toList(growable: false);
await ref.read(assetServiceProvider).updateVisibility(ids, .timeline);
ref.read(toastRepositoryProvider).success(context.t.unarchive_action_prompt(count: ids.length));
}
}
@@ -11,23 +11,17 @@ import 'package:immich_mobile/routing/router.dart';
class AssetDebugAction extends AssetAction<BaseAsset> {
const AssetDebugAction({required super.assets});
@override
AssetDebugActionView resolve(ActionScope scope) => .new(assets: assets, scope: scope);
}
@visibleForTesting
class AssetDebugActionView extends AssetActionView<BaseAsset> {
const AssetDebugActionView({required super.assets, required super.scope});
@override
IconData get icon => Icons.help_outline_rounded;
@override
String get label => scope.context.t.troubleshoot;
String label(ActionScope scope) => scope.context.t.troubleshoot;
@override
bool get isVisible => scope.ref.watch(settingsProvider.notifier).get(.advancedTroubleshooting) && assets.length == 1;
bool isVisible(ActionScope scope) =>
assets.length == 1 && scope.ref.watch(settingsProvider.notifier).get(.advancedTroubleshooting);
@override
Future<void> onAction() async => unawaited(scope.context.pushRoute(AssetTroubleshootRoute(asset: assets.first)));
Future<void> onAction(ActionScope scope) async =>
unawaited(scope.context.pushRoute(AssetTroubleshootRoute(asset: assets.first)));
}
@@ -3,61 +3,38 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/presentation/actions/action.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/toast.provider.dart';
import 'package:immich_mobile/utils/asset_filter.dart';
import 'package:immich_ui/immich_ui.dart';
class FavoriteAction extends BaseAction {
final Iterable<BaseAsset> assets;
class FavoriteAction extends AssetAction<RemoteAsset> {
final bool shouldFavorite;
const FavoriteAction({required this.assets});
FavoriteAction({required super.assets}) : shouldFavorite = assets.any((asset) => !asset.isFavorite);
@override
AssetActionView<RemoteAsset> resolve(ActionScope scope) {
final hasNonFavorite = AssetFilter(assets).owned(scope.authUser.id).any((asset) => !asset.isFavorite);
return hasNonFavorite ? FavoriteView(assets: assets, scope: scope) : UnfavoriteView(assets: assets, scope: scope);
}
}
@visibleForTesting
class FavoriteView extends AssetActionView<RemoteAsset> {
const FavoriteView({required super.assets, required super.scope});
@override
IconData get icon => Icons.favorite_border_rounded;
@override
String get label => scope.context.t.favorite;
@override
AssetFilter<RemoteAsset> get filter => .new(assets).owned(scope.authUser.id).favorite(isFavorite: false);
@override
Future<void> onAction() async {
final ActionScope(:ref, :context) = scope;
final ids = filter.map((asset) => asset.id).toList(growable: false);
await ref.read(assetServiceProvider).updateFavorite(ids, true);
ref.read(toastRepositoryProvider).success(context.t.favorite_action_prompt(count: ids.length));
}
}
@visibleForTesting
class UnfavoriteView extends AssetActionView<RemoteAsset> {
const UnfavoriteView({required super.assets, required super.scope});
@override
IconData get icon => Icons.favorite_rounded;
@override
String get label => scope.context.t.unfavorite;
@override
AssetFilter<RemoteAsset> get filter => .new(assets).owned(scope.authUser.id).favorite();
@override
Future<void> onAction() async {
final ActionScope(:ref, :context) = scope;
final ids = filter.map((asset) => asset.id).toList(growable: false);
await ref.read(assetServiceProvider).updateFavorite(ids, false);
ref.read(toastRepositoryProvider).success(context.t.unfavorite_action_prompt(count: ids.length));
IconData get icon => shouldFavorite ? Icons.favorite_border_rounded : Icons.favorite_rounded;
@override
String label(ActionScope scope) => shouldFavorite ? scope.context.t.favorite : scope.context.t.unfavorite;
@override
Iterable<RemoteAsset> filter(ActionScope scope) => assets
.where(
(asset) => asset is RemoteAsset && asset.ownerId == scope.authUser.id && asset.isFavorite == !shouldFavorite,
)
.cast<RemoteAsset>();
@override
bool isVisible(ActionScope scope) => filter(scope).isNotEmpty;
@override
Future<void> onAction(ActionScope scope) async {
final ActionScope(:ref) = scope;
final assets = filter(scope).map((asset) => asset.id).toList(growable: false);
await ref.read(assetServiceProvider).updateFavorite(assets, shouldFavorite);
final message = shouldFavorite
? StaticTranslations.instance.favorite_action_prompt(count: assets.length)
: StaticTranslations.instance.unfavorite_action_prompt(count: assets.length);
snackbar.success(message);
}
}
@@ -11,22 +11,14 @@ import 'package:immich_mobile/widgets/common/confirm_dialog.dart';
class PartnerAddAction extends BaseAction {
const PartnerAddAction();
@override
PartnerAddActionView resolve(ActionScope scope) => .new(scope: scope);
}
@visibleForTesting
class PartnerAddActionView extends ActionView {
const PartnerAddActionView({required super.scope});
@override
IconData get icon => Icons.person_add_rounded;
@override
String get label => scope.context.t.add_partner;
String label(ActionScope scope) => scope.context.t.add_partner;
@override
Future<void> onAction() async {
Future<void> onAction(ActionScope scope) async {
final ActionScope(:context, :ref, :authUser) = scope;
final selected = await showDialog<User>(context: context, builder: (_) => const PartnerSelectionDialog());
if (selected == null) {
@@ -43,26 +35,14 @@ class PartnerRemoveAction extends BaseAction {
final String sharedWithId;
final String partnerName;
@override
PartnerRemoveActionView resolve(ActionScope scope) =>
.new(sharedWithId: sharedWithId, partnerName: partnerName, scope: scope);
}
@visibleForTesting
class PartnerRemoveActionView extends ActionView {
final String sharedWithId;
final String partnerName;
const PartnerRemoveActionView({required this.sharedWithId, required this.partnerName, required super.scope});
@override
IconData get icon => Icons.person_remove_rounded;
@override
String get label => scope.context.t.remove;
String label(ActionScope scope) => scope.context.t.remove;
@override
Future<void> onAction() async {
Future<void> onAction(ActionScope scope) async {
final ActionScope(:context, :ref, :authUser) = scope;
final confirmed = await showDialog<bool>(
@@ -1,36 +0,0 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/presentation/actions/action.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/toast.provider.dart';
import 'package:immich_mobile/utils/asset_filter.dart';
class RestoreAction extends AssetAction<RemoteAsset> {
const RestoreAction({required super.assets});
@override
RestoreActionView resolve(ActionScope scope) => .new(assets: assets, scope: scope);
}
@visibleForTesting
class RestoreActionView extends AssetActionView<RemoteAsset> {
const RestoreActionView({required super.assets, required super.scope});
@override
IconData get icon => Icons.history_rounded;
@override
String get label => scope.context.t.restore;
@override
AssetFilter<RemoteAsset> get filter => .new(assets).owned(scope.authUser.id).trashed();
@override
Future<void> onAction() async {
final ActionScope(:ref, :context) = scope;
final ids = filter.map((asset) => asset.id).toList(growable: false);
await ref.read(assetServiceProvider).restoreTrash(ids);
ref.read(toastRepositoryProvider).success(context.t.assets_restored_count(count: ids.length));
}
}
@@ -1,65 +0,0 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/presentation/actions/action.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/toast.provider.dart';
import 'package:immich_mobile/utils/asset_filter.dart';
class StackAction extends AssetAction<RemoteAsset> {
const StackAction({required super.assets});
@override
AssetActionView<RemoteAsset> resolve(ActionScope scope) {
final unstacked = AssetFilter(assets).owned(scope.authUser.id).any((asset) => asset.stackId == null);
return unstacked ? StackView(assets: assets, scope: scope) : UnstackView(assets: assets, scope: scope);
}
}
@visibleForTesting
class StackView extends AssetActionView<RemoteAsset> {
const StackView({required super.assets, required super.scope});
@override
IconData get icon => Icons.filter_none_rounded;
@override
String get label => scope.context.t.stack;
@override
AssetFilter<RemoteAsset> get filter => .new(assets).owned(scope.authUser.id);
@override
bool get isVisible => filter.length > 1;
@override
Future<void> onAction() async {
final ActionScope(:ref, :context) = scope;
final ids = filter.map((asset) => asset.id).toList(growable: false);
await ref.read(assetServiceProvider).stack(scope.authUser.id, ids);
ref.read(toastRepositoryProvider).success(context.t.stacked_assets_count(count: ids.length));
}
}
@visibleForTesting
class UnstackView extends AssetActionView<RemoteAsset> {
const UnstackView({required super.assets, required super.scope});
@override
IconData get icon => Icons.layers_clear_outlined;
@override
String get label => scope.context.t.unstack;
@override
AssetFilter<RemoteAsset> get filter => .new(assets).owned(scope.authUser.id);
@override
Future<void> onAction() async {
final ActionScope(:ref, :context) = scope;
final assets = filter.toList(growable: false);
final stackIds = assets.map((asset) => asset.stackId).nonNulls.toList(growable: false);
await ref.read(assetServiceProvider).unstack(stackIds);
ref.read(toastRepositoryProvider).success(context.t.unstacked_assets_count(count: assets.length));
}
}
@@ -8,27 +8,17 @@ class TimelineAction extends BaseAction {
const TimelineAction({required this.action});
@override
TimelineActionView resolve(ActionScope scope) => .new(view: action.resolve(scope), scope: scope);
}
@visibleForTesting
class TimelineActionView extends ActionView {
final ActionView view;
const TimelineActionView({required this.view, required super.scope});
IconData get icon => action.icon;
@override
IconData get icon => view.icon;
String label(ActionScope scope) => action.label(scope);
@override
String get label => view.label;
bool isVisible(ActionScope scope) => action.isVisible(scope);
@override
bool get isVisible => view.isVisible;
@override
Future<void> onAction() async {
await view.onAction();
Future<void> onAction(ActionScope scope) async {
await action.onAction(scope);
scope.ref.read(multiSelectProvider.notifier).reset();
}
}
@@ -3,14 +3,42 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/presentation/widgets/memory/memory_lane.widget.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/presentation/widgets/feature_message/feature_message_dialog.widget.dart';
import 'package:immich_mobile/providers/feature_message.provider.dart';
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
@RoutePage()
class MainTimelinePage extends ConsumerWidget {
class MainTimelinePage extends ConsumerStatefulWidget {
const MainTimelinePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
ConsumerState<MainTimelinePage> createState() => _MainTimelinePageState();
}
class _MainTimelinePageState extends ConsumerState<MainTimelinePage> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!mounted) {
return;
}
final service = ref.read(featureMessageServiceProvider);
if (!service.shouldShow()) {
return;
}
await service.markSeen();
if (!mounted) {
return;
}
await showFeatureMessageDialog(context);
});
}
@override
Widget build(BuildContext context) {
final hasMemories = ref.watch(driftMemoryFutureProvider.select((state) => state.value?.isNotEmpty ?? false));
return Timeline(
topSliverWidget: const SliverToBoxAdapter(child: DriftMemoryLane()),
@@ -0,0 +1,73 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:immich_mobile/domain/models/feature_message.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/presentation/widgets/feature_message/feature_message_placeholder.widget.dart';
@RoutePage()
class WhatsNewPage extends StatelessWidget {
const WhatsNewPage({super.key});
@override
Widget build(BuildContext context) {
final highlights = visibleFeatureMessageHighlights;
return Scaffold(
appBar: AppBar(centerTitle: false, title: Text(context.t.whats_new)),
body: ListView.separated(
padding: const EdgeInsets.only(top: 16, bottom: 64),
itemCount: highlights.length,
separatorBuilder: (_, __) => const SizedBox(height: 24),
itemBuilder: (_, index) => _HighlightCard(highlight: highlights[index]),
),
);
}
}
class _HighlightCard extends StatelessWidget {
final FeatureHighlight highlight;
const _HighlightCard({required this.highlight});
@override
Widget build(BuildContext context) {
final scheme = context.colorScheme;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DecoratedBox(
decoration: BoxDecoration(
color: scheme.surfaceContainerHighest,
borderRadius: const BorderRadius.all(Radius.circular(18)),
border: Border.all(color: scheme.outlineVariant.withValues(alpha: 0.5)),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(18)),
child: SizedBox(
width: double.infinity,
height: 256,
child: highlight.image == null
? const FeatureMessagePlaceholder()
: Image.asset(
highlight.image!,
fit: BoxFit.contain,
errorBuilder: (context, _, __) => const FeatureMessagePlaceholder(),
),
),
),
),
const SizedBox(height: 12),
Text(highlight.titleKey.tr(), style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600)),
const SizedBox(height: 4),
Text(
highlight.bodyKey.tr(),
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceVariant),
),
],
),
);
}
}
@@ -1,24 +1,26 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/presentation/actions/action.widget.dart';
import 'package:immich_mobile/presentation/actions/archive.action.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/routes.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_ui/immich_ui.dart';
import 'package:immich_mobile/providers/user.provider.dart';
enum AddToMenuItem { album, lockedFolder }
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
enum AddToMenuItem { album, archive, unarchive, lockedFolder }
class AddActionButton extends ConsumerStatefulWidget {
const AddActionButton({super.key, this.originalTheme});
@@ -35,6 +37,12 @@ class _AddActionButtonState extends ConsumerState<AddActionButton> {
case AddToMenuItem.album:
_openAlbumSelector();
break;
case AddToMenuItem.archive:
performArchiveAction(context, ref, source: ActionSource.viewer);
break;
case AddToMenuItem.unarchive:
performUnArchiveAction(context, ref, source: ActionSource.viewer);
break;
case AddToMenuItem.lockedFolder:
performMoveToLockFolderAction(context, ref, source: ActionSource.viewer);
break;
@@ -49,6 +57,11 @@ class _AddActionButtonState extends ConsumerState<AddActionButton> {
final user = ref.read(currentUserProvider);
final isOwner = asset is RemoteAsset && asset.ownerId == user?.id;
final isInLockedView = ref.watch(inLockedViewProvider);
final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive;
final hasRemote = asset is RemoteAsset;
final showArchive = isOwner && !isInLockedView && hasRemote && !isArchived;
final showUnarchive = isOwner && !isInLockedView && hasRemote && isArchived;
return [
Padding(
@@ -68,7 +81,20 @@ class _AddActionButtonState extends ConsumerState<AddActionButton> {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text("move_to".tr(), style: context.textTheme.labelMedium),
),
ActionMenuItemWidget(action: ArchiveAction(assets: [asset])),
if (showArchive)
BaseActionButton(
iconData: Icons.archive_outlined,
label: "archive".tr(),
menuItem: true,
onPressed: () => _handleMenuSelection(AddToMenuItem.archive),
),
if (showUnarchive)
BaseActionButton(
iconData: Icons.unarchive_outlined,
label: "unarchive".tr(),
menuItem: true,
onPressed: () => _handleMenuSelection(AddToMenuItem.unarchive),
),
BaseActionButton(
iconData: Icons.lock_outline,
label: "locked_folder".tr(),
@@ -158,7 +184,7 @@ class _AddActionButtonState extends ConsumerState<AddActionButton> {
final themeData = widget.originalTheme ?? context.themeData;
return ImmichMenu(
return MenuAnchor(
consumeOutsideTap: true,
style: MenuStyle(
backgroundColor: WidgetStatePropertyAll(themeData.scaffoldBackgroundColor),
@@ -169,7 +195,7 @@ class _AddActionButtonState extends ConsumerState<AddActionButton> {
),
padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(vertical: 6)),
),
children: widget.originalTheme != null
menuChildren: widget.originalTheme != null
? [
Theme(
data: widget.originalTheme!,
@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
// used to allow performing archive action from different sources (without duplicating code)
Future<void> performArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async {
if (!context.mounted) {
return;
}
if (source == ActionSource.viewer) {
EventStream.shared.emit(const ViewerReloadAssetEvent());
}
final result = await ref.read(actionProvider.notifier).archive(source);
ref.read(multiSelectProvider.notifier).reset();
final successMessage = 'archive_action_prompt'.t(context: context, args: {'count': result.count.toString()});
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
}
class ArchiveActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const ArchiveActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
Future<void> _onTap(BuildContext context, WidgetRef ref) async {
await performArchiveAction(context, ref, source: source);
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.archive_outlined,
label: "to_archive".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}
}
@@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
class FavoriteActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const FavoriteActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
return;
}
final result = await ref.read(actionProvider.notifier).favorite(source);
if (source == ActionSource.viewer) {
if (result.success) {
final currentAsset = ref.read(assetViewerProvider).currentAsset;
if (currentAsset is RemoteAsset && !currentAsset.isFavorite) {
ref.read(assetViewerProvider.notifier).setAsset(currentAsset.copyWith(isFavorite: true));
}
}
return;
}
ref.read(multiSelectProvider.notifier).reset();
final successMessage = 'favorite_action_prompt'.t(context: context, args: {'count': result.count.toString()});
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.favorite_border_rounded,
label: "favorite".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}
}
@@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
class RestoreActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const RestoreActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
return;
}
final result = await ref.read(actionProvider.notifier).restoreTrash(source);
ref.read(multiSelectProvider.notifier).reset();
if (source == ActionSource.viewer) {
EventStream.shared.emit(const ViewerReloadAssetEvent());
}
final successMessage = 'assets_restored_count'.t(context: context, args: {'count': result.count.toString()});
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.history_rounded,
label: 'restore'.t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
maxWidth: 100.0,
);
}
}
@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
class RestoreTrashActionButton extends ConsumerWidget {
final ActionSource source;
const RestoreTrashActionButton({super.key, required this.source});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
return;
}
final result = await ref.read(actionProvider.notifier).restoreTrash(source);
ref.read(multiSelectProvider.notifier).reset();
final successMessage = 'assets_restored_count'.t(context: context, args: {'count': result.count.toString()});
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return TextButton.icon(
icon: const Icon(Icons.history_rounded),
label: Text('restore'.t(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
onPressed: () => _onTap(context, ref),
);
}
}
@@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
class StackActionButton extends ConsumerWidget {
final ActionSource source;
const StackActionButton({super.key, required this.source});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
return;
}
final user = ref.watch(currentUserProvider);
if (user == null) {
throw Exception('User must be logged in to access stack action');
}
final result = await ref.read(actionProvider.notifier).stack(user.id, source);
ref.read(multiSelectProvider.notifier).reset();
final successMessage = 'stack_action_prompt'.t(context: context, args: {'count': result.count.toString()});
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.filter_none_rounded,
label: "stack".t(context: context),
onPressed: () => _onTap(context, ref),
);
}
}
@@ -0,0 +1,61 @@
// dart
// File: `lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart`
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
// used to allow performing unarchive action from different sources (without duplicating code)
Future<void> performUnArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async {
if (!context.mounted) {
return;
}
if (source == ActionSource.viewer) {
EventStream.shared.emit(const ViewerReloadAssetEvent());
}
final result = await ref.read(actionProvider.notifier).unArchive(source);
ref.read(multiSelectProvider.notifier).reset();
final successMessage = 'unarchive_action_prompt'.t(context: context, args: {'count': result.count.toString()});
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
}
class UnArchiveActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const UnArchiveActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
Future<void> _onTap(BuildContext context, WidgetRef ref) async {
await performUnArchiveAction(context, ref, source: source);
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.unarchive_outlined,
label: "unarchive".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}
}
@@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
class UnFavoriteActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const UnFavoriteActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
return;
}
final result = await ref.read(actionProvider.notifier).unFavorite(source);
if (source == ActionSource.viewer) {
if (result.success) {
final currentAsset = ref.read(assetViewerProvider).currentAsset;
if (currentAsset is RemoteAsset && currentAsset.isFavorite) {
ref.read(assetViewerProvider.notifier).setAsset(currentAsset.copyWith(isFavorite: false));
}
}
return;
}
ref.read(multiSelectProvider.notifier).reset();
final successMessage = 'unfavorite_action_prompt'.t(context: context, args: {'count': result.count.toString()});
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.favorite_rounded,
label: "unfavorite".t(context: context),
onPressed: () => _onTap(context, ref),
iconOnly: iconOnly,
menuItem: menuItem,
);
}
}
@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
class UnStackActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const UnStackActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
return;
}
final result = await ref.read(actionProvider.notifier).unStack(source);
ref.read(multiSelectProvider.notifier).reset();
final successMessage = 'unstack_action_prompt'.t(context: context, args: {'count': result.count.toString()});
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.layers_clear_outlined,
label: "unstack".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}
}
@@ -4,13 +4,12 @@ import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/presentation/actions/action.widget.dart';
import 'package:immich_mobile/presentation/actions/restore.action.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/add_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/edit_image_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/restore_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/ocr_toggle_button.widget.dart';
@@ -43,10 +42,11 @@ class ViewerBottomBar extends ConsumerWidget {
final originalTheme = context.themeData;
final assets = [asset];
final actions = <Widget>[
ActionColumnButtonWidget(action: RestoreAction(assets: assets)),
const ShareActionButton(source: ActionSource.viewer),
if (isInTrash && isOwner && asset.hasRemote)
const RestoreActionButton(source: ActionSource.viewer)
else
const ShareActionButton(source: ActionSource.viewer),
if (!isInLockedView) ...[
if (!isInTrash) ...[
@@ -4,9 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/presentation/actions/action.widget.dart';
import 'package:immich_mobile/presentation/actions/archive.action.dart';
import 'package:immich_mobile/presentation/actions/favorite.action.dart';
import 'package:immich_mobile/presentation/actions/stack.action.dart';
import 'package:immich_mobile/presentation/actions/timeline.action.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart';
@@ -16,7 +14,10 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/edit_location_
import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
@@ -76,7 +77,7 @@ class _ArchiveBottomSheetState extends ConsumerState<ArchiveBottomSheet> {
}
final assets = multiselect.selectedAssets.toList(growable: false);
final actions = [FavoriteAction(assets: assets), ArchiveAction(assets: assets), StackAction(assets: assets)];
final actions = [FavoriteAction(assets: assets)];
return BaseBottomSheet(
controller: sheetController,
@@ -87,6 +88,7 @@ class _ArchiveBottomSheetState extends ConsumerState<ArchiveBottomSheet> {
const ShareActionButton(source: ActionSource.timeline),
if (multiselect.hasRemote) ...[
const ShareLinkActionButton(source: ActionSource.timeline),
const UnArchiveActionButton(source: ActionSource.timeline),
...actions.map((action) => ActionColumnButtonWidget(action: TimelineAction(action: action))),
if (multiselect.onlyRemote) const DownloadActionButton(source: ActionSource.timeline),
isTrashEnable
@@ -95,6 +97,8 @@ class _ArchiveBottomSheetState extends ConsumerState<ArchiveBottomSheet> {
const EditDateTimeActionButton(source: ActionSource.timeline),
const EditLocationActionButton(source: ActionSource.timeline),
const MoveToLockFolderActionButton(source: ActionSource.timeline),
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
],
if (multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline),
],
@@ -5,10 +5,9 @@ import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/actions/action.widget.dart';
import 'package:immich_mobile/presentation/actions/archive.action.dart';
import 'package:immich_mobile/presentation/actions/favorite.action.dart';
import 'package:immich_mobile/presentation/actions/stack.action.dart';
import 'package:immich_mobile/presentation/actions/timeline.action.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart';
@@ -17,7 +16,9 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/edit_location_
import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
@@ -67,7 +68,7 @@ class FavoriteBottomSheet extends ConsumerWidget {
}
final assets = multiselect.selectedAssets.toList(growable: false);
final actions = [FavoriteAction(assets: assets), ArchiveAction(assets: assets), StackAction(assets: assets)];
final actions = [FavoriteAction(assets: assets)];
return BaseBottomSheet(
initialChildSize: 0.4,
@@ -78,6 +79,7 @@ class FavoriteBottomSheet extends ConsumerWidget {
if (multiselect.hasRemote) ...[
const ShareLinkActionButton(source: ActionSource.timeline),
...actions.map((action) => ActionColumnButtonWidget(action: TimelineAction(action: action))),
const ArchiveActionButton(source: ActionSource.timeline),
if (multiselect.onlyRemote) const DownloadActionButton(source: ActionSource.timeline),
isTrashEnable
? const TrashActionButton(source: ActionSource.timeline)
@@ -85,6 +87,8 @@ class FavoriteBottomSheet extends ConsumerWidget {
const EditDateTimeActionButton(source: ActionSource.timeline),
const EditLocationActionButton(source: ActionSource.timeline),
const MoveToLockFolderActionButton(source: ActionSource.timeline),
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
],
if (multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline),
],
@@ -4,11 +4,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/presentation/actions/action.widget.dart';
import 'package:immich_mobile/presentation/actions/archive.action.dart';
import 'package:immich_mobile/presentation/actions/asset_debug.action.dart';
import 'package:immich_mobile/presentation/actions/favorite.action.dart';
import 'package:immich_mobile/presentation/actions/stack.action.dart';
import 'package:immich_mobile/presentation/actions/timeline.action.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/bulk_tag_assets_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
@@ -16,10 +14,13 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permane
import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/edit_location_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
@@ -83,12 +84,7 @@ class _GeneralBottomSheetState extends ConsumerState<GeneralBottomSheet> {
}
final assets = multiselect.selectedAssets.toList(growable: false);
final actions = [
AssetDebugAction(assets: assets),
FavoriteAction(assets: assets),
ArchiveAction(assets: assets),
StackAction(assets: assets),
];
final actions = [AssetDebugAction(assets: assets)];
return BaseBottomSheet(
controller: sheetController,
@@ -105,10 +101,14 @@ class _GeneralBottomSheetState extends ConsumerState<GeneralBottomSheet> {
isTrashEnable
? const TrashActionButton(source: ActionSource.timeline)
: const DeletePermanentActionButton(source: ActionSource.timeline),
const FavoriteActionButton(source: ActionSource.timeline),
const ArchiveActionButton(source: ActionSource.timeline),
if (tagsEnabled) const BulkTagAssetsActionButton(source: ActionSource.timeline),
const EditDateTimeActionButton(source: ActionSource.timeline),
const EditLocationActionButton(source: ActionSource.timeline),
const MoveToLockFolderActionButton(source: ActionSource.timeline),
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
if (multiselect.onlyLocal || multiselect.hasMerged) const DeleteActionButton(source: ActionSource.timeline),
],
if (multiselect.onlyLocal || multiselect.hasMerged)
@@ -4,10 +4,9 @@ import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/actions/action.widget.dart';
import 'package:immich_mobile/presentation/actions/archive.action.dart';
import 'package:immich_mobile/presentation/actions/favorite.action.dart';
import 'package:immich_mobile/presentation/actions/stack.action.dart';
import 'package:immich_mobile/presentation/actions/timeline.action.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart';
@@ -18,7 +17,9 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/remove_from_al
import 'package:immich_mobile/presentation/widgets/action_buttons/set_album_cover.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
@@ -85,7 +86,7 @@ class _RemoteAlbumBottomSheetState extends ConsumerState<RemoteAlbumBottomSheet>
}
final assets = multiselect.selectedAssets.toList(growable: false);
final actions = [FavoriteAction(assets: assets), ArchiveAction(assets: assets), StackAction(assets: assets)];
final actions = [FavoriteAction(assets: assets)];
return BaseBottomSheet(
controller: sheetController,
@@ -99,6 +100,7 @@ class _RemoteAlbumBottomSheetState extends ConsumerState<RemoteAlbumBottomSheet>
const ShareLinkActionButton(source: ActionSource.timeline),
if (ownsAlbum) ...[
const ArchiveActionButton(source: ActionSource.timeline),
...actions.map((action) => ActionColumnButtonWidget(action: TimelineAction(action: action))),
],
const DownloadActionButton(source: ActionSource.timeline),
@@ -109,6 +111,8 @@ class _RemoteAlbumBottomSheetState extends ConsumerState<RemoteAlbumBottomSheet>
const EditDateTimeActionButton(source: ActionSource.timeline),
const EditLocationActionButton(source: ActionSource.timeline),
const MoveToLockFolderActionButton(source: ActionSource.timeline),
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
],
],
if (multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline),
@@ -2,33 +2,26 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/presentation/actions/action.widget.dart';
import 'package:immich_mobile/presentation/actions/restore.action.dart';
import 'package:immich_mobile/presentation/actions/timeline.action.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_trash_action_button.widget.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/restore_trash_action_button.widget.dart';
class TrashBottomBar extends ConsumerWidget {
const TrashBottomBar({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final assets = ref.watch(multiSelectProvider.select((s) => s.selectedAssets)).toList(growable: false);
return Align(
alignment: Alignment.bottomCenter,
child: Container(
color: context.themeData.canvasColor,
padding: const EdgeInsets.symmetric(vertical: 8),
child: SafeArea(
child: const SafeArea(
top: false,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const DeleteTrashActionButton(source: ActionSource.timeline),
ActionColumnButtonWidget(
action: TimelineAction(action: RestoreAction(assets: assets)),
),
DeleteTrashActionButton(source: ActionSource.timeline),
RestoreTrashActionButton(source: ActionSource.timeline),
],
),
),
@@ -0,0 +1,316 @@
import 'dart:math' as math;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:immich_mobile/domain/models/feature_message.model.dart';
import 'package:immich_mobile/presentation/widgets/feature_message/feature_message_placeholder.widget.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
Future<void> showFeatureMessageDialog(BuildContext context) {
return showGeneralDialog<void>(
context: context,
useRootNavigator: true,
barrierDismissible: true,
barrierLabel: context.t.whats_new,
barrierColor: Colors.black.withValues(alpha: 0.55),
transitionDuration: const Duration(milliseconds: 280),
pageBuilder: (_, __, ___) => const _FeatureMessageDialog(),
transitionBuilder: (_, animation, __, child) {
final curved = CurvedAnimation(parent: animation, curve: Curves.easeOutCubic, reverseCurve: Curves.easeInCubic);
return FadeTransition(
opacity: animation,
child: ScaleTransition(scale: Tween<double>(begin: 0.94, end: 1.0).animate(curved), child: child),
);
},
);
}
class _FeatureMessageDialog extends StatefulWidget {
const _FeatureMessageDialog();
@override
State<_FeatureMessageDialog> createState() => _FeatureMessageDialogState();
}
class _FeatureMessageDialogState extends State<_FeatureMessageDialog> with SingleTickerProviderStateMixin {
static const double _radius = 24;
final PageController _controller = PageController();
late final AnimationController _borderController = AnimationController(
vsync: this,
duration: const Duration(seconds: 7),
)..repeat();
final List<FeatureHighlight> _highlights = visibleFeatureMessageHighlights;
int _index = 0;
bool get _isLast => _index >= _highlights.length - 1;
@override
void dispose() {
_controller.dispose();
_borderController.dispose();
super.dispose();
}
void _advance() {
if (_isLast) {
Navigator.of(context).pop();
return;
}
_controller.nextPage(duration: const Duration(milliseconds: 320), curve: Curves.easeOutCubic);
}
List<Color> _borderColors(BuildContext context) {
final scheme = context.colorScheme;
// Mute the hues toward the surface and drop opacity in dark mode to keep it gentle.
Color tone(Color c) => context.isDarkTheme ? Color.lerp(c, scheme.surface, 0.45)!.withValues(alpha: 0.6) : c;
return [tone(scheme.primary), tone(scheme.tertiary), tone(scheme.secondary), tone(scheme.primary)];
}
@override
Widget build(BuildContext context) {
return Dialog(
insetPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 64),
clipBehavior: Clip.antiAlias,
backgroundColor: context.isDarkTheme ? context.colorScheme.surfaceContainerLow : Colors.white,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(_radius))),
child: AnimatedBuilder(
animation: _borderController,
builder: (context, child) => CustomPaint(
foregroundPainter: _GradientBorderPainter(
rotation: _borderController.value,
colors: _borderColors(context),
radius: _radius,
strokeWidth: 3,
),
child: child,
),
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: context.height * 0.9, maxWidth: 480),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(24, 20, 24, 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
context.t.whats_new,
style: context.textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700),
),
const SizedBox(height: 2),
Text(
context.t.whats_new_version(version: featureMessageRelease.toString()),
style: context.textTheme.bodyLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary),
),
],
),
),
const SizedBox(height: 32),
Expanded(
child: PageView.builder(
controller: _controller,
itemCount: _highlights.length,
onPageChanged: (i) => setState(() => _index = i),
itemBuilder: (_, index) => _FeaturePage(highlight: _highlights[index]),
),
),
const SizedBox(height: 8),
_PageDots(controller: _controller, index: _index, count: _highlights.length),
Padding(
padding: const EdgeInsets.fromLTRB(20, 18, 20, 26),
child: Row(
children: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
style: TextButton.styleFrom(padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14)),
child: Text(context.t.skip),
),
const SizedBox(width: 8),
Expanded(
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(100)),
boxShadow: [
// Soft wide primary glow.
BoxShadow(
color: context.primaryColor.withValues(alpha: 0.38),
blurRadius: 22,
spreadRadius: -4,
offset: const Offset(0, 10),
),
// Tight contact shadow for grounding.
BoxShadow(
color: context.primaryColor.withValues(alpha: 0.22),
blurRadius: 6,
offset: const Offset(0, 2),
),
],
),
child: FilledButton(
onPressed: _advance,
style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
elevation: 0,
textStyle: context.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 16,
),
),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: Text(_isLast ? context.t.ok : context.t.next, key: ValueKey(_isLast)),
),
),
),
),
],
),
),
],
),
),
),
);
}
}
class _GradientBorderPainter extends CustomPainter {
const _GradientBorderPainter({
required this.rotation,
required this.colors,
required this.radius,
this.strokeWidth = 3,
});
final double rotation;
final List<Color> colors;
final double radius;
final double strokeWidth;
@override
void paint(Canvas canvas, Size size) {
final inset = strokeWidth / 2;
final rect = (Offset.zero & size).deflate(inset);
final rrect = RRect.fromRectAndRadius(rect, Radius.circular(radius - inset));
final shader = SweepGradient(
transform: GradientRotation(rotation * 2 * math.pi),
colors: colors,
).createShader(rect);
final paint = Paint()
..shader = shader
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth;
canvas.drawRRect(rrect, paint);
}
@override
bool shouldRepaint(_GradientBorderPainter oldDelegate) =>
oldDelegate.rotation != rotation || !listEquals(oldDelegate.colors, colors);
}
class _FeaturePage extends StatelessWidget {
final FeatureHighlight highlight;
const _FeaturePage({required this.highlight});
@override
Widget build(BuildContext context) {
final scheme = context.colorScheme;
return SingleChildScrollView(
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 0),
child: DecoratedBox(
decoration: BoxDecoration(
color: scheme.surfaceContainerHighest,
borderRadius: const BorderRadius.all(Radius.circular(18)),
border: Border.all(color: scheme.outlineVariant.withValues(alpha: 0.5)),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(18)),
child: SizedBox(
width: double.infinity,
height: 256,
child: highlight.image == null
? const FeatureMessagePlaceholder()
: Image.asset(
highlight.image!,
fit: BoxFit.contain,
errorBuilder: (context, _, __) => const FeatureMessagePlaceholder(),
),
),
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(24, 18, 24, 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
highlight.titleKey.tr(),
style: context.textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700, fontSize: 24),
),
const SizedBox(height: 8),
Text(
highlight.bodyKey.tr(),
style: context.textTheme.bodyLarge?.copyWith(color: scheme.onSurfaceVariant, height: 1.4),
),
],
),
),
],
),
);
}
}
class _PageDots extends StatelessWidget {
final PageController controller;
final int index;
final int count;
const _PageDots({required this.controller, required this.index, required this.count});
@override
Widget build(BuildContext context) {
final primary = context.primaryColor;
return AnimatedBuilder(
animation: controller,
builder: (context, _) {
final page = controller.hasClients ? (controller.page ?? index.toDouble()) : index.toDouble();
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(count, (i) {
final activeness = (1 - (page - i).abs()).clamp(0.0, 1.0);
return AnimatedContainer(
duration: const Duration(milliseconds: 150),
margin: const EdgeInsets.symmetric(horizontal: 3),
height: 7,
width: 7 + 16 * activeness,
decoration: BoxDecoration(
color: Color.lerp(context.colorScheme.surfaceContainerHighest, primary, activeness),
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
);
}),
);
},
);
}
}
@@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/widgets/common/immich_logo.dart';
class _SplatColors {
static const primary = Color(0xFF4250AF);
static const info = Color(0xFF3B82F6);
static const success = Color(0xFF2FB457);
static const warning = Color(0xFFF2A73B);
static const danger = Color(0xFFE5484D);
}
class FeatureMessagePlaceholder extends StatelessWidget {
const FeatureMessagePlaceholder({super.key});
@override
Widget build(BuildContext context) {
final dark = Theme.of(context).brightness == Brightness.dark;
final cardColor = dark ? const Color(0xFF232228) : const Color(0xFFEEEDF4);
final tileColor = dark ? const Color(0xFF2B2A32) : const Color(0xFFFBFAFE);
final inkColor = dark ? const Color(0xFFE7E7EC) : const Color(0xFF1A1A1E);
return Container(
width: double.infinity,
height: double.infinity,
clipBehavior: Clip.antiAlias,
// Fill a plain rectangle the parent's ClipRRect handles the corner rounding,
// so the placeholder doesn't round its own corners inside that clip.
decoration: BoxDecoration(color: cardColor),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// ---- confetti motif (168 × 120 region) ----
SizedBox(
width: 168,
height: 120,
child: Stack(
clipBehavior: Clip.none,
children: [
// scattered confetti
Positioned(left: 6, top: 24, child: _dot(12, _SplatColors.primary)),
Positioned(left: 80, top: -2, child: _dot(9, _SplatColors.danger)),
Positioned(left: 148, top: 84, child: _dot(11, _SplatColors.success)),
Positioned(left: 140, top: 14, child: _bar(22, 8, 0.49, _SplatColors.danger)), // ~28°
Positioned(left: 2, top: 90, child: _bar(20, 8, -0.31, _SplatColors.info)), // ~-18°
// tilted spark tile
Positioned(
left: 46,
top: 18,
child: Transform.rotate(
angle: -0.105, // ~-6°
child: Container(
width: 84,
height: 84,
decoration: BoxDecoration(
color: tileColor,
borderRadius: const BorderRadius.all(Radius.circular(18)),
boxShadow: [
BoxShadow(
color: const Color(0xFF0F122D).withValues(alpha: 0.22),
blurRadius: 22,
offset: const Offset(0, 10),
),
],
),
child: Stack(
alignment: Alignment.center,
children: [
Positioned(left: 12, top: 12, child: _dot(12, _SplatColors.warning)),
const ImmichLogo(size: 40),
],
),
),
),
),
],
),
),
const SizedBox(height: 16),
Text(
context.t.new_feature,
style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600, color: inkColor),
),
],
),
);
}
static Widget _dot(double d, Color c) => Container(
width: d,
height: d,
decoration: BoxDecoration(color: c, shape: BoxShape.circle),
);
static Widget _bar(double w, double h, double angle, Color c) => Transform.rotate(
angle: angle,
child: Container(
width: w,
height: h,
decoration: BoxDecoration(color: c, borderRadius: const BorderRadius.all(Radius.circular(99))),
),
);
}
@@ -0,0 +1,7 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/feature_message.service.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
final featureMessageServiceProvider = Provider<FeatureMessageService>(
(ref) => FeatureMessageService(ref.read(settingsProvider)),
);
@@ -156,6 +156,50 @@ class ActionNotifier extends Notifier<void> {
}
}
Future<ActionResult> favorite(ActionSource source) async {
final ids = _getOwnedRemoteIdsForSource(source);
try {
await _service.favorite(ids);
return ActionResult(count: ids.length, success: true);
} catch (error, stack) {
_logger.severe('Failed to favorite assets', error, stack);
return ActionResult(count: ids.length, success: false, error: error.toString());
}
}
Future<ActionResult> unFavorite(ActionSource source) async {
final ids = _getOwnedRemoteIdsForSource(source);
try {
await _service.unFavorite(ids);
return ActionResult(count: ids.length, success: true);
} catch (error, stack) {
_logger.severe('Failed to unfavorite assets', error, stack);
return ActionResult(count: ids.length, success: false, error: error.toString());
}
}
Future<ActionResult> archive(ActionSource source) async {
final ids = _getOwnedRemoteIdsForSource(source);
try {
await _service.archive(ids);
return ActionResult(count: ids.length, success: true);
} catch (error, stack) {
_logger.severe('Failed to archive assets', error, stack);
return ActionResult(count: ids.length, success: false, error: error.toString());
}
}
Future<ActionResult> unArchive(ActionSource source) async {
final ids = _getOwnedRemoteIdsForSource(source);
try {
await _service.unArchive(ids);
return ActionResult(count: ids.length, success: true);
} catch (error, stack) {
_logger.severe('Failed to unarchive assets', error, stack);
return ActionResult(count: ids.length, success: false, error: error.toString());
}
}
Future<ActionResult> moveToLockFolder(ActionSource source) async {
final ids = _getOwnedRemoteIdsForSource(source);
final localIds = _getLocalIdsForSource(source, ignoreLocalOnly: true);
@@ -191,6 +235,17 @@ class ActionNotifier extends Notifier<void> {
}
}
Future<ActionResult> restoreTrash(ActionSource source) async {
final ids = _getOwnedRemoteIdsForSource(source);
try {
await _service.restoreTrash(ids);
return ActionResult(count: ids.length, success: true);
} catch (error, stack) {
_logger.severe('Failed to restore trash assets', error, stack);
return ActionResult(count: ids.length, success: false, error: error.toString());
}
}
Future<ActionResult> emptyTrash(String userId) async {
try {
final count = await _service.emptyTrash(userId);
@@ -1,4 +0,0 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/repositories/toast.repository.dart';
final toastRepositoryProvider = Provider<ToastRepository>((ref) => const .new());
@@ -22,6 +22,8 @@ class MultiSelectState {
bool get hasRemote =>
selectedAssets.any((asset) => asset.storage == AssetState.remote || asset.storage == AssetState.merged);
bool get hasStacked => selectedAssets.any((asset) => asset is RemoteAsset && asset.stackId != null);
bool get hasMerged => selectedAssets.any((asset) => asset.storage == AssetState.merged);
bool get onlyLocal => selectedAssets.any((asset) => asset.storage == AssetState.local);
@@ -1,14 +1,12 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset_edit.model.dart' hide AssetEditAction;
import 'package:immich_mobile/domain/models/stack.model.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/api.repository.dart';
import 'package:immich_mobile/utils/option.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:openapi/api.dart' as api show AssetVisibility;
import 'package:openapi/api.dart' hide AssetVisibility;
import 'package:openapi/api.dart';
final assetApiRepositoryProvider = Provider(
(ref) => AssetApiRepository(
@@ -43,7 +41,7 @@ class AssetApiRepository extends ApiRepository {
return response?.count ?? 0;
}
Future<void> updateVisibility(List<String> ids, AssetVisibility visibility) async {
Future<void> updateVisibility(List<String> ids, AssetVisibilityEnum visibility) async {
return _api.updateAssets(AssetBulkUpdateDto(ids: ids, visibility: Optional.present(_mapVisibility(visibility))));
}
@@ -79,11 +77,11 @@ class AssetApiRepository extends ApiRepository {
return _api.downloadAssetWithHttpInfo(id, edited: edited);
}
api.AssetVisibility _mapVisibility(AssetVisibility visibility) => switch (visibility) {
AssetVisibility.timeline => api.AssetVisibility.timeline,
AssetVisibility.hidden => api.AssetVisibility.hidden,
AssetVisibility.locked => api.AssetVisibility.locked,
AssetVisibility.archive => api.AssetVisibility.archive,
_mapVisibility(AssetVisibilityEnum visibility) => switch (visibility) {
AssetVisibilityEnum.timeline => AssetVisibility.timeline,
AssetVisibilityEnum.hidden => AssetVisibility.hidden,
AssetVisibilityEnum.locked => AssetVisibility.locked,
AssetVisibilityEnum.archive => AssetVisibility.archive,
};
Future<String?> getAssetMIMEType(String assetId) async {
@@ -108,20 +106,6 @@ class AssetApiRepository extends ApiRepository {
Future<void> removeEdits(String assetId) async {
return _api.removeAssetEdits(assetId);
}
Future<void> update(
List<String> remoteIds, {
Option<bool> isFavorite = const .none(),
Option<AssetVisibility> visibility = const .none(),
}) {
return _api.updateAssets(
AssetBulkUpdateDto(
ids: remoteIds,
isFavorite: isFavorite.toOptional(),
visibility: visibility.map(_mapVisibility).toOptional(),
),
);
}
}
extension on StackResponseDto {
@@ -1,26 +0,0 @@
import 'dart:async';
import 'package:immich_ui/immich_ui.dart';
class ToastOption {
final Duration? timeout;
final FutureOr<void> Function()? onUndo;
const ToastOption({this.timeout, this.onUndo});
}
class ToastRepository {
const ToastRepository();
FutureOr<void> success(String message, {ToastOption? toast}) {
snackbar.success(message, duration: toast?.timeout);
}
FutureOr<void> info(String message, {ToastOption? toast}) {
snackbar.info(message, duration: toast?.timeout);
}
FutureOr<void> error(String message, {ToastOption? toast}) {
snackbar.error(message, duration: toast?.timeout);
}
}
+2
View File
@@ -38,6 +38,7 @@ import 'package:immich_mobile/pages/share_intent/share_intent.page.dart';
import 'package:immich_mobile/presentation/pages/cleanup_preview.page.dart';
import 'package:immich_mobile/presentation/pages/dev/main_timeline.page.dart';
import 'package:immich_mobile/presentation/pages/dev/media_stat.page.dart';
import 'package:immich_mobile/presentation/pages/feature_message/whats_new.page.dart';
import 'package:immich_mobile/presentation/pages/download_info.page.dart';
import 'package:immich_mobile/presentation/pages/drift_activities.page.dart';
import 'package:immich_mobile/presentation/pages/drift_album.page.dart';
@@ -131,6 +132,7 @@ class AppRouter extends RootStackRouter {
AutoRoute(page: ProfilePictureCropRoute.page),
AutoRoute(page: SettingsRoute.page, guards: [_duplicateGuard]),
AutoRoute(page: SettingsSubRoute.page, guards: [_duplicateGuard]),
AutoRoute(page: WhatsNewRoute.page, guards: [_duplicateGuard]),
AutoRoute(page: AppLogRoute.page, guards: [_duplicateGuard]),
AutoRoute(page: AppLogDetailRoute.page, guards: [_duplicateGuard]),
AutoRoute(page: FolderRoute.page, guards: [_authGuard]),
+16
View File
@@ -1872,3 +1872,19 @@ class TabShellRoute extends PageRouteInfo<void> {
},
);
}
/// generated route for
/// [WhatsNewPage]
class WhatsNewRoute extends PageRouteInfo<void> {
const WhatsNewRoute({List<PageRouteInfo>? children})
: super(WhatsNewRoute.name, initialChildren: children);
static const String name = 'WhatsNewRoute';
static PageInfo page = PageInfo(
name,
builder: (data) {
return const WhatsNewPage();
},
);
}
+27 -2
View File
@@ -68,8 +68,28 @@ class ActionService {
unawaited(context.pushRoute(SharedLinkEditRoute(assetsList: remoteIds)));
}
Future<void> favorite(List<String> remoteIds) async {
await _assetApiRepository.updateFavorite(remoteIds, true);
await _remoteAssetRepository.updateFavorite(remoteIds, true);
}
Future<void> unFavorite(List<String> remoteIds) async {
await _assetApiRepository.updateFavorite(remoteIds, false);
await _remoteAssetRepository.updateFavorite(remoteIds, false);
}
Future<void> archive(List<String> remoteIds) async {
await _assetApiRepository.updateVisibility(remoteIds, AssetVisibilityEnum.archive);
await _remoteAssetRepository.updateVisibility(remoteIds, AssetVisibility.archive);
}
Future<void> unArchive(List<String> remoteIds) async {
await _assetApiRepository.updateVisibility(remoteIds, AssetVisibilityEnum.timeline);
await _remoteAssetRepository.updateVisibility(remoteIds, AssetVisibility.timeline);
}
Future<void> moveToLockFolder(List<String> remoteIds, List<String> localIds) async {
await _assetApiRepository.updateVisibility(remoteIds, .locked);
await _assetApiRepository.updateVisibility(remoteIds, AssetVisibilityEnum.locked);
await _remoteAssetRepository.updateVisibility(remoteIds, AssetVisibility.locked);
// Ask user if they want to delete local copies
@@ -79,7 +99,7 @@ class ActionService {
}
Future<void> removeFromLockFolder(List<String> remoteIds) async {
await _assetApiRepository.updateVisibility(remoteIds, .timeline);
await _assetApiRepository.updateVisibility(remoteIds, AssetVisibilityEnum.timeline);
await _remoteAssetRepository.updateVisibility(remoteIds, AssetVisibility.timeline);
}
@@ -88,6 +108,11 @@ class ActionService {
await _remoteAssetRepository.trash(remoteIds);
}
Future<void> restoreTrash(List<String> ids) async {
await _assetApiRepository.restoreTrash(ids);
await _remoteAssetRepository.restoreTrash(ids);
}
Future<int> emptyTrash(String userId) async {
final count = await _assetApiRepository.emptyTrash();
await _remoteAssetRepository.emptyTrash(userId);
+16 -7
View File
@@ -8,10 +8,8 @@ import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/presentation/actions/action.widget.dart';
import 'package:immich_mobile/presentation/actions/archive.action.dart';
import 'package:immich_mobile/presentation/actions/asset_debug.action.dart';
import 'package:immich_mobile/presentation/actions/stack.action.dart';
import 'package:immich_mobile/presentation/actions/restore.action.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/cast_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart';
@@ -23,6 +21,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_f
import 'package:immich_mobile/presentation/widgets/action_buttons/open_in_browser_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/restore_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/set_album_cover.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/set_profile_picture_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart';
@@ -30,6 +29,8 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_act
import 'package:immich_mobile/presentation/widgets/action_buttons/similar_photos_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/slideshow_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
import 'package:immich_mobile/routing/router.dart';
@@ -199,11 +200,19 @@ enum ActionButtonType {
menuItem: menuItem,
),
ActionButtonType.slideshow => SlideshowActionButton(iconOnly: iconOnly, menuItem: menuItem),
ActionButtonType.archive ||
ActionButtonType.unarchive => ActionMenuItemWidget(action: ArchiveAction(assets: [context.asset])),
ActionButtonType.archive => ArchiveActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem),
ActionButtonType.unarchive => UnArchiveActionButton(
source: context.source,
iconOnly: iconOnly,
menuItem: menuItem,
),
ActionButtonType.download => DownloadActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem),
ActionButtonType.trash => TrashActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem),
ActionButtonType.restoreTrash => ActionMenuItemWidget(action: RestoreAction(assets: [context.asset])),
ActionButtonType.restoreTrash => RestoreActionButton(
source: context.source,
iconOnly: iconOnly,
menuItem: menuItem,
),
ActionButtonType.deletePermanent => DeletePermanentActionButton(
source: context.source,
iconOnly: iconOnly,
@@ -239,7 +248,7 @@ enum ActionButtonType {
menuItem: menuItem,
),
ActionButtonType.likeActivity => LikeActivityActionButton(iconOnly: iconOnly, menuItem: menuItem),
ActionButtonType.unstack => ActionMenuItemWidget(action: StackAction(assets: [context.asset])),
ActionButtonType.unstack => UnStackActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem),
ActionButtonType.openInBrowser => OpenInBrowserActionButton(
remoteId: context.asset.remoteId!,
origin: context.timelineOrigin,
-23
View File
@@ -1,23 +0,0 @@
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
extension type const AssetFilter<T extends BaseAsset>(Iterable<T> assets) implements Iterable<T> {
AssetFilter<T> where(bool Function(T asset) test) => AssetFilter(assets.where(test));
AssetFilter<T> whereNot(bool Function(T asset) test) => AssetFilter(assets.where((asset) => !test(asset)));
AssetFilter<T> type(AssetType type) => where((asset) => asset.type == type);
AssetFilter<T> favorite({bool isFavorite = true}) => where((asset) => asset.isFavorite == isFavorite);
AssetFilter<RemoteAsset> remote() => AssetFilter(assets.whereType<RemoteAsset>());
AssetFilter<RemoteAsset> owned(String ownerId) => remote().where((asset) => asset.ownerId == ownerId);
AssetFilter<RemoteAsset> visibility(AssetVisibility visibility) =>
remote().where((asset) => asset.visibility == visibility);
AssetFilter<RemoteAsset> notVisibility(AssetVisibility visibility) =>
remote().where((asset) => asset.visibility != visibility);
AssetFilter<RemoteAsset> archived({bool isArchived = true}) =>
remote().where((asset) => asset.isArchived == isArchived);
AssetFilter<RemoteAsset> stacked({bool isStacked = true}) => remote().where((asset) => asset.isStacked == isStacked);
AssetFilter<RemoteAsset> trashed({bool isTrashed = true}) => remote().where((asset) => asset.isTrashed == isTrashed);
AssetFilter<LocalAsset> local() => AssetFilter(assets.whereType<LocalAsset>());
AssetFilter<LocalAsset> backedUp() => local().where((asset) => asset.remoteAssetId != null);
}
+7 -3
View File
@@ -8,7 +8,7 @@ import 'package:openapi/api.dart';
// ignore: depend_on_referenced_packages
import 'package:stack_trace/stack_trace.dart';
void handleError(Object error, {StackTrace? stack, String? description}) {
void handleError(BuildContext context, Object error, {StackTrace? stack, String? description}) {
String? stackTrace;
if (stack != null) {
final trace = Trace.from(stack);
@@ -23,13 +23,17 @@ void handleError(Object error, {StackTrace? stack, String? description}) {
() => 'Error${description != null ? ' ($description)' : ''}: $error${stackTrace != null ? '\n$stackTrace' : ''}',
);
if (!context.mounted) {
return;
}
final String message;
if (serverErrorMessage(error) case String serverMessage) {
message = serverMessage;
} else if (isConnectionError(error)) {
message = StaticTranslations.instance.login_form_server_error;
message = context.t.login_form_server_error;
} else {
message = StaticTranslations.instance.scaffold_body_error_occurred;
message = context.t.scaffold_body_error_occurred;
}
snackbar.error(message);
+1
View File
@@ -26,6 +26,7 @@ final Map<String, Map<String, Object?>> openApiPatches = {
'sharedLinks': SharedLinksResponse(enabled: true, sidebarWeb: false).toJson(),
'cast': CastResponse(gCastEnabled: false).toJson(),
'albums': {'defaultAssetOrder': 'desc'},
'recentlyAdded': RecentlyAddedResponse(sidebarWeb: false).toJson(),
},
'ServerConfigDto': {
'mapLightStyleUrl': 'https://tiles.immich.cloud/v1/style/light.json',
-13
View File
@@ -1,4 +1,3 @@
import 'package:drift/drift.dart';
import 'package:openapi/api.dart' show Optional;
sealed class Option<T> {
@@ -22,11 +21,6 @@ sealed class Option<T> {
None() => null,
};
Option<U> map<U>(U Function(T value) f) => switch (this) {
Some(:final value) => Some(f(value)),
None() => None<U>(),
};
U fold<U>(U Function(T value) onSome, U Function() onNone) => switch (this) {
Some(:final value) => onSome(value),
None() => onNone(),
@@ -71,10 +65,3 @@ extension OptionToOptional<T> on Option<T> {
Some(:final value) => Optional.present(value),
};
}
extension OptionToDriftValue<T> on Option<T> {
Value<T> toDriftValue() => switch (this) {
Some(:final value) => Value(value),
None() => const Value.absent(),
};
}
+1 -1
View File
@@ -1,6 +1,6 @@
import 'package:immich_mobile/utils/semver.dart';
String? getVersionCompatibilityMessage(SemVer serverVersion, SemVer appVersion) {
String? getVersionCompatibilityMessage({required SemVer serverVersion, required SemVer appVersion}) {
// Add latest compat info up top
// ensure mobile app major version is not behind server major version
+18 -5
View File
@@ -18,6 +18,7 @@ import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/feature_message.provider.dart';
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/providers/oauth.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
@@ -91,7 +92,7 @@ class LoginForm extends HookConsumerWidget {
final packageInfo = await PackageInfo.fromPlatform();
final appSemVer = SemVer.fromString(packageInfo.version);
final serverSemVer = serverInfo.serverVersion;
warningMessage.value = getVersionCompatibilityMessage(appSemVer, serverSemVer);
warningMessage.value = getVersionCompatibilityMessage(serverVersion: serverSemVer, appVersion: appSemVer);
} catch (error) {
warningMessage.value = 'Error checking version compatibility';
}
@@ -254,6 +255,7 @@ class LoginForm extends HookConsumerWidget {
}
unawaited(handleSyncFlow());
ref.read(websocketProvider.notifier).connect();
unawaited(ref.read(featureMessageServiceProvider).markSeen());
unawaited(context.router.replaceAll([const TabShellRoute()]));
return;
}
@@ -341,6 +343,7 @@ class LoginForm extends HookConsumerWidget {
await getManageMediaPermission();
}
unawaited(handleSyncFlow());
unawaited(ref.read(featureMessageServiceProvider).markSeen());
unawaited(context.router.replaceAll([const TabShellRoute()]));
return;
}
@@ -377,11 +380,21 @@ class LoginForm extends HookConsumerWidget {
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: context.isDarkTheme ? Colors.red.shade700 : Colors.red.shade100,
borderRadius: const BorderRadius.all(Radius.circular(8)),
border: Border.all(color: context.isDarkTheme ? Colors.red.shade900 : Colors.red[200]!),
color: context.isDarkTheme ? Colors.amber.shade700 : Colors.amber.shade100,
borderRadius: const BorderRadius.all(Radius.circular(12)),
border: Border.all(color: context.isDarkTheme ? Colors.amber.shade800 : Colors.amber[200]!, width: 2),
),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(Icons.warning_amber_rounded, color: Colors.amber.shade800),
const SizedBox(width: 8),
Expanded(
child: Padding(padding: const EdgeInsets.only(top: 2), child: Text(warningMessage.value!)),
),
],
),
child: Text(warningMessage.value!, textAlign: TextAlign.center),
),
);
}
+3 -1
View File
@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 3.0.0-rc.4
- API version: 3.0.0
- Generator version: 7.22.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen
@@ -545,6 +545,8 @@ Class | Method | HTTP request | Description
- [RatingsUpdate](doc//RatingsUpdate.md)
- [ReactionLevel](doc//ReactionLevel.md)
- [ReactionType](doc//ReactionType.md)
- [RecentlyAddedResponse](doc//RecentlyAddedResponse.md)
- [RecentlyAddedUpdate](doc//RecentlyAddedUpdate.md)
- [ReleaseChannel](doc//ReleaseChannel.md)
- [ReleaseEventV1](doc//ReleaseEventV1.md)
- [ReleaseType](doc//ReleaseType.md)
+2
View File
@@ -266,6 +266,8 @@ part 'model/ratings_response.dart';
part 'model/ratings_update.dart';
part 'model/reaction_level.dart';
part 'model/reaction_type.dart';
part 'model/recently_added_response.dart';
part 'model/recently_added_update.dart';
part 'model/release_channel.dart';
part 'model/release_event_v1.dart';
part 'model/release_type.dart';
+4
View File
@@ -577,6 +577,10 @@ class ApiClient {
return ReactionLevelTypeTransformer().decode(value);
case 'ReactionType':
return ReactionTypeTypeTransformer().decode(value);
case 'RecentlyAddedResponse':
return RecentlyAddedResponse.fromJson(value);
case 'RecentlyAddedUpdate':
return RecentlyAddedUpdate.fromJson(value);
case 'ReleaseChannel':
return ReleaseChannelTypeTransformer().decode(value);
case 'ReleaseEventV1':

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